数据库架构之【MinIO+Nginx+Keepalived】文件库集群方案

MinIO 是一个开源的(遵循Apache License v2.0协议)对象存储系统。它是为海量数据存储、人工智能、大数据分析而设计,单个对象最大可达5TB,适合存储海量图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

本方案基于RockyLinux8系统设计,建议在RedHat/RockyLinux/CentOS系统中使用。

  • MinIO 的主要特性

  • MinIO 下载和安装

  • MinIO 单服务器单硬盘部署

  • MinIO 单服务器多硬盘数据冗余集群部署

  • MinIO+Nginx+Keepalived 高可用集群部署
    -- 5.1. 集群部署架构
    -- 5.2. MinIO 集群部署
    -- 5.3. MinIO 集群扩容
    -- 5.4. Nginx 集群部署
    -- 5.5. Keepalived 高可用

  • MinIO 运维管理
    -- 6.1. 客户端简介
    -- 6.2. 从本地存储到 MinIO 的数据镜像
    -- 6.3. 从 MinIO 到 MinIO 的数据镜像

  • Java SpringBoot 开发集成示例

  • 1.MinIO 的主要特性

    1、高性能 :在标准硬件上,读/写速度高达 183 GB / 秒 和 171 GB / 秒。

    2、简单 :仅由一个参数极少的二进制可执行文件组成。无论是集群或者单点模式下,都可以通过一个简单指令完成服务的安装、升级和管理。升级命令可以无中断的完成 MinIO 的升级,并且不需要停机即可完成升级操作,降低总使用和运维成本。

    3、可扩展 :支持无限可伸缩的横向扩展,可以将服务器的硬盘作为一个单位节点,集群规模为服务器上硬盘的总数。

    4、多平台 :支持 intel,amd,arm,ppc64le,s390x 的 CPU 指令集架构的 Linux,Unix,Windows,MaxOS 操作系统。

    5、云原生 :符合原生云计算的架构和构建过程 其中包括支持 Kubernetes 、微服务和多租户的的容器技术。MinIO 在 DockerHub 上已经提供了官方镜像文件。

    6、易用 :提供一个中文可视化的 Web 应用控制台,通过控制台可以管理文件桶和上传、下载、删除文件。

    7、多语言 SDK 支持 :提供 JavaScript,Java,Python,Golang,.Net,Haskell 的客户端开发 SDK API。

    8、冗余 :使用纠删码、Checksum 来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失 1/2 的硬盘也能恢复数据,但是需要有 1/2 + 1 个硬盘才能创建新的对象。

    9、安全 :提供服务器端文件加密方案,且对于性能的影响可忽略不计。

    2.MinIO 下载和安装

    1、下载 MinIO 服务器端的二进制可执行文件到【/usr/local/bin】目录中,并授予可执行权限。

    各版本最新稳定版的 MinIO 服务器端下载镜像地址:

    CPU 架构 Linux 64-bit PowerPC LE (ppc64le) http://dl.minio.io/server/minio/release/linux-ppc64le/minio Linux 64-bit MIPS http://dl.minio.io/server/minio/release/linux-mips64/minio Linux IBM Z-Series (S390X) http://dl.minio.io/server/minio/release/linux-s390x/minio MaxOS 64-bit Intel/AMD http://dl.minio.io/server/minio/release/darwin-amd64/minio MaxOS 64-bit ARM http://dl.minio.io/server/minio/release/darwin-arm64/minio Windows 64-bit Intel/AMD http://dl.minio.io/server/minio/release/windows-amd64/minio.exe

    以 64-bit Intel/AMD 的 CPU 架构为例:

    [root@rocky ~]# wget http://dl.minio.io/server/minio/release/linux-amd64/minio -P /usr/local/bin
    [root@rocky ~]# chmod +x /usr/local/bin/minio
    

    2、下载 MinIO 客户端的二进制可执行文件到【/usr/local/bin】目录中,并授予可执行权限。

    各版本最新稳定版的 MinIO 客户端下载镜像地址:

    CPU 架构

    以 64-bit Intel/AMD 的 CPU 架构为例:

    [root@rocky ~]# wget http://dl.minio.io/client/mc/release/linux-amd64/mc -P /usr/local/bin
    [root@rocky ~]# chmod +x /usr/local/bin/mc
    

    3.MinIO 单服务器单硬盘部署

    1、 下载和安装 MinIO 服务器端和客户端,操作步骤请参见“2.下载和安装”章节。

    2、创建 MinIO 数据目录。

    [root@rocky ~]# mkdir /vol
    

    注意:该目录应当挂载到一块独立的物理硬盘上。

    3、设置 MinIO 管理账号和口令。

    [root@rocky ~]# export MINIO_ROOT_USER=admin
    [root@rocky ~]# export MINIO_ROOT_PASSWORD=123456aA?
    

    指定账号通过指令:export MINIO_ROOT_USER=<账号>
    指定口令通过指令:export MINIO_ROOT_PASSWORD=<口令>
    不指定时,默认的账号和口令为【minioadmin】。

    4、启动 MinIO 服务。

    1)前台启动(调试):

    [root@rocky ~]# minio server --address :9000  --console-address :9001 /vol
    

    2)后台启动(运行):

    [root@rocky ~]# nohup minio server --address :9000  --console-address :9001 /vol > /var/log/minio.log  2>&1 &
    

    指令格式:minio server <--address> <--console-address> [数据目录]
    --address:API 监听地址和端口,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为【:9000】。
    --console-address:可视化管理应用的发布地址,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为随机端口号。

    5、开启远程访问策略。

    设置防火墙端口(RockyLinux8默认安装firewall防火墙),允许"9000"、"9001"端口(MinIO 监听端口)访问服务器。

    [root@rocky ~]$ firewall-cmd --zone=public --add-port=9000/tcp --permanent
    [root@rocky ~]$ firewall-cmd --zone=public --add-port=9001/tcp --permanent
    [root@rocky ~]$ firewall-cmd --reload
    

    6、配置 MinIO 开机自启动。

    使用文本编辑器修改 “/etc/rc.d/rc.local” 文件:

    [root@rocky ~ ]$ vi /etc/rc.d/rc.local
    

    追加文件内容并保存如下:

    export MINIO_ROOT_USER="admin"
    export MINIO_ROOT_PASSWORD="123456aA?"
    nohup /usr/local/bin/minio server --address :9000 --console-address :9001 /vol > /var/log/minio.log  2>&1 &
    

    授予可执行权限:

    [root@rocky ~ ]$ chmod +x /etc/rc.d/rc.local
    

    7、重新启动并通过浏览器访问控制台验证。

    [root@rocky ~ ]$ reboot
    

    在浏览器中访问【http://ip:9000】,输入用户名和口令登录。

    4.MinIO 单服务器多硬盘数据冗余集群部署

    单服务器多硬盘的是最简单、高性价比的数据冗余集群方案,作用是【当小于 1/2 块硬盘发生故障时,不会影响使用,不会丢失数据】,但是无法保障在服务器宕机时继续提供服务,也无法提升高并发的处理性能。

    MinIO 要求集群至少有 4 个节点组成,因此服务器至少需要部署 4 块独立的物理硬盘,当 MinIO 数据目录存在于相同的硬盘时,将无法启动 MinIO 。

    因此,当系统对于稳定性和性能需求不高,仅需保障数据冗余时,建议采用此方案。

    1、 下载和安装 MinIO 服务器端和客户端,操作步骤请参见“2.下载和安装”章节。

    2、创建 MinIO 数据目录。

    [root@rocky ~]# mkdir /vol1 /vol2 /vol3 /vol4
    

    注意:每个目录必须挂载到一块独立的物理硬盘上,当目录存在于相同的硬盘时,将无法启动 MinIO 集群。

    3、设置 MinIO 管理账号和口令。

    [root@rocky ~]# export MINIO_ROOT_USER=admin
    [root@rocky ~]# export MINIO_ROOT_PASSWORD=123456aA?
    

    指定账号通过指令:export MINIO_ROOT_USER=<账号>
    指定口令通过指令:export MINIO_ROOT_PASSWORD=<口令>
    不指定时,默认的账号和口令为【minioadmin】。

    4、启动 MinIO 服务。

    1)前台启动(调试):

    [root@rocky ~]# minio server --address :9000  --console-address :9001 /vol1 /vol2 /vol3 /vol4
    

    2)后台启动(运行):

    [root@rocky ~]# nohup minio server --address :9000  --console-address :9001 /vol1 /vol2 /vol3 /vol4 \ > /var/log/minio.log  2>&1 &
    

    指令格式:minio server <--address> <--console-address> [磁盘挂载目录]
    --address:API 监听地址和端口,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为【:9000】。
    --console-address:可视化管理应用的发布地址,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为随机端口号。

    5、开启远程访问策略。

    设置防火墙端口(RockyLinux8默认安装firewall防火墙),允许"9000"、"9001"端口(MinIO 监听端口)访问服务器。

    [root@rocky ~]$ firewall-cmd --zone=public --add-port=9000/tcp --permanent
    [root@rocky ~]$ firewall-cmd --zone=public --add-port=9001/tcp --permanent
    [root@rocky ~]$ firewall-cmd --reload
    

    6、配置 MinIO 开机自启动。

    使用文本编辑器修改 “/etc/rc.d/rc.local” 文件:

    [root@rocky ~ ]$ vi /etc/rc.d/rc.local
    

    追加文件内容并保存如下:

    export MINIO_ROOT_USER="admin"
    export MINIO_ROOT_PASSWORD="123456aA?"
    nohup /usr/local/bin/minio server --address :9000 --console-address :9001 /vol1 /vol2 /vol3 /vol4 > /var/log/minio.log 2>&1 &
    

    授予可执行权限:

    [root@rocky ~ ]$ chmod +x /etc/rc.d/rc.local
    

    7、重新启动并通过浏览器访问控制台验证。

    [root@rocky ~ ]$ reboot
    

    在浏览器中访问【http://ip:9000】,输入用户名和口令登录。

    5.MinIO+Nginx+Keepalived 高可用集群部署

    高可用集群是最完整的集群方案,此方案具有集群化的全部特性:
    1. 当小于 1/2 块硬盘发生故障时,不会影响使用,不会丢失数据;
    2. 任一单服务器宕机时,不会影响使用;
    3. 具有请求负载均衡能力,各服务器节点协同提供服务,可有效提升并发处理能力。

    MinIO 要求集群至少有 4 个节点组成,因此至少需要部署 4 台 MinIO 服务器 。

    高可用集群部署方案能够满足大多数对于稳定性和并发性能较高的应用场景,是生产级别系统的完整部署方案。

    5.1. 集群部署架构

    网络部署图:

    1、 MinIO 服务器集群的各个节点下载和安装 MinIO 服务器端和客户端,操作步骤请参见“2.下载和安装”章节。

    操作部署以“MinIO-1”节点为例,所有 MinIO 集群节点需全部完成一下操作:

    2、创建 MinIO 数据目录。

    [root@minio-1 ~]# mkdir /vol
    

    3、创建 MinIO 集群服务器节点 DNS 映射记录。

    注意:MinIO 集群启动的单元称为【区域】,每个【区域】必须由一组连续服务器 IP 或 DNS 和连续的硬盘挂载目录组成。而服务器 IP 地址通常为无序的,所以通过建立本地 DNS 映射记录,将无序的 IP 地址映射成有序的 DNS 名。如下所示:

    minio server http://host{1...4}/vol{1...4} http://host{5...8}/vol{1...4}
    

    使用文本编辑器打开 “/etc/hosts” 文件:

    [root@minio-1 ~]# vi /etc/hosts
    

    追加以下内容并保存:

    192.168.0.101 minio-1
    192.168.0.102 minio-2
    192.168.0.103 minio-3
    192.168.0.104 minio-4
    

    4、设置 MinIO 管理账号和口令(注意,集群所有服务器节点的管理账号和口令必须是一致的)。

    [root@minio-1 ~]# export MINIO_ROOT_USER=admin
    [root@minio-1 ~]# export MINIO_ROOT_PASSWORD=123456aA?
    

    指定账号通过指令:export MINIO_ROOT_USER=<账号>
    指定口令通过指令:export MINIO_ROOT_PASSWORD=<口令>
    不指定时,默认的账号和口令为【minioadmin】。

    5、启动 MinIO 服务。

    1)前台启动(调试):

    [root@minio-1 ~]# minio server --address :9000 --console-address :9001 http://minio-{1...4}/vol
    

    2)后台启动(运行):

    [root@minio-1 ~]# nohup minio server --address :9000 --console-address :9001 http://minio-{1...4}/vol > /var/log/minio-cluster.log  2>&1 &
    

    指令格式:minio server <--address> <--console-address> [节点位置]
    --address:API 监听地址和端口,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为【:9000】。
    --console-address:可视化管理应用的发布地址,可以设置【IP:PORT】或者【:PORT】的形式。不指定时,默认的监听端口为随机端口号。

    注意:各个服务器节点需要依次执行启动命令,才能完成整个集群的启动。启动集群过程中,将不断提示以下错误:

    这是一个正常的现象,当已启动所有服务器节点后会自动关闭。

    6、开启远程访问策略。

    设置防火墙端口(RockyLinux8默认安装firewall防火墙),允许"9000"、"9001"端口(MinIO 监听端口)访问服务器。

    [root@minio-1 ~]$ firewall-cmd --zone=public --add-port=9000/tcp --permanent
    [root@minio-1 ~]$ firewall-cmd --zone=public --add-port=9001/tcp --permanent
    [root@minio-1 ~]$ firewall-cmd --reload
    

    7、配置 MinIO 开机自启动。

    使用文本编辑器修改 “/etc/rc.d/rc.local” 文件:

    [root@minio-1 ~ ]$ vi /etc/rc.d/rc.local
    

    追加文件内容并保存如下:

    export MINIO_ROOT_USER="admin"
    export MINIO_ROOT_PASSWORD="123456aA?"
    nohup minio server --address :9000 --console-address :9001 http://minio-{1...4}/vol > /var/log/minio-cluster.log  2>&1 &
    

    授予可执行权限:

    [root@minio-1 ~ ]$ chmod +x /etc/rc.d/rc.local
    

    8、重新启动并通过浏览器访问控制台验证。

    [root@minio-1 ~ ]$ reboot
    

    在浏览器中访问任一节点的【http://ip:9000】,输入用户名和口令登录。

    所有 MinIO 集群节点均需完成以上步骤。

    5.3. MinIO 集群的扩容。

    MinIO的极简设计理念使得MinIO分布式集群并不支持向集群中添加单个节点并进行自动调节的扩容方式,这是因为加入单个节点后所引发的数据均衡以及纠删组划分等问题会为整个集群带来复杂的调度和处理过程,并不利于维护。因此,MinIO提供了一种对等扩容的方式,即要求增加的节点数和磁盘数均需与原集群保持对等。

    例如:原集群由 4 台服务器(每服务器 4 硬盘),则在扩容时必须同样增加 4 台服务器(每服务器 4 硬盘)或为其倍数,以便系统维持相同的数据冗余 SLA,从而极大地降低扩容的复杂性。

    如上例,在扩容后,MinIO 集群并不会对全部的 8 个服务器节点进行完全的数据均衡,而是将原本的 4 个服务器节点视作一个区域,新加入的 4 个服务器节点视作另一区域,当有新对象上传时,集群将依据各区域的可用空间比例确定存放区域,在各区域内仍旧通过哈希算法确定对应的纠删组进行最终的存放。此外,集群进行一次对等扩容后,还可依据扩容规则继续进行对等扩容,但出于安全性考虑,集群的最大服务器节点数一般不得超过32个,也就是说可以扩容 2 次(第一次扩容 4 服务器,第二次扩容 8 服务器 ,第三次扩容 16 服务器)。

    新增的服务器节点参见“5.2. MinIO 集群部署”章节部署服务器。但注意以下几点:

    1、所有的服务器节点创建一致的 MinIO 集群服务器节点 DNS 映射记录。

    注意:MinIO 集群启动的单元称为【区域】,每个【区域】必须由一组连续服务器 IP 或 DNS 和连续的硬盘挂载目录组成。而服务器 IP 地址通常为无序的,所以通过建立本地 DNS 映射记录,将无序的 IP 地址映射成有序的 DNS 名。如下所示:

    minio server http://host{1...4}/vol{1...4} http://host{5...8}/vol{1...4}
    

    使用文本编辑器打开 “/etc/hosts” 文件:

    [root@minio-5 ~]# vi /etc/hosts
    

    追加以下内容并保存:

    192.168.0.101 minio-1
    192.168.0.102 minio-2
    192.168.0.103 minio-3
    192.168.0.104 minio-4
    192.168.0.105 minio-5
    192.168.0.106 minio-6
    192.168.0.107 minio-7
    192.168.0.108 minio-8
    

    2、新增服务器节点必须设置与原始集群设置一致的 MinIO 管理账号和口令。

    [root@minio-5 ~]# export MINIO_ROOT_USER=admin
    [root@minio-5 ~]# export MINIO_ROOT_PASSWORD=123456aA?
    

    指定账号通过指令:export MINIO_ROOT_USER=<账号>
    指定口令通过指令:export MINIO_ROOT_PASSWORD=<口令>
    不指定时,默认的账号和口令为【minioadmin】。

    3、所有服务器节点的集群启动命令改变(注意修改开机启动文件),原始集群需重新启动 MinIO 服务。

    1)前台启动(调试):

    [root@minio-5 ~]# minio server --address :9000 --console-address :9001 http://minio-{1...4}/vol  http://minio-{5...8}/vol
    

    2)后台启动(运行):

    [root@minio-5 ~]# nohup minio server --address :9000 --console-address :9001 http://minio-{1...4}/vol   http://minio-{5...8}/vol > /var/log/minio-cluster.log  2>&1 &
    

    5.4. Nginx 集群部署

    在各个 "Nginx " 集群节点(Nginx-1、Nginx-2)安装、配置 Nginx,以 "Nginx-1" 为例:

    1、打开 Nginx 下载页面【http://nginx.org/en/download.html】,下载 Nginx 的源代码 tar.gz 包到用户主目录中。

    Nginx 下载页面

    2、验证并安装依赖软件。通过源代码编译的方式安装 Nginx,需要依赖软件"make"、"gcc"、"pcre"、"pcre-devel"、"zlib"、"zlib-devel"、"openssl"、"openssl-devel",验证或安装依赖软件。

    [root@nginx-1 ~]# dnf install make gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel
    

    补充知识:

    ① "gcc"是一个C/C++、FORTRAN、JAVA、OBJC、ADA等多种语言的编译器,用来将源代码编译成可发布的软件程序。

    ② "make"是一个工程管理工具,能够根据 Makefile 中的脚本执行编译、安装流程。

    ③ "pcre"是一个正则表达式函数库;"pcre-devel"是它的开发库。

    ④ "zlib"是一个数据压缩函数库;"zib-devel"是它的开发库。

    ⑤ "openssl"是一个实现安全通信,避免窃听,同时确认另一端连接者身份的软件程序;"openssl-devel"是它的开发库。

    3、解压缩 Nginx 的源代码 tar 包到用户主目录下。

    [root@nginx-1 ~]# tar -zxvf nginx-1.18.0.tar.gz
    [root@nginx-1 ~]# ll
    drwxr-xr-x.  8 centos centos    4096 4月  21 22:09 nginx-1.18.0
    

    4、安装 Nginx,进入源代码目录,配置、编译、安装程序。

    [root@nginx-1 ~]# cd nginx-1.18.0
    [root@nginx-1 nginx-1.18.0]# ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module  --with-stream
    Configuration summary
      + using system PCRE library
      + OpenSSL library is not used
      + using system zlib library
      nginx path prefix: "/usr/local/nginx"
      nginx binary file: "/usr/local/nginx/sbin/nginx"
      nginx modules path: "/usr/local/nginx/modules"
      nginx configuration prefix: "/usr/local/nginx/conf"
      nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
      nginx pid file: "/usr/local/nginx/logs/nginx.pid"
      nginx error log file: "/usr/local/nginx/logs/error.log"
      nginx http access log file: "/usr/local/nginx/logs/access.log"
      nginx http client request body temporary files: "client_body_temp"
      nginx http proxy temporary files: "proxy_temp"
      nginx http fastcgi temporary files: "fastcgi_temp"
      nginx http uwsgi temporary files: "uwsgi_temp"
      nginx http scgi temporary files: "scgi_temp"
    [root@nginx-1 nginx-1.18.0]# make
    [root@nginx-1 nginx-1.18.0]# make install
    [root@nginx-1 ~]# ll /usr/local/nginx
    drwxr-xr-x. 2 root root 4096 5月  18 09:39 conf
    drwxr-xr-x. 2 root root   40 5月  18 09:39 html
    drwxr-xr-x. 2 root root    6 5月  18 09:39 logs
    drwxr-xr-x. 2 root root   19 5月  18 09:39 sbin
    

    程序安装目录是"/usr/local/nginx"。

    5、设置 Nginx 配置文件参数。

    使用文本编辑器打开配置文件:

    [root@nginx-1 ~]# vi /usr/local/nginx/conf/nginx.conf
    

    修改或验证文件中的以下参数并保存(七层代理):

    http {
        # 负载均衡
        upstream minio-api {
            server 192.168.0.101:9000 weight=1;
            server 192.168.0.102:9000 weight=1;
            server 192.168.0.103:9000 weight=1;
            server 192.168.0.104:9000 weight=1;
        # 负载均衡
        upstream minio-console {
            server 192.168.0.101:9001 weight=1;
            server 192.168.0.102:9001 weight=1;
            server 192.168.0.103:9001 weight=1;
            server 192.168.0.104:9001 weight=1;
        server {
            # 监听端口
            listen       9000;
            # 服务器域名(主机头)
            server_name  localhost;
            # 代理 Web 服务的 Url 前缀,一般是 Web 服务的虚拟目录(可以是正则表达式)。
            location / {
                proxy_pass http://minio-api;
        server {
            # 监听端口
            listen       9001;
            # 服务器域名(主机头)
            server_name  localhost;
            # 代理 Web 服务的 Url 前缀,一般是 Web 服务的虚拟目录(可以是正则表达式)。
            location / {
                proxy_pass http://minio-console;
    

    6、配置 Nginx 开机自启动。

    使用文本编辑器创建配置文件:

    [root@nginx-1 ~]# vi /usr/lib/systemd/system/nginx.service
    

    编写文件内容并保存如下:

    [Unit]
    Description=Nginx
    After=syslog.target network.target
    [Service]
    Type=forking
    User=root
    Group=root
    ExecStart=/usr/local/nginx/sbin/nginx
    ExecReload=/usr/local/nginx/sbin/nginx -s reload
    ExecStop=/usr/local/nginx/sbin/nginx -s quit
    PrivateTmp=true
    [Install]
    WantedBy=multi-user.target
    

    设置开机启动:

    [root@nginx-1 ~]#  systemctl daemon-reload
    [root@nginx-1 ~]#  systemctl enable nginx.service
    

    7、启动 Nginx 服务。

    [root@nginx-1 ~]#  systemctl start nginx.service
    

    8、设置防火墙端口(CentOS8默认安装firewall防火墙),允许"9000","9001"端口(Nginx 默认端口)访问服务器。

    [root@nginx-1 ~]# firewall-cmd --zone=public --add-port=9000/tcp --permanent
    [root@nginx-1 ~]# firewall-cmd --zone=public --add-port=9001/tcp --permanent
    [root@nginx-1 ~]# firewall-cmd --reload
    

    注意:其他 "Nginx" 集群节点全部需要按照以上步骤配置。

    9、Nginx 运维管理。

    1)启动 Nginx 服务(任选一种方式)

    [root@nginx-1 ~]# systemctl start nginx.service
    
    [root@nginx-1 ~]# /usr/local/nginx/sbin/nginx
    

    2)停止 Nginx 服务(任选一种方式)

    [root@nginx-1 ~]# systemctl stop nginx.service
    
    [root@nginx-1 ~]# /usr/local/nginx/sbin/nginx -s quit
    

    3)重启 Nginx 服务

    [root@nginx-1 ~]# systemctl restart nginx.service
    
    [root@nginx-1 ~]# /usr/local/nginx/sbin/nginx -s reload
    

    4)查看 Nginx 服务状态

    [root@nginx-1 ~]# systemctl status nginx.service
    
    [root@nginx-1 ~]# ps -ef | grep nginx
    root     119777      1  0 10:16 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
    [root@nginx-1 ~]# netstat -ntap | grep nginx
    tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      119777/nginx: maste
    [root@nginx-1 ~]# tail /usr/local/nginx/logs/error.log
    [root@nginx-1 ~]# tail /usr/local/nginx/logs/access.log
    

    5)启用 Nginx 服务开机自启动

    [root@nginx-1 ~]# systemctl enable nginx.service
    

    6)禁用 Nginx 服务开机自启动

    [root@nginx-1 ~]# systemctl disable nginx.service
    

    5.5. Keepalived 高可用

    在各个 "Nginx" 集群节点 (Nginx-1、Nginx-2)安装、配置 Keepalived,以 "Nginx-1" 为例:

    1、安装 EPEL 的 Yum源。

    使用文本编辑器创建仓库配置文件:

    [root@nginx-1 ~ ]# vi /etc/yum.repos.d/epel.repo
    

    在文件中编写以下内容并保存:

    [epel-modular]
    name=Extra Packages for Enterprise Linux Modular $releasever - $basearch
    baseurl=http://mirrors.aliyun.com/epel/$releasever/Modular/$basearch
    enabled=1
    gpgcheck=1
    gpgkey=http://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-8
    [epel]
    name=Extra Packages for Enterprise Linux $releasever - $basearch
    baseurl=http://mirrors.aliyun.com/epel/$releasever/Everything/$basearch
    enabled=1
    gpgcheck=1
    gpgkey=http://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-8
    

    更新 Yum 源:

    [root@nginx-1 ~ ]# dnf clean all
    [root@nginx-1 ~ ]# dnf makecache
    Extra Packages for Enterprise Linux Modular 8 - 429 kB/s | 118 kB     00:00    
    Extra Packages for Enterprise Linux 8 - x86_64  3.7 MB/s | 6.9 MB     00:01    
    元数据缓存已建立。
    

    EPEL(Extra Packages for Enterprise Linux)是企业级 Linux 操作系统的扩展包仓库,为 Redhat/CentOS系统提供大量的额外软件包。

    2、安装 Keepalived。

    [root@nginx-1 ~ ]# dnf install keepalived
    

    程序安装目录是"/usr/sbin",配置文件目录是"/etc/keepalived"。

    3、设置 Keepalived 配置文件参数。

    使用文本编辑器打开配置文件:

    [root@nginx-1 ~ ]# vi /etc/keepalived/keepalived.conf
    

    在文件中编写以下内容并保存:

    # 定义全局配置
    global_defs {
        # 本地节点 ID 标识,一般设置为主机名。
        router_id nginx-1
    # 定义周期性执行的脚本,脚本的退出状态码会被调用它的所有的 vrrp_instance 记录。
    vrrp_script chk_nginx {
        # 执行脚本的路径。
        script "/etc/keepalived/nginx_check.sh"
        # 脚本执行的间隔(单位是秒)。默认为1s。
        interval 2
        # 当脚本调整优先级,从 -254 到 254。默认为2。
        # 1. 如果脚本执行成功(退出状态码为0),weight大于0,则priority增加。
        # 2. 如果脚本执行失败(退出状态码为非0),weight小于0,则priority减少。
        # 3. 其他情况下,priority不变。
        weight -20
        # 当脚本执行超过时长(单位是秒)则被认为执行失败。
        # 运行脚本的用户和组。
        user root root
        # timeout 30
        # 当脚本执行成功到设定次数时,才认为是成功。
        # rise 1
        # 当脚本执行失败到设定次数时,才认为是失败。
        # fall 3
    # 定义虚拟路由,可以定义多个。
    vrrp_instance VI_1 {
        # 本地节点初始状态,包括 MASTER(主节点) 和 BACKUP (备节点)。
        state MASTER
        # 本地节点绑定虚拟 IP 的网络接口。
        interface ens33
        # 本地节点优先级,优先级高的节点将动态变成 MASTER 节点,接管 VIP 。初始状态下,MASTER 节点的优先级必须高于 BACKUP 节点。
        priority 100
        # VRRP 实例 ID,范围是0-255。同一集群的所有节点应设置一致的值。
        virtual_router_id 216
        # 组播信息发送时间间隔。同一集群的所有节点必须设置一样,默认为1秒。
        advert_int 1
        # 设置验证信息。同一集群的所有节点必须一致
        authentication {
            # 指定认证方式。PASS 表示简单密码认证(推荐);AH:IPSEC认证(不推荐)。
            auth_type PASS
            # 指定认证所使用的密码,最多8位。
            auth_pass 1111
        # 声明调用已定义的 vrrp_script 脚本。
        track_script {
            chk_nginx
        # 定义虚拟 IP 地址。
        virtual_ipaddress {
            192.168.0.200
    

    初始化的主节点和备节点的区别体现在以下参数中:

  • 初始主节点
  • vrrp_instance VI_1 {
        # 必须设置为 MASTER 。
        state MASTER
        # 必须设置为最大值。
        priority 100
    
  • 初始备节点
  • vrrp_instance VI_1 {
        # 必须设置为 BACKUP 。
        state BACKUP
        # 必须设置为小于主节点的值。
        priority 90
    

    4、创建或编辑 Nginx 检测脚本文件。文件路径对应配置文件中 vrrp_script 的 script 设置值。

    使用文本编辑器创建脚本文件:

    [root@nginx-1 ~ ]# vi /etc/keepalived/nginx_check.sh
    

    在脚本文件中编写以下内容并保存:

    #!/bin/bash
    counter=$(ps -C nginx --no-heading|wc -l)
    if [ "${counter}" = "0" ]; then
        /usr/local/nginx/sbin/nginx
        sleep 2
        counter=$(ps -C nginx --no-heading|wc -l)
        if [ "${counter}" = "0" ]; then
            killall -9 keepalived
    

    给脚本文件增加可执行权限:

    [root@nginx-1 ~ ]# chmod 755 /etc/keepalived/nginx_check.sh
    

    5、配置 Keepalived 系统服务。

    使用文本编辑器创建配置文件:

    [root@nginx-1 ~ ]# vi /usr/lib/systemd/system/keepalived.service
    

    验证或修改文件内容并保存如下:

    [Unit]
    Description=LVS and VRRP High Availability Monitor
    After=network-online.target syslog.target nginx.service
    Wants=network-online.target
    Requires=nginx.service
    [Service]
    Type=forking
    User=root
    Group=root
    PIDFile=/var/run/keepalived.pid
    KillMode=process
    EnvironmentFile=-/etc/sysconfig/keepalived
    ExecStart=/usr/sbin/keepalived $KEEPALIVED_OPTIONS
    ExecReload=/bin/kill -HUP $MAINPID
    ExecStop=/bin/kill -HUP $MAINPID
    [Install]
    WantedBy=multi-user.target
    

    重新加载系统服务管理器:

    [root@nginx-1 ~ ]# systemctl daemon-reload
    

    6、设置防火墙端口(CentOS8默认安装firewall防火墙),允许"112"端口(Keepalived 默认端口)访问服务器。

    [root@nginx-1 ~ ]# firewall-cmd --zone=public --add-port=112/tcp --permanent
    [root@nginx-1 ~ ]# firewall-cmd --reload
    

    7、启动/重启 Keepalived 服务(不建议设置为开机自启动)。

    启动 Keepalived 服务之前,应确保已正确启动了各节点的 Nginx 服务。各节点的启动或重启的顺序为:① 启动 Keepalived 主节点;② 依次启动 Keepalived 备节点。

    [root@nginx-1 ~ ]# systemctl restart keepalived.service
    

    注意:其他 "Nginx" 集群节点全部需要按照以上步骤配置。

    6. MinIO 运维管理

    6.1. 客户端简介

    MinIO 提供两种客户端,一是基于 Shell 的【mc】,二是基于 Web 的【console】。

    mc 客户端的安装参见 “2.MinIO 下载和安装” 章节,安装后可以通过以下指令来了解提供的功能:

    [root@rocky ~]$ mc --help
    

    在使用 mc 客户端操作 MinIO 之前,需要创建一个数据链接配置,指令如下:

    [root@rocky ~]$ mc config host add minio http://192.168.0.101:9000 admin 123456aA?
    Added `minio` successfully.
    

    指令格式:mc config host add <别名> <MinIO API 地址:端口号> <账号> <口令>

    其他功能可参见MinIO中文网站【http://docs.minio.org.cn/docs/master/minio-client-quickstart-guide】。

    console 客户端在启动 MinIO 服务时跟随启动,可以通过在浏览器中访问【http://ip:9000】,输入用户名和口令登录。

    6.2. 从本地存储到 MinIO 的数据镜像

    mc 客户端提供从本地路径到远程 MinIO 文件桶的指令,可用于将本地数据一次性或者实时上传到指定的 MinIO 文件桶中。

    1、登录 console 客户端创建一个目标文件桶,如:backup。

    2、在客户端本地通过 mc 客户端创建 MinIO 服务器连接配置(只需创建一次)。

    [root@rocky ~]$ mc config host add minio http://192.168.0.101:9000 admin 123456aA?
    Added `minio` successfully.
    

    3、从客户端本地路径镜像到 MinIO 目标文件桶中。

    指令格式:mc mirror <--overwrite> <-w> <本地目录> <MinIO 服务器别名 / 目标文件桶>

    [root@rocky ~]$ mc mirror --overwrite /vol minio/backup
    

    4、开启实时增量镜像。

    实时增量镜像应当后台运行,启动后将监听镜像目录的变化,并将这些变化实时同步到 MinIO 的目标文件桶中:

    [root@rocky ~]$ nohup mc mirror -w --overwrite /vol minio/backup  > /var/log/minio-mirror.log  2>&1 &
    

    5、将实时增量镜像设置为开机启动服务。

    使用文本编辑器修改 “/etc/rc.d/rc.local” 文件:

    [root@rocky ~ ]$ vi /etc/rc.d/rc.local
    

    追加文件内容并保存如下:

    nohup mc mirror -w --overwrite /vol minio/backup  > /var/log/minio-mirror.log  2>&1 &
    

    授予可执行权限:

    [root@rocky ~ ]$ chmod +x /etc/rc.d/rc.local
    

    6.3. 从 MinIO 到 MinIO 的数据镜像

    console 客户端提供文件桶的发布订阅功能,可用于数据实时分发和同步的应用场景。

    发布端能够以“文件桶”为管理单位,通过创建并启用分发规则,规则主要包括订阅:服务器的访问信息、目标文件桶、数据删除策略等。自发布规则生效之时起,发布端源文件桶所有增量操作都将同步到订阅端的目标文件桶中。

    实时同步是从发布端到订阅端的单向同步过程,并且是基于事件触发方式的。因此,当订阅端修改数据时,不会与发布端相互影响,发布端也不会检查和修正订阅端的数据。

  • 源 MinIO 服务器接入点为【http://192.168.0.101:9000】,源文件桶叫做【origin】;
  • 目标 MinIO 服务器接入点为【http://192.168.0.102:9000】,目标文件桶叫做【dest】。
  • 操作步骤如下:

    1、分别登录源 MinIO 服务器和目标 MinIO 服务器的 console 客户端,创建源文件桶【origin】和目标文件桶【dest】,并开启这些文件桶的版本控制功能。以源 MinIO 服务器的操作为例:

    3、登录源 MinIO 服务器的 console 客户端,在源文件桶中创建测试文件,查看是否同步到目标 MinIO 服务器的目标文件中。

    7.Java SpringBoot 开发集成

    1、Maven 引用。

    <dependency>
      <groupId>io.minio</groupId>
      <artifactId>minio</artifactId>
      <version>7.0.2</version>
    </dependency>
    

    2、application.yml 配置文件。

    # [note] MinIO settings。
    minio:
      endpoint: http://<IP>:<PORT>
      accessKey: <username>
      secretKey: <password>
      secure: false
    

    3、注入 MinIOClient 实例

    package zhangy.minio;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import io.minio.MinioClient;
    import lombok.SneakyThrows;
     * MinIO 配置类
     * @author 张毅
    @Configuration
    public class MinIOClientConfig {
         * 接入点
        @Value("${minio.endpoint}")
        private String endpoint;
         * 访问秘钥
        @Value("${minio.accessKey}")
        private String accessKey;
         * 安全秘钥
        @Value("${minio.secretKey}")
        private String secretKey;
         * 是否启用ssl协议
        @Value("${minio.secure}")
        private boolean secure;
         * 注入配置。
         * @return 配置。
        @Bean
        @SneakyThrows
        public MinioClient getMinioClient() {       
            return new MinioClient(endpoint, accessKey, secretKey, secure);
    

    4、MinIO 工具类。

    package zhangy.minio;
    import java.io.InputStream;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Optional;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import cn.hutool.core.io.FileUtil;
    import cn.hutool.core.util.StrUtil;
    import io.minio.MinioClient;
    import io.minio.ObjectStat;
    import io.minio.PutObjectOptions;
    import io.minio.Result;
    import io.minio.messages.Bucket;
    import io.minio.messages.Item;
    import lombok.SneakyThrows;
     * MinIO 远程文件系统客户端实现类
     * @author 张毅
    @Component
    public class MinIOClient {
        @Autowired
        protected MinioClient minioClient;
         * 配置文件桶
        @Value("${minio.bucket:default}")
        protected String bucket;
         * 创建文件桶。
         * @param bucketName 文件桶名称。
        @SneakyThrows
        public void makeBucket(String bucketName) {
            if (!minioClient.bucketExists(bucketName)) {
                minioClient.makeBucket(bucketName);
         * 获取全部文件桶。
        @SneakyThrows
        public List<Bucket> listBuckets() {
            return minioClient.listBuckets();
         * 检查文件桶是否已创建。
         * @param bucketName 文件桶名称。
        @SneakyThrows
        public boolean existsBucket(String bucketName) {
            return minioClient.bucketExists(bucketName);
         * 根据文件桶名称获取信息。
         * @param bucketName 文件桶名称。
        @SneakyThrows
        public Optional<Bucket> getBucket(String bucketName) {
            return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
         * 根据文件桶名称删除信息。
         * @param bucketName 文件桶名称。
        @SneakyThrows
        public void removeBucket(String bucketName) {
            minioClient.removeBucket(bucketName);
         * 设置文件桶权限。
         * @param bucketName 文件桶名称。
         * @param policy     文件桶授权。
        @SneakyThrows
        public void setBucketPolicy(String bucketName, PolicyType policy) {
            if (minioClient.bucketExists(bucketName)) {
                minioClient.setBucketPolicy(bucketName, policy.value);
         * 设置文件桶权限(只读)。
         * @param bucketName 文件桶名称。
        @SneakyThrows
        public void setBucketPolicy(String bucketName) {
            this.setBucketPolicy(bucketName, PolicyType.READ_ONLY);
         * 设置文件桶权限。
         * @param bucketName 文件桶名称。
         * @param policy     文件桶授权。
        @SneakyThrows
        public String getBucketPolicy(String bucketName) {
            if (minioClient.bucketExists(bucketName)) {
                return minioClient.getBucketLifeCycle(bucketName);
            return null;
         * 上传文件。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @param stream     文件流。
         * @param options    参数。
        @SneakyThrows
        public void putObject(String bucketName, String objectName, InputStream stream, PutObjectOptions options) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            minioClient.putObject(bucketName, objectName, stream, options);
         * 上传文件。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @param stream     文件流。
         * @param size       文件长度。
        @SneakyThrows
        public void putObject(String bucketName, String objectName, InputStream stream, long size) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            PutObjectOptions options = new PutObjectOptions(size, size >= PutObjectOptions.MIN_MULTIPART_SIZE ? size : 0l);
            minioClient.putObject(bucketName, objectName, stream, options);
         * 上传文件。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @param filename   文件名。
         * @param options    参数。
        @SneakyThrows
        public void putObject(String bucketName, String objectName, String filename, PutObjectOptions options) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            minioClient.putObject(bucketName, objectName, filename, options);
         * 上传文件。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @param filename   文件名。
        @SneakyThrows
        public void putObject(String bucketName, String objectName, String filename) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            long size = FileUtil.size(FileUtil.file(filename));
            PutObjectOptions options = new PutObjectOptions(size, size >= PutObjectOptions.MIN_MULTIPART_SIZE ? size : 0l);
            minioClient.putObject(bucketName, objectName, filename, options);
         * 根据文件前置查询文件。
         * @param bucketName bucket名称。
         * @param prefix     前缀。
         * @param recursive  是否递归查询。
         * @return MinioItem 列表。
        @SneakyThrows
        public List<Item> getObjects(String bucketName, String prefix, boolean recursive) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            List<Item> list = new ArrayList<>();
            Iterable<Result<Item>> objectsIterator = minioClient.listObjects(bucketName, prefix, recursive);
            if (objectsIterator != null) {
                Iterator<Result<Item>> iterator = objectsIterator.iterator();
                if (iterator != null) {
                    while (iterator.hasNext()) {
                        Result<Item> result = iterator.next();
                        Item item = result.get();
                        list.add(item);
            return list;
         * 获取文件
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @return 二进制流。
        @SneakyThrows
        public InputStream getObject(String bucketName, String objectName) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            return minioClient.getObject(bucketName, objectName);
         * 获取文件外链。
         * @param bucketName bucket名称。
         * @param objectName 文件名称。
         * @param expires    过期时间 <=7。
         * @return url。
        @SneakyThrows
        public String getObjectURL(String bucketName, String objectName, Integer expires) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            return minioClient.presignedGetObject(bucketName, objectName, expires);
         * 获取文件路径。
         * @param bucketName 文件桶名称。
         * @param fileName   文件名。
         * @return 文件路径。
        @SneakyThrows
        public String getObjectURL(String bucketName, String fileName) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            return minioClient.getObjectUrl(bucketName, fileName);
         * 获取对象元数据。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称。
         * @return 对象元数据。
        @SneakyThrows
        public ObjectStat statObject(String bucketName, String objectName) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            return minioClient.statObject(bucketName, objectName);
         * 删除文件。
         * @param bucketName 文件桶名称。
         * @param objectName 文件名称,
        @SneakyThrows
        public void removeObject(String bucketName, String objectName) {
            if (StrUtil.isBlank(bucketName)) {
                bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : bucket;
            this.makeBucket(bucketName);
            minioClient.removeObject(bucketName, objectName);
         * 文件桶权限枚举。
         * @author 张毅
        public enum PolicyType {
            NONE("none"), READ_ONLY("readonly"), READ_WRITE("readwrite"), WRITE_ONLY("writeonly");
            private final String value;
            private PolicyType(final String value) {
                this.value = value;
            public String getValue() {