# oss
**Repository Path**: 2452860/oss
## Basic Information
- **Project Name**: oss
- **Description**: 开源的对象存储解决方案
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-09-16
- **Last Updated**: 2023-03-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 对象存储
## 前言
> 在程序开发中,难免会有些文件需要收集,存储,比如去银行开户,需要提供身份证复印件,在比如在微信发朋友圈,上传的图片视频信息,这种数据都需要我们存成电子版到系统中。
>
> 在单机时代,存储文件一般会在服务器上创建一个文件夹,在将文件保存到该文件夹下,程序中只需要将文件路径保存到数据库中的某个字段中即可,下次要查看上传的文件时,我们可以根据文件路径读取文件,达到查看显示文件的目的。但是此种方案随着用户的增多,我们会扩展多台服务器,此时用这种方式需要严格保障用户的访问每次必须到唯一的一台服务器上,不然就读取不到文件,这个时候就衍生出另一种方案,单独拿一台服务器当作文件服务器(FTP等),这时候解决了上述问题,但存在单点故障的风险,而且随着文件的增多,硬盘数据暴涨,单机扩容成本比较高。
>
> 在微服务时代,需要这样一种分布式的文件存储系统来帮我们存储文件数据,它基于分布式,可以通过 RESTful API 数据读写接口,可以快速实现扩容,这时候就涌现出了很多云厂商,他们都有自己相应的产品,比如AWS的S3,阿里的OSS,腾讯的COS,华为云的OBS,七牛云的Kodo,这种云产品都是对象存储应用。有没有开源的对象存储解决方案那,答案肯定是有,这些开源的解决方案可以快速帮助公司搭建出自己的对象存储服务。
## FastDFS
### 简介
> FastDFS是一款开源的分布式文件系统,功能主要包括:**文件存储**、**文件同步**、**文件访问**(文件上传、文件下载)等,解决了文件大容量存储和高性能访问的问题。FastDFS特别适合以文件为载体的在线服务,如图片、视频、文档等等
>
> FastDFS是由阿里的余庆(https://github.com/happyfish100/)大神采用C语言开发的一个工具,支持Linux、FreeBSD、MacOS等类UNIX系统。FastDFS类似google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有API访问,目前提供了C和Java SDK,以及PHP扩展SDK。
>
> **特点**
>
> 1. 分组存储,灵活简洁、对等结构,不存在单点
> 2. 文件不分块存储,上传的文件和OS文件系统中的文件一一对应
> 3. 文件ID由FastDFS生成,作为文件访问凭证
> 4. FastDFS已提供apache和nginx扩展模块
> 5. 中、小文件均可以很好支持,支持海量小文件存储
> 6. 支持多块磁盘,支持单盘数据恢复
> 7. 支持相同内容的文件只保存一份,节约磁盘空间
> 8. 支持在线扩容 支持主从文件
> 9. 下载文件支持多线程方式,支持断点续传
> 10. 存储服务器上可以保存文件属性(meta-data)V2.0网络通信采用libevent,支持大并发访问,整体性能更好
>
> **缺点**
>
> 1. 官方资料不全
> 2. 系统搭建复杂,需要熟悉liunx命令才能快速搭建出服务
> 3. 开发语言支持度不高,目前支持C,Java,PHP
### FastDFS的架构

FastDFS由客户端(Client)、 跟踪服务器(Tracker Server)和存储服务器(Storage Server)构成
**Client**
客户端(client),作为业务请求的发起方,通过专有接口,使用TCP/IP协议与跟踪器服务器或存储节
点进行数据交互
**Tracker Server**
Tracker Server作用是负载均衡和调度,通过Tracker server在文件上传时可以根据一些策略找到Storage Server提供文件上传服务。可以将tracker称为追踪服务器或调度服务器。跟踪器在访问上起负载均衡的作用。可以随时增加或下线而不会影响线上服务
**Storage Server**
Storage Server作用是文件存储,客户端上传的文件最终存储在Storage服务器上,Storage Server没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。存储节点中的服务器均可以随时增加或下线而不会影响线上服务
### FastDFS安装
| 名称 | 说明 |
| -------------------- | ----------------------------- |
| centos | 7.x |
| libfastcommon | FastDFS分离出的一些公用函数包 |
| FastDFS | FastDFS本体 |
| fastdfs-nginx-module | FastDFS和nginx的关联模块 |
| nginx | nginx1.15.4 |
**安装磁盘目录说明**
| 说明 | 位置 |
| ------------ | ------------- |
| 所有安装包 | /root/fastdfs |
| 数据存储位置 | /FastDFS |
```shell
mkdir /FastDFS #创建数据存储目录
cd /root/fastdfs #切换到安装目录准备下载安装包
```
**1. 安装编译环境**
```shell
yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y
```
**2. 安装libfastcommon 基础库**
```shell
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd libfastcommon/
./make.sh && ./make.sh install #编译安装
```
**3. 安装FastDFS**
```shell
cd ../ #返回上一级目录
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs/
./make.sh && ./make.sh install #编译安装
#配置文件准备
cp /usr/etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
cp /usr/etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
cp /usr/etc/fdfs/client.conf.sample /etc/fdfs/client.conf #客户端文件,测试用
cp /usr/local/src/fastdfs/conf/http.conf /etc/fdfs/ #供nginx访问使用
cp /usr/local/src/fastdfs/conf/mime.types /etc/fdfs/ #供nginx访问使用
```
**4. 安装 fastdfs-nginx-module**
```shell
cd ../ #返回上一级目录
git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1
cp /root/fastdfs/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs
```
**5. 安装nginx**
```shell
wget http://nginx.org/download/nginx-1.15.4.tar.gz #下载nginx压缩包
tar -zxvf nginx-1.15.4.tar.gz #解压
cd nginx-1.15.4/
#添加fastdfs-nginx-module模块
./configure --add-module=/usr/local/src/fastdfs-nginx-module/src/
make && make install #编译安装
```
### 单机部署
#### tracker配置
```shell
#服务器ip为 192.168.52.1
#我建议用ftp下载下来这些文件 本地修改
vim /etc/fdfs/tracker.conf
#需要修改的内容如下
port=22122 # tracker服务器端口(默认22122,一般不修改)
base_path=/FastDFS # 存储日志和数据的根目录
```
#### storage配置
```shell
vim /etc/fdfs/storage.conf
#需要修改的内容如下
port=23000 # storage服务端口(默认23000,一般不修改)
base_path=/FastDFS # 数据和日志文件存储根目录
store_path0=/FastDFS # 第一个存储目录
tracker_server=192.168.52.1:22122 # tracker服务器IP和端口
http.server_port=8888 # http访问文件的端口(默认8888,看情况修改,和nginx中保持一致)
```
#### client测试
```shell
vim /etc/fdfs/client.conf
#需要修改的内容如下
base_path=/FastDFS
tracker_server=192.168.52.1:22122 #tracker服务器IP和端口
#保存后测试,返回ID表示成功 如:group1/M00/00/00/xx.tar.gz
fdfs_upload_file /etc/fdfs/client.conf /root/fastdfs/nginx-1.15.4.tar.gz
```
#### 配置nginx访问
```shell
vim /etc/fdfs/mod_fastdfs.conf
#需要修改的内容如下
tracker_server=192.168.52.1:22122 #tracker服务器IP和端口
url_have_group_name=true
store_path0=/FastDFS
#配置nginx.config
vim /usr/local/nginx/conf/nginx.conf
#添加如下配置
server {
listen 8888; ## 该端口为storage.conf中的http.server_port相同
server_name localhost;
location ~/group[0-9]/ {
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
#测试下载,用外部浏览器访问刚才已传过的nginx安装包,引用返回的ID
http://192.168.52.1:8888/group1/M00/00/00/wKgAQ1pysxmAaqhAAA76tz-dVgg.tar.gz
#弹出下载单机部署全部跑通
```
### 分布式部署
#### tracker配置
```shell
#服务器ip为 192.168.52.2,192.168.52.3,192.168.52.4
#我建议用ftp下载下来这些文件 本地修改
vim /etc/fdfs/tracker.conf
store_lookup=0 # 0是轮询,1是指定组,2是剩余存储空间多的group优先
#需要修改的内容如下
port=22122 # tracker服务器端口(默认22122,一般不修改)
base_path=/FastDFS # 存储日志和数据的根目录
```
#### storage配置
```shell
vim /etc/fdfs/storage.conf
#需要修改的内容如下
port=23000 # storage服务端口(默认23000,一般不修改)
base_path=/FastDFS # 数据和日志文件存储根目录
store_path0=/FastDFS # 第一个存储目录
tracker_server=192.168.52.2:22122 # 服务器1
tracker_server=192.168.52.3:22122 # 服务器2
tracker_server=192.168.52.4:22122 # 服务器3
http.server_port=8888 # http访问文件的端口(默认8888,看情况修改,和nginx中保持一致)
```
#### client测试
```shell
vim /etc/fdfs/client.conf
#需要修改的内容如下
base_path=/home/moe/dfs
tracker_server=192.168.52.2:22122 # 服务器1
tracker_server=192.168.52.3:22122 # 服务器2
tracker_server=192.168.52.4:22122 # 服务器3
#保存后测试,返回ID表示成功 如:group1/M00/00/00/xx.tar.gz
fdfs_upload_file /etc/fdfs/client.conf /root/fastdfs/nginx-1.15.4.tar.gz
```
#### 配置nginx访问
```shell
vim /etc/fdfs/mod_fastdfs.conf
#需要修改的内容如下
tracker_server=192.168.52.2:22122 # 服务器1
tracker_server=192.168.52.3:22122 # 服务器2
tracker_server=192.168.52.4:22122 # 服务器3
url_have_group_name=true
store_path0=/home/dfs
#配置nginx.config
vim /usr/local/nginx/conf/nginx.conf
#添加如下配置
server {
listen 8888; ## 该端口为storage.conf中的http.server_port相同
server_name localhost;
location ~/group[0-9]/ {
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
```
### 启动
**关闭防火墙**
```shell
#不关闭防火墙的话无法使用
systemctl stop firewalld.service #关闭
systemctl restart firewalld.service #重启
```
**tracker**
```shell
/etc/init.d/fdfs_trackerd start #启动tracker服务
/etc/init.d/fdfs_trackerd restart #重启动tracker服务
/etc/init.d/fdfs_trackerd stop #停止tracker服务
chkconfig fdfs_trackerd on #自启动tracker服务
```
**storage**
```shell
/etc/init.d/fdfs_storaged start #启动storage服务
/etc/init.d/fdfs_storaged restart #重动storage服务
/etc/init.d/fdfs_storaged stop #停止动storage服务
chkconfig fdfs_storaged on #自启动storage服务
```
**nginx**
```shell
/usr/local/nginx/sbin/nginx #启动nginx
/usr/local/nginx/sbin/nginx -s reload #重启nginx
/usr/local/nginx/sbin/nginx -s stop #停止nginx
```
**检测集群**
```shell
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
# 会显示会有几台服务器 有3台就会 显示 Storage 1-Storage 3的详细信息
```
### 说明
#### 配置文件
```
tracker_server #有几台服务器写几个
group_name #地址的名称的命名
bind_addr #服务器ip绑定
store_path_count #store_path(数字)有几个写几个
store_path(数字) #设置几个储存地址写几个 从0开始
```
#### 配置优化
```
配置文件:tracker.conf 和 storage.conf 参数名:max_connections 缺省值:256
说明:FastDFS为一个连接分配一个task buffer,为了提升分配效率,FastDFS采用内存池的做法。 FastDFS老版本直接事先分配max_connections个buffer,这个做法显然不是太合理,在 max_connections 设置过大的情况下太浪费内存。v5.04对预分配采用增量方式,tracker一次预分配 1024个,storage一次预分配256个。 #define ALLOC_CONNECTIONS_ONCE 1024 总的task buffer初始内存占用情况测算如下 改进前:max_connections * buffer_size 改进后:max_connections和预分配的连接中那个小 * buffer_size 使用v5.04及后续版本,可以根据实际需要将 max_connections 设置为一个较大的数值,比如 10240 甚至更大。 注意此时需要将一个进程允许打开的最大文件数调大到超过max_connections否则FastDFS server启动会报错。 vi /etc/security/limits.conf 重启系统生效 * soft nofile 65535 * hard nofile 65535 另外,对于32位系统,请注意使用到的内存不要超过3GB
```
**工作线程数设置**
```
配置文件:tracker.conf 和 storage.conf
参数名: work_threads
缺省值:4
说明:为了避免CPU上下文切换的开销,以及不必要的资源消耗,不建议将本参数设置得过大。为了发挥出多个CPU的效能,系统中的线程数总和,应等于CPU总数。 对于tracker server,公式为: work_threads + 1 = CPU数 对于storage,公式为: work_threads + 1 + (disk_reader_threads + disk_writer_threads) * store_path_count = CPU数
```
**storage目录数设置**
```
配置文件: storage.conf
参数名:subdir_count_per_path
缺省值:256
说明:FastDFS采用二级目录的做法,目录会在FastDFS初始化时自动创建。存储海量小文件,打开了 trunk存储方式的情况下,建议将本参数适当改小,比如设置为32,此时存放文件的目录数为 32 * 32 = 1024。假如trunk文件大小采用缺省值64MB,磁盘空间为2TB,那么每个目录下存放的trunk文件数均值 为:2TB/(1024 * 64MB) = 32个
```
**storage磁盘读写线程设置**
```
配置文件: storage.conf
参数名:disk_rw_separated:磁盘读写是否分离
参数名:disk_reader_threads:单个磁盘读线程数
参数名:disk_writer_threads:单个磁盘写线程数
如果磁盘读写混合,单个磁盘读写线程数为读线程数和写线程数之和,对于单盘挂载方式,磁盘读写线程分别设置为1即可
如果磁盘做了RAID,那么需要酌情加大读写线程数,这样才能最大程度地发挥磁盘性能
```
**storage同步延迟相关设置**
```
配置文件: storage.conf
参数名:sync_binlog_buff_interval:将binlog buffer写入磁盘的时间间隔,取值大于0,缺省值为60s
参数名:sync_wait_msec:如果没有需要同步的文件,对binlog进行轮询的时间间隔,取值大于0,缺省值为200ms
参数名: sync_interval:同步完一个文件后,休眠的毫秒数,缺省值为0
为了缩短文件同步时间,可以将上述3个参数适当调小即可
```
### spring boot 集成FastDFS客户端
**1. 引入jar包依赖**
```xml
com.github.tobato
fastdfs-client
1.26.1-RELEASE
```
**2. 配置文件 application.yml**
```yaml
fdfs:
connectTimeout: 600
trackerList:
- 192.168.211.130:22122
- 192.168.211.135:22122
- 192.168.211.136:22122
```
**3. 配置类**
```java
import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;
@Configuration
@Import(FdfsClientConfig.class)
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastDFSClientConfig {
}
```
**4. 业务代码示例**
```java
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
@Service
public class FastDFSClientService {
private Logger logger= LoggerFactory.getLogger(FastDFSClientService.class);
@Autowired
private FastFileStorageClient fastFileStorageClient;
/**
* 文件上传
* @param file
* @return
* @throws Exception
*/
public String uploadFile(MultipartFile file) throws Exception{
byte[] bytes = file.getBytes();
// 获取的是文件的完整名称,包括文件名称+文件拓展名
String originalFileName = file.getOriginalFilename();
logger.debug("【上传的文件名:{}】",originalFileName);
String fileType = FilenameUtils.getExtension(originalFileName);
logger.debug("【上传的文件类型{}】",fileType);
long fileSize = file.getSize();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
StorePath storePath = fastFileStorageClient.uploadFile(byteArrayInputStream, fileSize, fileType, null);
return storePath.getFullPath();
}
/**
* 文件下载
* @param fileUrl
* @return
*/
public byte[] downloadFile(String fileUrl){
// 获取分组
String group = fileUrl.substring(0, fileUrl.indexOf("/"));
logger.debug("【分组:{}】",group);
// 获取path路径
String path = fileUrl.substring(fileUrl.indexOf("/") + 1);
logger.debug("【存储路径:{}】",path);
DownloadByteArray downloadByteArray = new DownloadByteArray();
return fastFileStorageClient.downloadFile(group, path, downloadByteArray);
}
}
```
## minio
### 简介
> MinIO 是一款非常热门的开源对象存储服务器,能够完美兼容亚马逊的 S3 协议, 对 K8s 也能够进行非常友好的支持,专为 AI 等云原生工作负载而设计。包括构建高性能的云原生数据机器学习、大数据分析、海量存储的基础架构等方面,MinIO 都可以提供数据工作负载。有数据统计,在中国大陆,很多企业,比如阿里巴巴、腾讯、百度、中国联通、华为、中国移动等9000多家企业也都在使用 MinIO 产品。
>
> **官方中文文档** http://docs.minio.org.cn/docs/
>
> **特点**
>
> 1. 高性能
>
> 在标准硬件上,读/写速度上高达183 GB / 秒 和 171 GB / 秒
>
> 2. 可扩展性
>
> MinIO利用了Web缩放器的来之不易的知识,为对象存储带来了简单的缩放模型。 这是我们坚定的理念 “简单可扩展.” 在 MinIO, 扩展从单个群集开始,该群集可以与其他MinIO群集联合以创建全局名称空间, 并在需要时可以跨越多个不同的数据中心。 通过添加更多集群可以扩展名称空间, 更多机架,直到实现目标。
>
> 3. 云的原生支持
>
> MinIO 是在过去4年的时间内从0开始打造的一款软件 ,符合一切原生云计算的架构和构建过程,并且包含最新的云计算的全新的技术和概念。 其中包括支持Kubernetes 、微服和多租户的的容器技术。使对象存储对于 Kubernetes更加友好。
>
> 4. 纯开源
>
> MinIO 基于Apache V2 license 100% 开放源代码 。 这就意味着 MinIO的客户能够自动的、无限制、自由免费使用和集成MinIO、自由的创新和创造、 自由的去修改、自由的再次发行新的版本和软件. 确实, MinIO 强有力的支持和驱动了很多世界500强的企业。 此外,其部署的多样性和专业性提供了其他软件无法比拟的优势。
>
> 5. 与Amazon S3 兼容
>
> 亚马逊云的 S3 API(接口协议) 是在全球范围内达到共识的对象存储的协议,是全世界内大家都认可的标准。 MinIO 在很早的时候就采用了 S3 兼容协议,并且MinIO 是第一个支持 S3 Select 的产品
>
> 6. 简单
>
> 极简主义是MinIO的指导性设计原则。简单性减少了出错的机会,提高了正常运行时间,提供了可靠性,同时简单性又是性能的基础。 只需下载一个二进制文件然后执行,即可在几分钟内安装和配置MinIO。 配置选项和变体的数量保持在最低限度,这样让失败的配置概率降低到接近于0的水平。 MinIO升级是通过一个简单命令完成的,这个命令可以无中断的完成MinIO的升级,并且不需要停机即可完成升级操作 - 降低总使用和运维成本
### 安装
#### 单机部署
> MinIO 部署开始使用默认的 root 凭据 `minioadmin:minioadmin`。您可以使用 MinIO 控制台测试部署,这是一个内置在 MinIO 服务器中的基于 Web 的嵌入式对象浏览器。将主机上运行的 Web 浏览器指向 [http://127.0.0.1:9000](http://127.0.0.1:9000/) 并使用 root 凭据登录。您可以使用浏览器来创建桶、上传对象以及浏览 MinIO 服务器的内容
**windows**
下载地址:https://dl.min.io/server/minio/release/windows-amd64/minio.exe
服务启动
```shell
minio.exe server D:\oss
# D:\oss 存储位置
```

**liunx(centos 64位)**
```shell
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server /data
# 将/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径
# 防火墙设置
# 对于启用了 firewall-cmd (CentOS) 的主机
firewall-cmd --get-active-zones
firewall-cmd --zone=public --add-port=9000/tcp --permanent
firewall-cmd --reload
# 对于启用了 iptables 的主机
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
service iptables restart
```
**macOS**
使用 [Homebrew](https://brew.sh/)安装最新的稳定 MinIO 包
```shell
brew install minio/stable/minio
minio server /data
```
二进制文件安装
```shell
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server /data
```
**docker**
```shell
# liunx
docker run -p 9000:9000 --name minio1 \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server /data
# windows
docker run -p 9000:9000 --name minio1 \
-v D:\data:/data \
-v D:\minio\config:/root/.minio \
minio/minio server /data
```
#### 分布式部署
> 分布式搭建的流程和单机搭建的流程基本一样,Minio服务基于命令行传入的参数自动切换成单机模式还是分布式模式,启动一个分布式Minio实例,你只需要把硬盘位置做为参数传给minio server命令即可,然后,你需要在所有其它节点运行同样的命令
>
> 注意
>
> - 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,这样这些节点才能建立联接。为了实现这个,你需要在执行minio server命令之前,先将access秘钥和secret秘钥export成环境变量。
> - 分布式Minio使用的磁盘里必须是干净的,里面没有数据。
> - 下面示例里的IP仅供示例参考,你需要改成你真实用到的IP和文件夹路径。
> - 分布式Minio里的节点时间差不能超过3秒,你可以使用[NTP](http://www.ntp.org/) 来保证时间一致。
> - 在Windows下运行分布式Minio处于实验阶段,请悠着点使用。
示例1: 启动分布式Minio实例,8个节点,每节点1块盘,需要在8个节点上都运行下面的命令。
**liunx 和macOS**
```shell
export MINIO_ACCESS_KEY=
export MINIO_SECRET_KEY=
minio server http://192.168.1.11/export1 http://192.168.1.12/export2 \
http://192.168.1.13/export3 http://192.168.1.14/export4 \
http://192.168.1.15/export5 http://192.168.1.16/export6 \
http://192.168.1.17/export7 http://192.168.1.18/export8
```
**windows**
```powershell
set MINIO_ACCESS_KEY=
set MINIO_SECRET_KEY=
minio.exe server http://192.168.1.11/C:/data http://192.168.1.12/C:/data ^
http://192.168.1.13/C:/data http://192.168.1.14/C:/data ^
http://192.168.1.15/C:/data http://192.168.1.16/C:/data ^
http://192.168.1.17/C:/data http://192.168.1.18/C:/data
```
### Java Client SDK
#### 1. 引入jar包依赖
```xml
io.minio
minio
7.0.2
```
#### 2. 编写业务代码
```java
import io.minio.MinioClient;
public class FileUploader {
public static void main(String[] args) {
try {
MinioClient minioClient = new MinioClient("http://127.0.0.1:9000", "minioadmin", "minioadmin");
// 检查存储桶是否已经存在
boolean isExist = minioClient.bucketExists("asiatrip");
if(isExist) {
System.out.println("Bucket already exists.");
} else {
// 创建一个名为asiatrip的存储桶,用于存储照片的zip文件。
minioClient.makeBucket("asiatrip");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
#### 3. API
**官网API网址**
https://minio-java.min.io/io/minio/MinioClient.html
```java
MinioClient minioClient = new MinioClient("Endpoint", "ACCESSKEYID", "SECRETACCESSKEY");
```
**创建一个新的存储桶**
```java
public void makeBucket(String bucketName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :--------- |
| `bucketName` | *String* | 存储桶名称 |
| 返回值类型 | 异常 |
| :--------- | :---------------------------------------------------------- |
| `None` | 异常列表: |
| | `InvalidBucketNameException` : 非法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常 |
| | `ErrorResponseException` : 执行失败 |
| | `InternalException` : 内部异常 |
**列出所有存储桶**
```java
public List listBuckets()
```
| 返回值类型 | 异常 |
| :----------------------------------- | :---------------------------------------------------------- |
| `List Bucket` : List of bucket type. | 异常列表: |
| | `NoResponseException` : 服务端无响应 |
| | `IOException` : 连接异常 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常 |
| | `ErrorResponseException` :执行失败异溃 |
| | `InternalException` : 内部错误 |
```java
try {
// 列出所有存储桶
List bucketList = minioClient.listBuckets();
for (Bucket bucket : bucketList) {
System.out.println(bucket.creationDate() + ", " + bucket.name());
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**检查存储桶是否存在**
```java
public boolean bucketExists(String bucketName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :--------- |
| `bucketName` | *String* | 存储桶名称 |
| 返回值值类型 | 异常 |
| :----------------------------------- | :----------------------------------------------------------- |
| `boolean`: true if the bucket exists | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
**删除一个存储桶**
```java
public void removeBucket(String bucketName)
// removeBucket不会删除存储桶里的对象,你需要通过removeObject API来删除它们
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :----------- |
| `bucketName` | *String* | 存储桶名称。 |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 删除之前先检查`my-bucket`是否存在。
boolean found = minioClient.bucketExists("mybucket");
if (found) {
// 删除`my-bucketname`存储桶,注意,只有存储桶为空时才能删除成功。
minioClient.removeBucket("mybucket");
System.out.println("mybucket is removed successfully");
} else {
System.out.println("mybucket does not exist");
}
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**列出某个存储桶中的所有对象**
```java
public Iterable> listObjects(String bucketName, String prefix, boolean recursive, boolean useVersion1)
```
**参数**
| 参数 | 类型 | 描述 |
| :------------ | :-------- | :----------------------------------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `prefix` | *String* | 对象名称的前缀 |
| `recursive` | *boolean* | 是否递归查找,如果是false,就模拟文件夹结构查找。 |
| `useVersion1` | *boolean* | 如果是true, 使用版本1 REST API |
| 返回值类型 | 异常 |
| :---------------------------------------------------- | :----- |
| `Iterable>`:an iterator of Result Items. | *None* |
```javascript
try {
// 检查'mybucket'是否存在。
boolean found = minioClient.bucketExists("mybucket");
if (found) {
// 列出'my-bucketname'里的对象
Iterable> myObjects = minioClient.listObjects("mybucket");
for (Result- result : myObjects) {
Item item = result.get();
System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
}
} else {
System.out.println("mybucket does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**列出存储桶中被部分上传的对象**
```java
public Iterable> listIncompleteUploads(String bucketName, String prefix, boolean recursive)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :-------- | :----------------------------------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `prefix` | *String* | 对象名称的前缀,列出有该前缀的对象 |
| `recursive` | *boolean* | 是否递归查找,如果是false,就模拟文件夹结构查找。 |
| 返回值类型 | 异常 |
| :------------------------------------------------- | :----- |
| `Iterable>`: an iterator of Upload. | *None* |
```java
try {
// 检查'mybucket'是否存在。
boolean found = minioClient.bucketExists("mybucket");
if (found) {
// 列出'mybucket'中所有未完成的multipart上传的的对象。
Iterable> myObjects = minioClient.listIncompleteUploads("mybucket");
for (Result result : myObjects) {
Upload upload = result.get();
System.out.println(upload.uploadId() + ", " + upload.objectName());
}
} else {
System.out.println("mybucket does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**以流的形式下载一个对象**
```java
public InputStream getObject(String bucketName, String objectName, long offset)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| 返回值类型 | 异常 |
| :----------------------------------------------------- | :----------------------------------------------------------- |
| `InputStream`: InputStream containing the object data. | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 调用statObject()来判断对象是否存在。
// 如果不存在, statObject()抛出异常,
// 否则则代表对象存在。
minioClient.statObject("mybucket", "myobject");
// 获取"myobject"的输入流。
InputStream stream = minioClient.getObject("mybucket", "myobject");
// 读取输入流直到EOF并打印到控制台。
byte[] buf = new byte[16384];
int bytesRead;
while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) {
System.out.println(new String(buf, 0, bytesRead));
}
// 关闭流,此处为示例,流关闭最好放在finally块。
stream.close();
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**下载对象指定区域的字节数组做为流。(断点下载)**
```java
public InputStream getObject(String bucketName, String objectName, long offset, Long length)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------------------------------------------------------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| `offset` | *Long* | `offset` 是起始字节的位置 | | |
| `length` | *Long* | `length`是要读取的长度 (可选,如果无值则代表读到文件结尾)。 | | |
| 返回值类型 | 异常 |
| :-------------------------------------------------------- | :----------------------------------------------------------- |
| `InputStream` : InputStream containing the object's data. | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 调用statObject()来判断对象是否存在。
// 如果不存在, statObject()抛出异常,
// 否则则代表对象存在。
minioClient.statObject("mybucket", "myobject");
// 获取指定offset和length的"myobject"的输入流。
InputStream stream = minioClient.getObject("mybucket", "myobject", 1024L, 4096L);
// 读取输入流直到EOF并打印到控制台。
byte[] buf = new byte[16384];
int bytesRead;
while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) {
System.out.println(new String(buf, 0, bytesRead));
}
// 关闭流。
stream.close();
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**下载并将文件保存到本地**
```java
public void getObject(String bucketName, String objectName, String fileName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| `fileName` | *String* | File name. |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 调用statObject()来判断对象是否存在。
// 如果不存在, statObject()抛出异常,
// 否则则代表对象存在。
minioClient.statObject("mybucket", "myobject");
// 获取myobject的流并保存到photo.jpg文件中。
minioClient.getObject("mybucket", "myobject", "photo.jpg");
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**通过InputStream上传对象**
```java
public void putObject(String bucketName, String objectName, InputStream stream, long size, String contentType)
```
**参数**
| 参数 | 类型 | 描述 |
| :--------------------------------------- | :------------ | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| `stream` | *InputStream* | 要上传的流。 |
| `size` | *long* | 要上传的`stream`的size | | |
| `contentType` | *String* | Content type。 |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
单个对象的最大大小限制在5TB。putObject在对象大于5MiB时,自动使用multiple parts方式上传。这样,当上传失败时,客户端只需要上传未成功的部分即可(类似断点上传)。上传的对象使用MD5SUM签名进行完整性验证。
```java
try {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append("Sphinx of black quartz, judge my vow: Used by Adobe InDesign to display font samples. ");
builder.append("(29 letters)\n");
builder.append("Jackdaws love my big sphinx of quartz: Similarly, used by Windows XP for some fonts. ");
builder.append("(31 letters)\n");
builder.append("Pack my box with five dozen liquor jugs: According to Wikipedia, this one is used on ");
builder.append("NASAs Space Shuttle. (32 letters)\n");
builder.append("The quick onyx goblin jumps over the lazy dwarf: Flavor text from an Unhinged Magic Card. ");
builder.append("(39 letters)\n");
builder.append("How razorback-jumping frogs can level six piqued gymnasts!: Not going to win any brevity ");
builder.append("awards at 49 letters long, but old-time Mac users may recognize it.\n");
builder.append("Cozy lummox gives smart squid who asks for job pen: A 41-letter tester sentence for Mac ");
builder.append("computers after System 7.\n");
builder.append("A few others we like: Amazingly few discotheques provide jukeboxes; Now fax quiz Jack! my ");
builder.append("brave ghost pled; Watch Jeopardy!, Alex Trebeks fun TV quiz game.\n");
builder.append("- --\n");
}
ByteArrayInputStream bais = new
ByteArrayInputStream(builder.toString().getBytes("UTF-8"));
// 创建对象
minioClient.putObject("mybucket", "myobject", bais, bais.available(), "application/octet-stream");
bais.close();
System.out.println("myobject is uploaded successfully");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**通过文件上传到对象中**
```java
public void putObject(String bucketName, String objectName, String fileName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| `fileName` | *String* | File name. |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
minioClient.putObject("mybucket", "island.jpg", "/mnt/photos/island.jpg")
System.out.println("island.jpg is uploaded successfully");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
}
```
**删除一个对象**
```java
public void removeObject(String bucketName, String objectName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 从mybucket中删除myobject。
minioClient.removeObject("mybucket", "myobject");
System.out.println("successfully removed mybucket/myobject");
} catch (MinioException e) {
System.out.println("Error: " + e);
}
```
**删除多个对象**
```java
public Iterable> removeObject(String bucketName, Iterable objectNames)
```
**参数**
| 参数 | 类型 | 描述 |
| :------------ | :--------- | :--------------------------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectNames` | *Iterable* | 含有要删除的多个object名称的迭代器对象。 |
| 返回值类型 | 异常 |
| :----------------------------------------------------------- | :----- |
| `Iterable>`:an iterator of Result DeleteError. | *None* |
```java
List objectNames = new LinkedList();
objectNames.add("my-objectname1");
objectNames.add("my-objectname2");
objectNames.add("my-objectname3");
try {
// 删除my-bucketname里的多个对象
for (Result errorResult: minioClient.removeObject("my-bucketname", objectNames)) {
DeleteError error = errorResult.get();
System.out.println("Failed to remove '" + error.objectName() + "'. Error:" + error.message());
}
} catch (MinioException e) {
System.out.println("Error: " + e);
}
```
**删除一个未完整上传的对象**
```java
public void removeIncompleteUpload(String bucketName, String objectName)
```
**参数**
| 参数 | 类型 | 描述 |
| :----------- | :------- | :------------------- |
| `bucketName` | *String* | 存储桶名称。 |
| `objectName` | *String* | 存储桶里的对象名称。 |
| 返回值类型 | 异常 |
| :--------- | :----------------------------------------------------------- |
| None | 异常列表: |
| | `InvalidBucketNameException` : 不合法的存储桶名称。 |
| | `NoResponseException` : 服务器无响应。 |
| | `IOException` : 连接异常。 |
| | `org.xmlpull.v1.XmlPullParserException` : 解析返回的XML异常。 |
| | `ErrorResponseException` : 执行失败异常。 |
| | `InternalException` : 内部错误。 |
```java
try {
// 从存储桶中删除名为myobject的未完整上传的对象。
minioClient.removeIncompleteUpload("mybucket", "myobject");
System.out.println("successfully removed all incomplete upload session of my-bucketname/my-objectname");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
}
```