什么是对象存储?
对象存储是一种使企业能够管理离散单元或对象中存储的方法。使用这种存储类型时,数据对象将保留在没有嵌套或分层文件结构的单个存储库中。
存储中的每个对象都包含构成文件的所有数据块以及任何关联的元数据。然后,将这些数据放入一个存储池(一个平面地址空间)中。当企业需要检索数据时,可以通过分配给该对象的唯一标识符来查找数据。
对象存储之所以有用,部分原因在于附加到文件的元数据。该元数据可以自动生成,也可以由企业定义,从而可以进行多种分析。另一个好处是能够灵活存储数据而无需考虑层次结构。这赋予了使用存储资源最大容量的更大能力,并且更易于扩展。
尽管有这些好处,对象存储确实具有比文件或块存储系统慢的缺点。对于要求低延迟的应用程序或工作负载,这是一个糟糕的选择。
什么是文件存储?
文件存储是一种在分层系统中存储数据的方法。文件存储是大多数用户熟悉的标准存储方法。使用文件存储,企业的数据以与检索时相同的格式存储。企业可以通过Windows中的服务器消息块(SMB)协议或Unix或Linux中的网络文件系统(NFS)协议访问文件存储。
服务器消息块(SMB)和网络文件系统(NFS)是使企业能够以与将数据存储在客户端计算机上相同的方式将文件存储在服务器上的协议。企业可以挂载全部或部分文件系统,并在多个客户端设备之间共享访问权限。这些协议也通常与网络附加存储(NAS)设备一起使用。
网络附加存储(NAS)设备通常用于扩展文件存储,也能够以网络附加存储(NAS)备份的形式使用,用于为文件存储提供冗余。这些设备使扩展文件存储成为可能,否则文件存储将限于单个硬盘或物理连接的存储设备。
什么是块存储?
块存储是一种抽象底层存储设备上的存储的方法。块存储设备作为称为块的单元集群进行管理。在每个块中,企业存储单个文件的一部分。然后,为该块分配一个唯一的地址,使文件可以分散在多台计算机上存储,从而更有效地使用存储。
当要检索文件时,将向企业存储文件的块设备发出请求。将请求转换为阻止请求后,重新组合的文件将返回到企业的计算机,就像该设备是标准硬盘一样。
块存储的好处是,它可以在功能类似于即插即用存储磁盘的卷上启用低延迟操作。将块存储附加到服务时,可以对其进行格式化以接受所需的任何文件系统,其中包括NTFS、XFS或ext4。块存储通常也跨设备复制,以确保在一个设备损坏时数据是可恢复的。
文件vs.块存储
使用块存储时,将根据发送到存储服务器的归档请求在特定的块中进行操作。这要求查找块的存储位置,检索那些块,并返回文件的各个字节。
使用文件存储时,请求通过用户级数据表示接口发送。这些界面要求用户指定文件信息,包括文件名、URL和目录位置。这需要用户提供更多信息,但无需存储系统搜索或转换文件。
与块存储相比,文件存储的主要好处是用户对文件系统很熟悉。相比之下,块存储比文件存储提供了更大的灵活性。还可以对其进行修改以提高性能,而文件存储的自定义性则较差。
一般来说,文件存储更适合于创建供办公室使用的内容存储库或目录、存储较小数量的结构化数据或存储具有强烈数据保护要求的文件。同时,块存储更适合于数据库、关键应用程序的数据存储和虚拟化系统的存储。
对象存储vs.块存储
对象存储和块存储之间的主要区别在于,对象存储包含元数据,而块存储则不包含元数据。这使对象存储可以包含有关文件的场景,而块存储则是无场景的。另外,在对象存储中,每个对象都有唯一的标识符,从而无需像块存储中那样按位置搜索数据。
虽然块存储和对象存储都可以扩展,但是对象存储却越来越容易扩展。要扩展对象存储,只需要将更多节点添加到存储集群。与其相反,根据使用的块服务,可能无法在达到分配的存储空间后进行扩展。
虽然对象存储提供了更大的灵活性,但存储的可定制性比块存储慢。这至少部分是因为块存储使企业能够修改文件的增量部分。同时,对象存储要求将对象修改为单个单元。这意味着企业进行的任何小更改都需要重写整个对象。这比使用块方法要慢得多,因为它需要访问和更新更多的数据。
在通常情况下,块存储对于依赖存储性能、事务性数据存储和不需要元数据分析的数据的应用程序和工作流来说是更好的选择。同时,对象存储更适合于存储非结构化数据、存储大型数据集以及使用自定义数据保留、删除和保留策略存储数据。
对象存储 vs. 文件存储 vs. 块存储
对象存储将获取的每个数据片段指定为对象。数据保存在单独的存储库中,而不是以文件的形式保存在文件夹中,数据与关联的元数据和唯一标识符捆绑在一起,以形成存储池。
文件存储将数据作为一条信息存储在文件夹中,以便于区分其他数据进行组织。这也称为分层存储,模仿了纸质文件的存储方式。当您需要访问数据时,您的计算机系统需要知道数据的路径。
块存储将文件拆分为单个数据块,然后将这些块作为单独的数据片段来存储。每条数据都有不同的地址,因此无需将它们存储在文件结构中。
对象存储的优势
- 数据分析能力更为出色。对象存储由元数据驱动,对每个数据片段进行这种级别的分类后,分析机会就大得多。
- 可扩展性极高。可以永久持续地添加数据。没有限制。
- 数据检索速度更快。由于对象存储的分类结构以及没有文件夹层次结构,因此,您可以更快地检索数据。
- 成本降低。由于对象存储的横向扩展特性,因此能够以较低的成本存储所有数据。
- 资源优化。由于对象存储没有归档层次结构,而且元数据可以完全自定义,因此限制比文件或块存储的要少得多。
使用数据的“用户”不同:
块存储的用户是可以读写块设备的软件系统,例如传统的文件系统、数据库;
文件存储的用户是自然人或者某个群体组织;
对象存储的用户则是其它计算机软件。
安装
wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio ./minio server /data #将/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径
当部署在单个驱动器上时,MinIO 服务器允许客户端访问数据目录中的任何预先存在的数据。例如,如果 MinIO 是用命令 minio server /data 启动的,/data 目录中的任何预先存在的数据都可以被客户端访问。
默认情况下,MinIO 使用端口 9000 来侦听传入连接。如果您的平台默认阻止该端口,您可能需要启用对该端口的访问。
#此命令获取活动区域。例如,如果区域是public,请使用 firewall-cmd --get-active-zones #“permanent”确保规则在防火墙启动、重启或重新加载时是持久的。 最后重新加载防火墙以使更改生效。 firewall-cmd --zone=public --add-port=9000/tcp --permanent firewall-cmd --reload
中文文档:http://docs.minio.org.cn/docs/
测试
MinIO Server 带有一个基于 Web 的嵌入式对象浏览器。将您的 Web 浏览器指向 http://127.0.0.1:9000 以确保您的服务器已成功启动
说明:Segmentation fault报错
大概率是文件下载不全!!
分布式
export MINIO_ROOT_USER=minioadmin export MINIO_ROOT_PASSWORD=minioadmin minio server --address 192.168.9.203:9000 http://192.168.9.203/data1 http://192.168.9.203/data2 http://192.168.9.203/data3 http://192.168.9.203/data4
如果要使用纠删码的功能,需要有多个硬盘挂载到操作系统上,没有进行硬盘划分,直接在用一个硬盘上新建多个文件夹作为纠删码的存储目录,会报错
解决:
[root@skx3 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 300G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 299G 0 part ├─centos-root 253:0 0 50G 0 lvm / ├─centos-swap 253:1 0 2G 0 lvm [SWAP] └─centos-home 253:2 0 247G 0 lvm /home sr0 11:0 1 1024M 0 rom [root@skx3 ~]# for host in $(ls /sys/class/scsi_host) ; do echo "- - -" > /sys/class/scsi_host/$host/scan; done [root@skx3 ~]# ls /sys/class/scsi_host host0 host1 host2 [root@skx3 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 300G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 299G 0 part ├─centos-root 253:0 0 50G 0 lvm / ├─centos-swap 253:1 0 2G 0 lvm [SWAP] └─centos-home 253:2 0 247G 0 lvm /home sdb 8:16 0 1G 0 disk sdc 8:32 0 1G 0 disk sdd 8:48 0 1G 0 disk sde 8:64 0 1G 0 disk sr0 11:0 1 1024M 0 rom [root@skx3 ~]# mkfs.ext4 /dev/sdb mke2fs 1.42.9 (28-Dec-2013) /dev/sdb is entire device, not just one partition! Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done [root@skx3 ~]# mkfs.ext4 /dev/sdc mke2fs 1.42.9 (28-Dec-2013) /dev/sdc is entire device, not just one partition! Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done [root@skx3 ~]# mkfs.ext4 /dev/sdd mke2fs 1.42.9 (28-Dec-2013) /dev/sdd is entire device, not just one partition! Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done [root@skx3 ~]# mkfs.ext4 /dev/sde mke2fs 1.42.9 (28-Dec-2013) /dev/sde is entire device, not just one partition! Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done [root@skx3 ~]# mount /dev/sdb /data1/ [root@skx3 ~]# mount /dev/sdc /data2/ [root@skx3 ~]# mount /dev/sdd /data3/ [root@skx3 ~]# mount /dev/sde /data4/ [root@skx3 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 300G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 299G 0 part ├─centos-root 253:0 0 50G 0 lvm / ├─centos-swap 253:1 0 2G 0 lvm [SWAP] └─centos-home 253:2 0 247G 0 lvm /home sdb 8:16 0 1G 0 disk /data1 sdc 8:32 0 1G 0 disk /data2 sdd 8:48 0 1G 0 disk /data3 sde 8:64 0 1G 0 disk /data4 sr0 11:0 1 1024M 0 rom
参看
springboot+MinIO
依赖
io.minio minio 8.2.2
application.yml
minio: endpoint: http://127.0.0.1:9000 #Minio服务所在地址 bucketName: tulaoda #存储桶名称 accessKey: Jonny #访问的key secretKey: minioadmin #访问的秘钥
MinioConfig.class配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
minio工具类
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioConfig prop;
@Resource
private MinioClient minioClient;
@Autowired
private CodeService codeService;
public Boolean bucketExists(String bucketName) {
Boolean found;
try {
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return found;
}
public Boolean makeBucket(String bucketName) {
try {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
String policyJson = "{n" +
"t"Version": ""+new SimpleDateFormat("yyyy-mm-dd").format(System.currentTimeMillis())+"",n" +
"t"Statement": [{n" +
"tt"Effect": "Allow",n" +
"tt"Principal": {n" +
"ttt"AWS": ["*"]n" +
"tt},n" +
"tt"Action": ["s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads"],n" +
"tt"Resource": ["arn:aws:s3:::" + bucketName + ""]n" +
"t}, {n" +
"tt"Effect": "Allow",n" +
"tt"Principal": {n" +
"ttt"AWS": ["*"]n" +
"tt},n" +
"tt"Action": ["s3:AbortMultipartUpload", "s3:DeleteObject", "s3:GetObject", "s3:ListMultipartUploadParts", "s3:PutObject"],n" +
"tt"Resource": ["arn:aws:s3:::" + bucketName + "
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public List getAllBuckets() {
try {
List buckets = minioClient.listBuckets();
return buckets;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename)){
throw new RuntimeException();
}
String fileName = UuidUtils.generateUuid() + originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = CommUtils.getNowDateLongStr("yyyy-MM/dd") + "/" + fileName;
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
.stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectName;
}
public String preview(String fileName){
// 查看文件地址
GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();
try {
String url = minioClient.getPresignedObjectUrl(build);
return url;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void download(String fileName, HttpServletResponse res) {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
.object(fileName).build();
try (GetObjectResponse response = minioClient.getObject(objectArgs)){
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
while ((len=response.read(buf))!=-1){
os.write(buf,0,len);
}
os.flush();
byte[] bytes = os.toByteArray();
res.setCharacterEncoding("utf-8");
// 设置强制下载不打开
// res.setContentType("application/force-download");
res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
try (ServletOutputStream stream = res.getOutputStream()){
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public List- listObjects() {
Iterable
> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
List- items = new ArrayList<>();
try {
for (Result
- result : results) {
items.add(result.get());
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return items;
}
public boolean remove(String fileName){
try {
minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
}catch (Exception e){
return false;
}
return true;
}
}
接口
@Api(tags = "文件相关接口")
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController {
@Autowired
private MinioUtil minioUtil;
@Autowired
private MinioConfig prop;
@ApiOperation(value = "查看存储bucket是否存在")
@GetMapping("/bucketExists")
public R bucketExists(@RequestParam("bucketName") String bucketName) {
return R.ok().put("bucketName",minioUtil.bucketExists(bucketName));
}
@ApiOperation(value = "创建存储bucket")
@GetMapping("/makeBucket")
public R makeBucket(String bucketName) {
return R.ok().put("bucketName",minioUtil.makeBucket(bucketName));
}
@ApiOperation(value = "删除存储bucket")
@GetMapping("/removeBucket")
public R removeBucket(String bucketName) {
return R.ok().put("bucketName",minioUtil.removeBucket(bucketName));
}
@ApiOperation(value = "获取全部bucket")
@GetMapping("/getAllBuckets")
public R getAllBuckets() {
List allBuckets = minioUtil.getAllBuckets();
return R.ok().put("allBuckets",allBuckets);
}
@ApiOperation(value = "文件上传返回url")
@PostMapping("/upload")
public R upload(@RequestParam("file") MultipartFile file) {
String objectName = minioUtil.upload(file);
if (null != objectName) {
return R.ok().put("url",(prop.getEndpoint() + "/" + prop.getBucketName() + "/" + objectName));
}
return R.error();
}
@ApiOperation(value = "图片/视频预览")
@GetMapping("/preview")
public R preview(@RequestParam("fileName") String fileName) {
return R.ok().put("filleName",minioUtil.preview(fileName));
}
@ApiOperation(value = "文件下载")
@GetMapping("/download")
public R download(@RequestParam("fileName") String fileName, HttpServletResponse res) {
minioUtil.download(fileName,res);
return R.ok();
}
@ApiOperation(value = "删除文件", notes = "根据url地址删除文件")
@PostMapping("/delete")
public R remove(String url) {
String objName = url.substring(url.lastIndexOf(prop.getBucketName()+"/") + prop.getBucketName().length()+1);
minioUtil.remove(objName);
return R.ok().put("objName",objName);
}
}
时间同步
#安装ntp ntpdate yum -y install ntp ntpdate #与时间服务器同步时间 ntpdate 0.asia.pool.ntp.org #将系统时间写入硬件时间 hwclock --systohc



