1.Minio及背景

Minio是一个开源的分布式文件存储系统,它基于 Golang 编写,虽然轻量,却拥有着不错的高性能,可以将图片、视频、音乐、pdf这些文件存储到多个主机,可以存储到多个Linux,或者多个Windows,或者多个Mac,Minio中存储最大文件可以达到5TB任何类型的文件都是支持的,主要应用在微服务系统中。

微服务字典保存 微服务文件存储_客户端

1.使用Minio的背景

我们先回顾下,平时我们做文件存储是普遍如何操作的,简单描述下步骤和时序,例如我们在添加用户信息&上传照片这个业务中,首先客户端发起文件上传操作到API,服务将文件存储到服务器本地文件夹中,生成返回一个文件摘要,摘要包括路径,文件ID等一些基本信息,然后将这些摘要信息用户业务数据组装程一个DTO,最终存储到数据库中。

微服务字典保存 微服务文件存储_微服务字典保存_02

此时我们只是一个单系统的上传文件操作,无论是业务逻辑还是技术都比较简单,但是如果是一些分布式高并发和高访问量的电商网站面临此类业务应该怎么做呢?一般电商类都是几十上百个微服务组成,如果按照单体系统的思路去实现,如果有100个微服务,那就存储在100个地方,后期的维护量岂不是令人非常头疼,加上事情做多错多,为了降低维护量,提升访问效率,我们需要将文件统一存储,基于MinIO高性能和可用性,我们选择使用MinIO来作为我们的文件管理中间件,用它承载系统文件上传下载。

微服务字典保存 微服务文件存储_客户端_03

2.MinIO实践

我们先上手简单操作一下MinIO,先准备环境,然后使用minio来进行文件的上传下载 ,首先我们需要下载MinIO,然后创建一个Api服务项目作为我们的文件微服务

1.Windows浏览器在线下载MinIOmc客户端官方文档地址

2.Linux直接创建对应目录输入下载命令或者下到本地拷贝进去,也可以使用Docker

wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server ./data

3.准备一个Api服务作为文件微服务 说白了就是使创建一个控制器,访问这个控制器进行文件操作,然后Nuget引入操作的Minio

4.创建一个FileUploadControll控制器来作为我们的接口,此处只演示简单的上传下载,当然它还支持分片批量以及拷贝操作,具体的可以根据需要查看对应的api,也比较简单

[ApiController]
[Route("[controller]")]
public class FileUploadController  : ControllerBase
{
    private readonly ILogger<FileUploadController> _logger;
    public FileUploadController(ILogger<FileUploadController> logger)
    {
        _logger = logger;
    }

    [HttpPost("Upload")]
    public IActionResult Upload(IFormFile formFile)
    {
        //1.创建MinioClient客户端
        MinioClient minioClient = new MinioClient("127.0.0.1:9000", "minioadmin", "minioadmin");

        //2.创建文件桶(数据库)
        if (!minioClient.BucketExistsAsync("micservice").Result)
        {
            minioClient.MakeBucketAsync("micservice").Wait();
        }

        //3.上传文件(最大上传5TB的数据)
        minioClient.PutObjectAsync("micservice", formFile.FileName, formFile.OpenReadStream(), formFile.Length).Wait();
        _logger.LogInformation($"文件:{formFile.FileName}上传到MinIO成功");
        return new JsonResult("上传文件成功");
    }

    [HttpGet("Download")]
    public IActionResult Download(string fileName)
    {
        FileStreamResult fileStreamResult = null;
        try
        {
            // 1、创建MioIO客户端
            MinioClient minioClient = new MinioClient("127.0.0.1:9000", "minioadmin", "minioadmin");

            var imgStream = new MemoryStream();
            // 2、下载图片
            minioClient.GetObjectAsync("micservice", fileName, stream => stream.CopyTo(imgStream)).Wait();
            imgStream.Position = 0;
            fileStreamResult = new FileStreamResult(imgStream, "image/jpg");            
        }
        catch (MinioException e)
        {
             _logger.LogInformation($"文件:{fileName}下载失败");
        }

        return fileStreamResult;
    }

}

3.现在需要启动我们的Minio服务,进入到我们的下载目录Linux一样,然后启动命令行工具

minin.exe server ./data

微服务字典保存 微服务文件存储_数据_04

我们启动后控制台会输出如下信息,首先第一部分中是我们和Minio,进程通信的服务地址用户名密码

API: http://192.168.0.102:9000 http://127.0.0.1:9000 RootUser: minioadmin
RootPass: minioadmin

第二部分就是minio提供给我们查看和运维的可视化界面,包含地址和用户名密码,密码账号是支持修改的

Console: http://192.168.0.102:52429 http://127.0.0.1:52429 RootUser: minioadmin
RootPass: minioadmin

3.MinIO文件高可用

在MinIO中目前我们上传的文件存储在服务器的磁盘中,我们在启动时默认创建了一个目录来存储文件bucket(文件桶,理解为存储文件的容器),这时如果删除了目录中的文件,可想而知会发生什么,所以保证文件高可用首要需求就是防止误删引发的数据丢失,直接导致用户使用文件失效。

1.常用的部署架构

在进行MInio文件高可用应对策略学习之前,我们先了解下保证系统和存储高可用层面的一些部署方式。

单机部署(stand-alone):只有一个服务实例提供服务,服务只部署一份。

集群部署(cluster):有多个服务实例同时提供服务,服务冗余部署,每个冗余的服务都对外提供服务 。

热备部署(hot-swap):只有一个服务提供服务,另一个服务stand-by,在服务挂掉时自动热替换。

服务冗余部署:只有一个主服务对外提供服务,影子服务在主服务挂掉时顶上

磁盘阵列RAID(Redundant Arrays of independent Disks)

RAID0:存储性能高的磁盘阵列,又称striping,它的原理是,将连续的数据分散到不同的磁盘上存储,这些不同的磁盘能同时并行存取数据

RAID1:安全性高的磁盘阵列,又称mirror,它的原理是,将数据完全复制到另一个磁盘上,磁盘空间利用率只有50%

RAID0+1:RAID0和RAID1的综合方案,速度快,安全性又高,但是很贵,这也是国企用的比较多的存储方案

RAID5:RAID0和RAID1的折衷方案,读取速度比较快(不如RAID0,因为多存储了校验位),安全性也很高(可以利用校验位恢复数据),空间利用率也不错(不完全复制,只冗余校验位),这也是互联网公司用的比较多的存储方案

2.多副本

通常面对这种防止误删的问题,我们使用的策略就是使用冗余多个目录来存储多个副本文件,多副本技术比较简单直接,要冗余保护关键数据,就干脆多存几份,单个数据的损坏不要紧,还有备份可以使用。同理多个目录不行,加入多个主机来存储,这种做法在很多场景下适用。

但这种方法的优缺点也比较明显,优点写入效率高,无需多余的计算,直接存多份即可,数据恢复快,从副本复制就好了。缺点就是存储效率低占用空间以前需要的磁盘容量直接 X2 或者 X3 倍了,对于Minio来说,文件最大可以支持5TB,那如果按照这种做法对磁盘的消耗是巨大的,就成本方面而言是不可行的,所以MinIO实现了另外一种保证文件高可用的机制,核心思想和集群方式差不多,但是对文件存储的方式跟简副本拷贝不同,Minio使用了纠删码的策略来保证我们的文件高可用。

微服务字典保存 微服务文件存储_数据_05

但这种方法的优缺点也比较明显,优点写入效率高,无需多余的计算,直接存多份即可,数据恢复快,从副本复制就好了。缺点就是存储效率低占用空间以前需要的磁盘容量直接 X2 或者 X3 倍了,对于Minio来说,文件最大可以支持4个G,那如果按照这种做法对磁盘的消耗是巨大的,就成本方面而言是不可行的,所以Minio实现了另外一种保证文件高可用的机制,核心思想和集群方式差不多,但是对文件存储的方式跟简副本拷贝不同,Minio使用了纠删码的策略来保证我们的文件高可用。

3.纠删码

在这里我们需要搞清楚的问题有好几个,先列出来吧,当然只是简单的介绍,具体深入还是需要自己去了解一下。

1.什么是RAID?

RAID中文简称为独立磁盘冗余磁盘阵列。简单的说,RAID是一种把多块独立的物理硬盘)按不同的方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据备份技术。

2.什么是纠删码?

一种数据冗余保护技术,RAID的延伸,纠删码不仅具备识别错码和纠正错码的功能,而且当错码超过纠正范围时可把无法纠错的信息删除。

我们使用一个简单的例子来理解纠删码,首先从冗余思想来着手,假设我们在存储一份大小为2M的文件时,按照4份副本冗余的做法,占用的空间就是 2Mx4的空间,先将文件分片为2份,我们取名为Sharding1、Sharding2,然后再做冗余 就是4份数据,如果我们删除A1和A2那么A3和A4可以继续组合成一个完整的文件,同理删除A3和A4它依然可以组合成完整文件。

微服务字典保存 微服务文件存储_高可用_06

这时我们思考,如果我们同时删除分片1的数据A1和A3,剩下A2和A4中都是分片2的数据,不就组合不成了吗?

微服务字典保存 微服务文件存储_客户端_07

此时如果使用纠删码的做法就是,A1和A2分片数据还是保持不变,A3=分片1+分片2A4 = 分片1+ 2*分片2,这样任意两份数据丢失,都可以恢复出分片1 和 分片2的数据了

微服务字典保存 微服务文件存储_数据_08

1.使用传统副本冗余至少2个数据目录,如果分片后就需要最少4份数据。
2.Minio按照最少分片2个的话,至少需要4个数据目录,必须有一半数据,数据不丢失,才能恢复(N/2)
3.纠删码可以恢复任何磁盘损坏的数据,包括人为删除、磁盘信道丢失、磁盘中毒。

4.MinIO文件监听及多租户
1.文件监听

有时我们想把客户端上传下载文件的一些操作,存储起来,方便以后做数据分析,最简单的做法我们可以使用写日志的方式,但是目前要做的不是介绍这种,而是使用MioIO自带的文件监听机制,MinIO允许我们配置存储操作日志的存储介质,例如Mysql、Redis、Elastic Search、Kafka、WebHook等等,MinIO可以设置对某一个Bucket实现事件监听。

1.首先我们需要在Console中配置存储介质,此处选择 Mysql

1.在mysql中创建一个可供MinIO存储操作的数据库。

2.在MinIO中配置数据库名称、表以及其他连接信息。

微服务字典保存 微服务文件存储_客户端_09

2.重启MinIO服务,并指定Console地址为9001

minio server --address :9000 --console-address ":9001" ./data

重启后控制台会给出操作Mysql的队列名arn:minio:sqs::_:mysql

3.启动MinIO自带的命令客户端mc.exe,给MinIO的Api地址取一个别名minio_queue,方便后续使用

mc.exe alias set minio_queue http://127.0.0.1:9000 minioadmin minioadmin

4.然后执行客户端命令。用于告诉MinIO服务将文件Bucket 和 Mysql队列事件注册绑定

下面命令的意思是对minio_queue服务中的micservice这个Bucket添加增、删、改、查的监听事件到arn:minio:sqs::_:mysql中,说白了就是如果对这个桶中的文件进行操作,那么就会写入日志到Mysql中

mc event add --event "put,delete" minio_queue/micservice arn:minio:sqs::_:mysql
2.MinIO多租户

Minio默认会给一个客户使用,当客户变多了之后,所有客户的数据都集中在Minio内部的时候,导致数据冲突的问题。例如客户A的数据,可能会修改成客户B的数据,客户B可能查询客户C的数据。所以解决客户数据冲突问题,我们采用多租户来实现,说白了就是系统运行多个实例给多个不同的客户使用,为不同的客户端提供服务。

微服务字典保存 微服务文件存储_客户端_10

执行如下命令,创建3个minio实例,为三个不同租户提供服务

minio server --address :8001 --console-address ":9001" ./MinIO/tenant1
minio server --address :8002 --console-address ":9001" ./MinIO/tenant2
minio server --address :8003 --console-address ":9001" ./MinIO/tenant3