当前位置: 首页 > news >正文

Spring Boot 3 整合 MinIO 实现分布式文件存储

引言

文件存储已成为一个做任何应用都不可回避的需求。传统的单机文件存储方案在面对大规模数据和高并发访问时往往力不从心,而分布式文件存储系统则提供了更好的解决方案。本篇文章我将基于Spring Boot 3 为大家讲解如何基于MinIO来实现分布式文件存储。

分布式存储的出现

在探讨核心内容之前,我们不妨先回顾分布式存储技术是如何伴随系统架构演变发展的。在单体架构早期,文件直接存储于应用服务器中,这种方式简单直接,存取便捷。然而,随着业务规模扩大和用户量激增,系统架构逐步向分布式或微服务方向演进。此时,若仍将文件存储在应用服务器中,在负载均衡机制下可能导致文件访问异常 —— 用户上传的文件可能因路由到其他服务节点而无法访问。
在这里插入图片描述面对这个挑战,我们可以借鉴"分层解决"的架构思想:将文件存储从应用服务中剥离,集中在独立的存储服务中统一管理。这便是分布式文件存储系统的雏形。

技术选型

在了解了分布式存储的演进背景后,让我们来梳理当前主流的分布式存储解决方案。

其他

  • FastDFS -> 架构老旧,社区活跃度低,文档资料匮乏
  • Ambry -> 过度依赖 LinkedIn 技术栈,通用性不足
  • MooseFS -> 部署配置繁琐,运维门槛高
  • MogileFS -> 性能一般,扩展性受限
  • LeoFS -> 更新维护缓慢,生态系统不完善
  • openstack -> 架构复杂重量级,不适合轻量级应用
  • TFS -> 主要服务于阿里内部,外部支持有限
  • ceph -> 学习曲线陡峭,配置调优复杂
  • GlusterFS -> 架构复杂,问题定位困难
  • OSS -> 商业收费服务,成本随数据量增长

✨MinIO

MinIO 是一款轻量级的分布式对象存储系统,完全兼容 Amazon S3 云存储服务接口。其部署维护简单,性能卓越,成为我们的首选方案。

MinIO安装

MinIO 提供了多种部署方式,包括单机部署和分布式部署。本文主要关注 Spring BootMinIO 的整合实践,因此我们选择使用Docker(Ps:没安装Docker的同学速速去安装,或者用别的方式只要本地部署的能跑就行)进行快速部署。

首先,通过命令拉取镜像。

docker pull minio/minio

接着在 本地创建一个存储文件的映射目录 D:\minio\data(Ps:我当前演示的环境是win系统,大家根据自己的操作系统建个目录就行),使用以下命令启动 MinIO:

🔑 补充一个小细节:MinIO 的安全限制要求用户名长度至少需要 3 个字符,密码长度至少需要 8 个字符。

    docker run -d --name minio -p 9000:9000 -p 9001:9001 -v D:\minio\data:/data -e "MINIO_ROOT_USER=root" -e "MINIO_ROOT_PASSWORD=12345678" minio/minio server /data --console-address ":9001" --address ":9000"

参数说明:

  • -d: 后台运行容器
  • --name: 容器名称
  • -p: 端口映射,9000用于API访问,9001用于控制台访问
  • -v: 目录映射,将本地目录映射到容器的 /data
  • -e: 环境变量,设置管理员账号和密码
  • --console-address: 指定控制台端口
  • --restart=always: 容器自动重启策略
  • --address ":9000": 显式指定 API 端口

运行成功后访问 http://localhost:9001,使用执行命令中的凭据(Ps:大家在使用时可以修改为自己的用户名和密码)登录:

  • 用户名:root
  • 密码:12345678
    在这里插入图片描述登录系统后,界面会提示创建。熟悉云服务商OSS服务的读者对此概念应该不陌生。对初次接触的读者,可以将理解为一个命名空间或文件夹,您可以创建多个,每个内还能包含多层级的文件夹和文件。

在这里插入图片描述这里我演示下控制台如何建桶和上传文件,方便大家理解文件在MinIO上的存储结构。
在这里插入图片描述只需要输入名称就可以,建好之后可以看到的使用状态。
在这里插入图片描述点击它进入的内部,这里大家需要关注一个设置- Access Policy,默认是Private。这个设置需要根据业务的实际情况来,如果你的业务是需要提供一些不需要鉴权的公共访问的文件,就设为public;反之,就保持private。我这里把它修改为public
在这里插入图片描述然后点击右上角的上传按钮进入上传页可以向桶内上传文件。
在这里插入图片描述上传成功后可以在桶内看到文件。
在这里插入图片描述点击文件可查看详情,支持预览、删除、分享等多种功能。这些操作较为直观,安装后各位读者可自行体验。本文重点关注不在控制台的操作,就不做过多赘述了。

🔑这里再强调一点:存储在里的文件通过API访问的端口和控制台是不一样的。如果你对这里感觉迷惑,可以回看一下上面我贴上的docker运行命令里配置了两个端口-90009001。如果要通过API访问查看这个文件的话,通过拼接地址/端口号/桶名/文件路径查看,那么刚测试上传的文件的访问API就是http://localhost:9000/test/1.gif,在浏览器地址栏输入后可以看到。
在这里插入图片描述# Spring Boot整合MinIO

这部分对于新建项目就不赘述了,直接说下我使用的 Spring boot 版本为3.2.3,供大家参考。

1.引入依赖

pom.xml引入minIO的依赖,版本大家自己使用你当前最新的版本即可。

<!-- minio -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>${latest.version}</version>
</dependency>

2.添加配置

在yml配置文件中配置连接信息。

# minIO配置
minio:endpoint: http://127.0.0.1:9000     # MinIO服务地址fileHost: http://127.0.0.1:9000     # 文件地址hostbucketName: wechat                  # 存储桶bucket名称accessKey: root                     # 用户名secretKey: 12345678                 # 密码

3.编写配置类

import com.pitayafruits.utils.MinIOUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@Data
public class MinIOConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.fileHost}")private String fileHost;@Value("${minio.bucketName}")private String bucketName;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinIOUtils creatMinioClient() {return new MinIOUtils(endpoint, fileHost, bucketName, accessKey, secretKey);}
}

4.引入工具类

这个工具类封装了MinIO的核心功能,为您提供了很多开箱即用的功能。通过引入它,可以轻松实现文件上传、下载等操作,让大家将更多精力集中在业务开发上。

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;/*** MinIO工具类*/
@Slf4j
public class MinIOUtils {private static MinioClient minioClient;private static String endpoint;private static String fileHost;private static String bucketName;private static String accessKey;private static String secretKey;private static final String SEPARATOR = "/";public MinIOUtils() {}public MinIOUtils(String endpoint, String fileHost, String bucketName, String accessKey, String secretKey) {MinIOUtils.endpoint = endpoint;MinIOUtils.fileHost = fileHost;MinIOUtils.bucketName = bucketName;MinIOUtils.accessKey = accessKey;MinIOUtils.secretKey = secretKey;createMinioClient();}/*** 创建基于Java端的MinioClient*/public void createMinioClient() {try {if (null == minioClient) {log.info("开始创建 MinioClient...");minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();createBucket(bucketName);log.info("创建完毕 MinioClient...");}} catch (Exception e) {log.error("MinIO服务器异常:{}", e);}}/*** 获取上传文件前缀路径* @return*/public static String getBasisUrl() {return endpoint + SEPARATOR + bucketName + SEPARATOR;}/******************************  Operate Bucket Start  ******************************//*** 启动SpringBoot容器的时候初始化Bucket* 如果没有Bucket则创建* @throws Exception*/private static void createBucket(String bucketName) throws Exception {if (!bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/***  判断Bucket是否存在,true:存在,false:不存在* @return* @throws Exception*/public static boolean bucketExists(String bucketName) throws Exception {return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 获得Bucket的策略* @param bucketName* @return* @throws Exception*/public static String getBucketPolicy(String bucketName) throws Exception {String bucketPolicy = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());return bucketPolicy;}/*** 获得所有Bucket列表* @return* @throws Exception*/public static List<Bucket> getAllBuckets() throws Exception {return minioClient.listBuckets();}/*** 根据bucketName获取其相关信息* @param bucketName* @return* @throws Exception*/public static Optional<Bucket> getBucket(String bucketName) throws Exception {return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在* @param bucketName* @throws Exception*/public static void removeBucket(String bucketName) throws Exception {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());}/******************************  Operate Bucket End  ******************************//******************************  Operate Files Start  ******************************//*** 判断文件是否存在* @param bucketName 存储桶* @param objectName 文件名* @return*/public static boolean isObjectExist(String bucketName, String objectName) {boolean exist = true;try {minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());} catch (Exception e) {exist = false;}return exist;}/*** 判断文件夹是否存在* @param bucketName 存储桶* @param objectName 文件夹名称* @return*/public static boolean isFolderExist(String bucketName, String objectName) {boolean exist = false;try {Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());for (Result<Item> result : results) {Item item = result.get();if (item.isDir() && objectName.equals(item.objectName())) {exist = true;}}} catch (Exception e) {exist = false;}return exist;}/*** 根据文件前置查询文件* @param bucketName 存储桶* @param prefix 前缀* @param recursive 是否使用递归查询* @return MinioItem 列表* @throws Exception*/public static List<Item> getAllObjectsByPrefix(String bucketName,String prefix,boolean recursive) throws Exception {List<Item> list = new ArrayList<>();Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());if (objectsIterator != null) {for (Result<Item> o : objectsIterator) {Item item = o.get();list.add(item);}}return list;}/*** 获取文件流* @param bucketName 存储桶* @param objectName 文件名* @return 二进制流*/public static InputStream getObject(String bucketName, String objectName) throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 断点下载* @param bucketName 存储桶* @param objectName 文件名称* @param offset 起始字节的位置* @param length 要读取的长度* @return 二进制流*/public InputStream getObject(String bucketName, String objectName, long offset, long length)throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());}/*** 获取路径下文件列表* @param bucketName 存储桶* @param prefix 文件名称* @param recursive 是否递归查找,false:模拟文件夹结构查找* @return 二进制流*/public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,boolean recursive) {return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());}/*** 使用MultipartFile进行文件上传* @param bucketName 存储桶* @param file 文件名* @param objectName 对象名* @param contentType 类型* @return* @throws Exception*/public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,String objectName, String contentType) throws Exception {InputStream inputStream = file.getInputStream();return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream, inputStream.available(), -1).build());}/*** 上传本地文件* @param bucketName 存储桶* @param objectName 对象名称* @param fileName 本地文件路径*/public static String uploadFile(String bucketName, String objectName,String fileName, boolean needUrl) throws Exception {minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());if (needUrl) {String imageUrl = fileHost+ "/"+ bucketName+ "/"+ objectName;return imageUrl;}return "";}/*** 通过流上传文件** @param bucketName 存储桶* @param objectName 文件对象* @param inputStream 文件流*/public static ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());}public static String uploadFile(String bucketName, String objectName, InputStream inputStream, boolean needUrl) throws Exception {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());if (needUrl) {String imageUrl = fileHost+ "/"+ bucketName+ "/"+ objectName;return imageUrl;}return "";}/*** 创建文件夹或目录* @param bucketName 存储桶* @param objectName 目录路径*/public static ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());}/*** 获取文件信息, 如果抛出异常则说明文件不存在** @param bucketName 存储桶* @param objectName 文件名称*/public static String getFileStatusInfo(String bucketName, String objectName) throws Exception {return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();}/*** 拷贝文件** @param bucketName 存储桶* @param objectName 文件名* @param srcBucketName 目标存储桶* @param srcObjectName 目标文件名*/public static ObjectWriteResponse copyFile(String bucketName, String objectName,String srcBucketName, String srcObjectName) throws Exception {return minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(objectName).build()).bucket(srcBucketName).object(srcObjectName).build());}/*** 删除文件* @param bucketName 存储桶* @param objectName 文件名称*/public static void removeFile(String bucketName, String objectName) throws Exception {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 批量删除文件* @param bucketName 存储桶* @param keys 需要删除的文件列表* @return*/public static void removeFiles(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();keys.forEach(s -> {objects.add(new DeleteObject(s));try {removeFile(bucketName, s);} catch (Exception e) {log.error("批量删除失败!error:{}",e);}});}/*** 获取文件外链* @param bucketName 存储桶* @param objectName 文件名* @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒))* @return url* @throws Exception*/public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();return minioClient.getPresignedObjectUrl(args);}/*** 获得文件外链* @param bucketName* @param objectName* @return url* @throws Exception*/public static String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(Method.GET).build();return minioClient.getPresignedObjectUrl(args);}/*** 将URLDecoder编码转成UTF8* @param str* @return* @throws UnsupportedEncodingException*/public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");return URLDecoder.decode(url, "UTF-8");}/******************************  Operate Files End  ******************************/}

5.开发测试

我刚好在做练手项目,这里写个上传头像的接口。

import com.pitayafruits.base.BaseInfoProperties;
import com.pitayafruits.config.MinIOConfig;
import com.pitayafruits.grace.result.GraceJSONResult;
import com.pitayafruits.grace.result.ResponseStatusEnum;
import com.pitayafruits.utils.MinIOUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("file")
public class FileController extends BaseInfoProperties {@Resourceprivate MinIOConfig minIOConfig;@PostMapping("uploadFace")public GraceJSONResult upload(@RequestParam MultipartFile file,String userId) throws Exception {if (StringUtils.isBlank(userId)) {return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);}String filename = file.getOriginalFilename();if (StringUtils.isBlank(filename)) {return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);}filename = "face" +  "/" + userId + "/" + filename;MinIOUtils.uploadFile(minIOConfig.getBucketName(), filename, file.getInputStream());String faceUrl = minIOConfig.getFileHost()+ "/"+ minIOConfig.getBucketName()+ "/"+ filename;return GraceJSONResult.ok(faceUrl);}}

可以看到通过工具类只需要一行代码就可以实现上传文件,我们只需要在调用的时候做好文件的业务隔离即可。完成了接口的开发,这里我来通过Apifox调用测试一下。
在这里插入图片描述通过浏览器访问返回的图片链接会自动下载,我们再登录控制台看对应的桶下的这个路径,也可以看到这个文件。
在这里插入图片描述

小结

我们在集成第三方服务时应遵循一个核心原则:将API操作封装成通用工具类。这不仅让MinIO的集成更加优雅,也让代码具备更好的复用性和可维护性。这种思维方式同样适用于其他第三方服务的对接。

相关文章:

Spring Boot 3 整合 MinIO 实现分布式文件存储

引言 文件存储已成为一个做任何应用都不可回避的需求。传统的单机文件存储方案在面对大规模数据和高并发访问时往往力不从心&#xff0c;而分布式文件存储系统则提供了更好的解决方案。本篇文章我将基于Spring Boot 3 为大家讲解如何基于MinIO来实现分布式文件存储。 分布式存…...

ubuntu20 安装python2

1. 确保启用了 Universe 仓库 在某些情况下&#xff0c;python2-minimal 包可能位于 Universe 仓库中。你可以通过以下命令启用 Universe 仓库并更新软件包列表&#xff1a; bash复制 sudo add-apt-repository universe sudo apt update 然后尝试安装&#xff1a; bash复制…...

2025.3.3总结

周一这天&#xff0c;我约了绩效教练&#xff0c;主要想了解专业类绩效的考核方式以及想知道如何拿到一个更好的绩效。其他的岗位并不是很清楚&#xff0c;但是专业类的岗位&#xff0c;目前采取绝对考核&#xff0c;管理层和专家岗采取相对考核&#xff0c;有末尾淘汰。 通过…...

多线程-JUC源码

简介 JUC的核心是AQS&#xff0c;大部分锁都是基于AQS扩展出来的&#xff0c;这里先结合可重入锁和AQS&#xff0c;做一个讲解&#xff0c;其它的锁的实现方式也几乎类似 ReentrantLock和AQS AQS的基本结构 AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象队列…...

ICLR 2025|香港浸会大学可信机器学习和推理课题组专场

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; AITIME 01 ICLR 2025预讲会团队专场 AITIME 02 专场信息 01 Noisy Test-Time Adaptation in Vision-Language Models 讲者&#xff1a;曹晨涛&#xff0c;HKBU TMLR Group一年级博士生&#xff0c;目前关注基础…...

docker引擎备份及解决拉取失败的问题

总结一下本文&#xff0c;docker引擎不是越多越好&#xff0c;此外阿里云的容器引擎加速可适用大多数情况。 docker引擎备份 仅使用阿里云 docker引擎备份&#xff0c;唯一使用的镜像地址是我的阿里云docker镜像加速地址&#xff0c;效果好&#xff08;注意下面的阿里云镜像加…...

Django项目实战

1、安装django 查看包安装的位置 pip镜像源 镜像源名称镜像地址​清华源​https://pypi.tuna.tsinghua.edu.cn/simple​阿里云​https://mirrors.aliyun.com/pypi/simple​腾讯云​https://mirrors.cloud.tencent.com/pypi/simple​华为云​https://repo.huaweicloud.co…...

【ThreeJS Basics 1-6】Camera

文章目录 Camera 相机PerspectiveCamera 透视相机正交相机用鼠标控制相机大幅度转动&#xff08;可以看到后面&#xff09; 控制组件FlyControls 飞行组件控制FirstPersonControls 第一人称控制PointerLockControls 指针锁定控制OrbitControls 轨道控制TrackballControls 轨迹球…...

SpringBoot-模拟SSE对话交互

SpringBoot-模拟SSE对话交互 后端使用SSE进行会话&#xff0c;前端使用Html模拟大模型的问答交互->【前端】【后端】 1-学习目的 本项目代码仓库&#xff1a;https://gitee.com/enzoism/springboot_sse 1-核心知识点 1&#xff09;什么是SSE协议->客户端发起一次请求&am…...

删除链表的倒数第N个节点 力扣19

一、题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&a…...

IvorySQL v4 逻辑复制槽同步功能解析:高可用场景下的数据连续性保障

功能简介 IvorySQL v4 基于 PostgreSQL 17&#xff0c;引入了逻辑复制槽同步至热备份数据库的功能。这一改进有效解决了旧版本中主数据库与备份数据库切换后逻辑复制中断的问题。对于那些追求数据高可用性和业务连续性的数据库来说&#xff0c;这无疑是一个重大的利好消息。它…...

vxe-table开启表尾和el-collapse-transition不兼容,动画卡顿

调用控制台 发现是el-collapse-transition内置的计算高度函数计算的高度总是会高一点 直接放弃使用el-collapse-transition 使用下面的div包裹住vxe-table 我的table是渲染出来的会有多个 <el-button click"group.messShow !group.messShow" type"text&q…...

康谋分享 | 3DGS:革新自动驾驶仿真场景重建的关键技术

随着自动驾驶技术的迅猛发展&#xff0c;构建高保真、动态的仿真场景成为了行业的迫切需求。传统的三维重建方法在处理复杂场景时常常面临效率和精度的挑战。在此背景下&#xff0c;3D高斯点阵渲染&#xff08;3DGS&#xff09;技术应运而生&#xff0c;成为自动驾驶仿真场景重…...

golang学习笔记——go语言安装及系统环境变量设置

文章目录 go语言安装go envgo getgoproxy测试安装 Go 插件安装 Go 插件依赖工具参考资料用户环境变量和系统环境变量用户环境变量系统环境变量示例设置环境变量的步骤设置用户环境变量设置系统环境变量 验证环境变量总结 2024年最火的5大Go框架1. Gin&#xff1a;高并发接口的“…...

Redis|集群 Cluster

文章目录 是什么能干嘛集群算法-分片-槽位slotredis集群的槽位slotredis集群的分片分片槽位的优势slot槽位映射——业界的3种解决方案小厂&#xff1a;哈希取余分区中厂&#xff1a;一致性哈希算法分区大厂&#xff1a;哈希槽分区 面试题&#xff1a;为什么 Redis 集群的最大槽…...

解锁MacOS开发:环境配置与应用开发全攻略

✨✨✨这里是小韩学长yyds的BLOG(喜欢作者的点个关注吧) ✨✨✨想要了解更多内容可以访问我的主页 小韩学长yyds-CSDN博客 目录 引言 一、MacOS 开发环境配置 &#xff08;一&#xff09;必备工具安装 &#xff08;二&#xff09;集成开发环境&#xff08;IDE&#xff09;选…...

如何通过卷积神经网络(CNN)有效地提取图像的局部特征,并在CIFAR-10数据集上实现高精度的分类?

目录 1. CNN 提取图像局部特征的原理 2. 在 CIFAR - 10 数据集上实现高精度分类的步骤 2.1 数据准备 2.2 构建 CNN 模型 2.3 定义损失函数和优化器 2.4 训练模型 2.5 测试模型 3. 提高分类精度的技巧 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#…...

监听 RabbitMQ 延时交换机的消息数、OpenFeign 路径参数传入斜杠无法正确转义

背景 【MQ】一套为海量消息和高并发热点消息&#xff0c;提供高可用精准延时服务的解决方案 我现在有一个需求&#xff0c;就是监听 RabbitMQ 一个延时交换机的消息数&#xff0c;而 RabbitTemplate 是不存在对应的方法来获取的。 而我们在 RabbitMQ 的控制台却可以发现延时交…...

希音(Shein)前端开发面试题集锦和参考答案

用 Node 写过什么工具或 npm 包 在实际开发中,使用 Node 编写过多种实用工具和 npm 包。 自动化构建工具 开发了一个简单的自动化构建工具,用于处理前端项目的资源压缩和合并。在前端项目中,为了优化性能,需要对 CSS 和 JavaScript 文件进行压缩,减少文件体积,同时将多个…...

python全栈-Linux基础

python全栈-Linux基础 文章目录 Linux安装/配置网络配置配置Linux远程登录配置虚拟机内部ip配置真机的ip安装XShell和Xftp目录结构用户和用户组用户管理添加用户useradd查看用户id修改用户usermod (选项)(参数)用户密码设置passed (选项)(参数)删除用户userdel [选项] 用户名 用…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...

Mysql故障排插与环境优化

前置知识点 最上层是一些客户端和连接服务&#xff0c;包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念&#xff0c;为通过安全认证接入的客户端提供线程。同样在该层上可…...