注:集群教程不建议直接上生产!! 可自行先在测试环境先验证
单机部署minio
官方中文文档地址:
https://minio.org.cn/docs/minio/linux/operations/install-deploy-manage/deploy-minio-single-node-single-drive.html
官方提供了三种方式,本文采用的是二进制方式
step1: 下载二进制文件
wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio
step2:权限改成可执行
chmod +x minio
step3: 可选 博主没执行这步
(和windows配置环境变量的功能类似)
sudo mv minio /usr/local/bin/
step4: 二进制方式 需要手动创建service文件
(注:保证/etc/systemd/system/minio.service 和 /usr/lib/systemd/system/minio.service只有一份
否则会冲突,官方推荐 /usr/lib/systemd/system/minio.service原因大概是其它方式安装默认就在这个路径下。
本文是二进制安装方式 经常看博主文章的同学应该能发现 我都是尽可能选择免安装的方式
此外 博主并没按推荐路径 也是为了加强一下记忆 选择避免节外生枝的同学可以按推荐路径 )
tips:
# 进入目录, 避免节外生枝可以选择 /usr/lib/systemd/system/ 路径
cd /etc/systemd/system
# 查看有哪些服务
ls
#复制个文件(博主复制的syslog.service)命名为minio.service
cp syslog.service minio.service
这样就可以得到一个格式正确的service文件
minio.service样例:
(注意替换Environment的账号密码以及ExecStart的路径,
不同版本可能配置会有差异 博主使用的minio是截止2024-05-22的最新版本)
[Unit]
Description=MinIO
Documentation=https://minio.org.cn/docs/minio/linux/index.html
[Service]
# create access and secret ,replace your self ,创建新的access secret
Environment="MINIO_ACCESS_KEY=mengqiuyuni"
Environment="MINIO_SECRET_KEY=12345678"
# modify minioadmin account ,这里则是修改默认账号minioadmin的密码
Environment="MINIO_ROOT_USER=minioadmin"
Environment="MINIO_ROOT_PASSWORD=12345678"
# replace yourself minio path and data dir path
#ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
ExecStart=/app/minio/minio server /app/minio/minio_data/
# MinIO RELEASE.2023-05-04T21-44-30Z adds support for Type=notify (https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=)
# This may improve systemctl setups where other services use `After=minio.server`
# Uncomment the line to enable the functionality
# Type=notify
# Let systemd restart this service always (×) on-failure(√)
Restart=on-failure
RestartSec=15
# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536
# Specifies the maximum number of threads this process can create
TasksMax=infinity
# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
# Built for ${project.name}-${project.version} (${project.name})
step5: 测试启动是否成功
(假设minio二进制文件在 /app/minio/minio目录 ,data文件夹需要创建 假设在/app/minio/minio_data/ )
/app/minio/minio server /app/minio/minio_data/
( 如果执行了第三步 那命令就为minio server /app/minio/minio_data/)
step6: 启动服务
# 重新加载
systemctl daemon-reload
# 启动服务
systemctl start minio.service
(注:systemctl start minio.service方式启动 才读取minio.service配置,
如果用minio server /minio_data 的方式启动 不会读取minio.service ,则需要我们先export环境变量)
step7: 检测是否启动成功
sudo systemctl status minio.service
journalctl -f -u minio.service
step8: 访问网页 检查是否能正常登录
网页打开 localhost:9000 (假设在本机)
输入minio.service配置的账号密码登录
集群部署minio
集群部署的核心在于需要挂载磁盘 否则会报错:drive is part of root drive
本文教程为多节点多磁盘的集群方式 : 2节点 4磁盘 即每个节点对应2个磁盘
(单节点并不算真正的集群)
挂载磁盘步骤:
- 创建虚拟设备命名minio bs=1M count=1024 表示创建1m*1024 = 1G 这里仅做测试用 自行调整大小
sudo dd if=/dev/zero of=/minio bs=1M count=1024
- 格式化
sudo mkfs.ext4 /minio
如果我们的 /app/minio/minio_data目录已经单机部署过
现在想集群部署 那需先清空文件夹 (前提是测试环境 且数据不重要)
否则会报错:unable initialize backend: /app/minio/minio data drive is already being used in another erasure deployment. (Number of drives specified:+ but the number of drivesfound in the 1st drive’s format.json
(当然新建另一个文件夹也行)
清空文件夹步骤:依次执行命令
cd /app/minio/minio_data
ls -a
rm -rf ./minio.sys/
#一定要先cd到minio_data目录再用下面这个命令!!
rm -rf *.*
- 挂载磁盘
/app/minio/minio_data是数据目录 不懂参考上文单机教程就知道是什么了
sudo mount /minio /app/minio/minio_data
如果挂载输错了 使用 umount卸载 例如 :umount /minio
多磁盘就是指要挂载多个磁盘 我们需要再创建一个和minio_data类似的目录
(否则会报错:read failed insufficient number of drives online )
# 创建目录
mkdir /app/minio/minio_data_sec
# 对minio_data_sec 文件夹 也来一遍挂载操作
sudo dd if=/dev/zero of=/minio_sec bs=1M count=1024
sudo mkfs.ext4 /minio_sec
sudo mount /minio_sec /app/minio/minio_data_sec
- 永久挂载
mount命令挂载是一次性的,服务器重启磁盘挂载就掉了 所以我们需要永久挂载
永久挂载有两种方式 一种是挂载设备路径 一种是挂载uuid ,挂载设备路径的方式更简单
但设备重命令等操作后就会丢失,所以我们本文采用uuid方式
查看设备uuid命令:
blkid
记下这个uuid 编辑/etc/fstab 文件
vim /etc/fstab
在文件末尾添加 例如:
UUID=4c748d5d-6b45-4781-8e2e-894a8de1b5aa /app/minio/minio_data ext4 defaults 0 0
UUID=3c748d5d-6b45-4781-4a3b-463b2ce2a4ce /app/minio/minio_data_sec ext4 defaults 0 0
- 使用下面命令来检测是否挂载成功:
lsblk
# 或
df -h
建议使用lsblk ,因为不小心重复挂载了 用df -h体现不出来
当然 上面命令只是检测有没有临时挂载成功,检测永久挂载是否成功的方式自然是重启服务器了
# 重启
reboot
-
挂载成功后,另一台服务器执行一模一样的操作即可
(创建两个数据文件夹+挂载两次磁盘) -
启动命令:
(注意:这里启动第一个节点后会一直报错 这是正常的,因为磁盘数量不够 在启动第二个节点后 报错就停止了 )需要先修改一下 minio.service文件
[Unit] Description=MinIO Documentation=https://minio.org.cn/docs/minio/linux/index.html # replace your minio file path AssertFileIsExecutable=/app/minio/minio [Service] # 集群中MINIO_ACCESS_KEY ,MINIO_SECRET_KEY旧配置已经失效了 #Environment="MINIO_ACCESS_KEY=yourAccess" #Environment="MINIO_SECRET_KEY=12345678" Environment="MINIO_ROOT_USER=minioadmin" Environment="MINIO_ROOT_PASSWORD=12345678" # replace yourself minio path and data dir path #ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES ExecStart=/app/minio/minio server http://192.168.0.1/app/minio/minio_data http://192.168.0.1/app/minio/minio_data_sec http://192.168.0.2/app/minio/minio_data http://192.168.0.2/app/minio/minio_data_sec # This may improve systemctl setups where other services use `After=minio.server` # Uncomment the line to enable the functionality # Type=notify # Let systemd restart this service always (×) on-failure(√) Restart=on-failure RestartSec=15 # Specifies the maximum file descriptor number that can be opened by this process LimitNOFILE=65536 # Specifies the maximum number of threads this process can create TasksMax=infinity # Disable timeout logic and wait until process is stopped TimeoutStopSec=infinity SendSIGKILL=no [Install] WantedBy=multi-user.target # Built for ${project.name}-${project.version} (${project.name})
单机部署教程说过
/app/minio/minio server http://… http://…
这样直接启动是没走minio.service的, 会使用默认账号密码(生产严禁)
所以我们通过systemctl start minio.service来启动
启动后一定要使用下列命令检测是否成功
journalctl -xe
如果报错:Assertion failed on job for minio.service.
那则是AssertFileIsExecutable 路径写错了,填写minio文件的全路径
有的时候单词拼写错误,也会有提示 例如:
- 验证
在http://192.168.0.1:9001/ 和 http://192.168.0.2:9001/ 都能看到这个页面
servers 2 drives 4即表示集群部署成功
不放心可以上传张图片,两台服务器的data目录都保存了该图片 即为成功
springboot整合
maven依赖
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.10</version>
</dependency>
<!-- lombok springboot依赖略 -->
minio bean config:
@Data
@Configuration
@EnableConfigurationProperties({MinIOConfigProperties.class})
@ConditionalOnClass(FileStorageService.class)
public class MinIOConfig {
@Autowired
private MinIOConfigProperties minIOConfigProperties;
@Bean
public MinioClient buildMinioClient() {
return MinioClient
.builder()
.endpoint(minIOConfigProperties.getEndpoint())
.credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
.build();
}
}
@Data
@ConfigurationProperties(prefix = "minio")
public class MinIOConfigProperties implements Serializable {
private String accessKey;
private String secretKey;
private String bucketName;
private String endpoint;
private String readPath;
}
mino服务类
@Slf4j
@EnableConfigurationProperties(MinIOConfigProperties.class)
@Component
@Import(MinIOConfig.class)
public class FileStorageService {
@Autowired
private MinioClient minioClient;
@Autowired
private MinIOConfigProperties minIOConfigProperties;
private final static String separator = "/";
/**
* @param dirPath
* @param filename yyyy/mm/dd/file.jpg
* @return
*/
public String builderFilePath(String dirPath, String filename) {
StringBuilder stringBuilder = new StringBuilder(50);
if (!StringUtils.isEmpty(dirPath)) {
stringBuilder.append(dirPath).append(separator);
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
String todayStr = sdf.format(new Date());
stringBuilder.append(todayStr).append(separator);
stringBuilder.append(filename);
return stringBuilder.toString();
}
/**
* 创建bucket
*/
public void makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* bucket 是否存在
*
* @param bucketName
* @return
*/
public boolean existBucket(String bucketName) {
boolean exist = false;
try {
exist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minIOConfigProperties.getBucketName()).build());
} catch (Exception e) {
e.printStackTrace();
}
return exist;
}
/**
* 上传图片文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadImgFile(String prefix, String filename, InputStream inputStream) {
String filePath = builderFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("image/jpg")
.bucket(minIOConfigProperties.getBucketName()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
urlPath.append(separator + minIOConfigProperties.getBucketName());
urlPath.append(separator);
urlPath.append(filePath);
return urlPath.toString();
} catch (Exception ex) {
log.error("minio put file error.", ex);
throw new RuntimeException("上传文件失败");
}
}
/**
* 上传html文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadHtmlFile(String prefix, String filename, InputStream inputStream) {
String filePath = builderFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("text/html")
.bucket(minIOConfigProperties.getBucketName()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
urlPath.append(separator + minIOConfigProperties.getBucketName());
urlPath.append(separator);
urlPath.append(filePath);
return urlPath.toString();
} catch (Exception ex) {
log.error("minio put file error.", ex);
ex.printStackTrace();
throw new RuntimeException("上传文件失败");
}
}
/**
* 删除文件
*
* @param pathUrl 文件全路径
*/
public void delete(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
int index = key.indexOf(separator);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
// 删除Objects
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
try {
minioClient.removeObject(removeObjectArgs);
} catch (Exception e) {
log.error("minio remove file error. pathUrl:{}", pathUrl);
e.printStackTrace();
}
}
/**
* 下载文件
*
* @param pathUrl 文件全路径
* @return 文件流
*/
public byte[] downLoadFile(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
int index = key.indexOf(separator);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
InputStream inputStream = null;
try {
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucketName()).object(filePath).build());
} catch (Exception e) {
log.error("minio down file error. pathUrl:{}", pathUrl);
e.printStackTrace();
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while (true) {
try {
if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
} catch (IOException e) {
e.printStackTrace();
}
byteArrayOutputStream.write(buff, 0, rc);
}
return byteArrayOutputStream.toByteArray();
}
}
controller调用(测试简化版代码)
@RestController
@RequestMapping("/test/minio")
public class MinioController {
@Autowired
private FileStorageService fileStorageService;
@PostMapping("/upload")
@ApiOperation("文件上传")
public String upload(@RequestPart MultipartFile file) {
String filePath;
try {
filePath = fileStorageService.uploadImgFile("test","test",file.getInputStream());
} catch (Exception e) {
e.printStackTrace();
return "上传失败";
}
return "成功";
}
}
yml配置:
minio:
# 换成自己的地址、key、bucketName
endpoint: http://192.168.207.130:9000
accessKey: yourAccessKey
secretKey: yourSecretKey
bucketName: test
可能出现的bug
一般正常安装及代码调用是不会出现bug的,但是博主在调用api时,发现会报错:
Non-XML response from server. Response code: 502, Content-Type: null, body:
但是在浏览器直接访问操作minio却可以成功
调试源码也不知道问题出在哪里,网上也没找到答案,最后博主给minio java sdk的作者提了issue ,作者答复是叫我检查proxy或者负载均衡配置等, 发现果然是代理问题。
(现在github的issue已经可以搜到博主的提问了)
解决方案:
1.关闭网络代理
2. 检查springboot的Application启动类是否设置的代理,同样需要关闭
// String proxyHost = "127.0.0.1";
// //
// String proxyPort = "7890";
// // 对http开启代理
// System.setProperty("http.proxyHost", proxyHost);
// System.setProperty("http.proxyPort", proxyPort);
// // 对https也开启代理
// System.setProperty("https.proxyHost", proxyHost);
// System.setProperty("https.proxyPort", proxyPort);