Spiga

Docker Swarm集群1

2025-08-09 10:45:35

最近公司有个.net项目需要集群部署,由于不打算使用K8S,计划用docker swarm来搭建整个集群环境。

所以计划用2篇文章,记录一下docker swarm部署的整个过程。

文章使用的环境是本地虚拟机环境,生产环境大同小异。

文中用的到资料下载:docker-swarm.zip

一、安装 Docker

对于 Ubuntu/Debian 系统:

# 查看 ip 
ip a
使用工具测试链接,如putty

# 1. 更新软件包索引
sudo apt-get update

# 2. 安装依赖包
sudo apt-get install ca-certificates curl gnupg lsb-release

# 3. 添加 Docker 官方 GPG 密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 4. 设置稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu   $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. 安装 Docker 引擎
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 检查 Docker 版本
docker --version

对于 CentOS/RHEL 系统:

# 安装依赖包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加 Docker 仓库
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io

# 启动并启用 Docker
sudo systemctl start docker
sudo systemctl enable docker

配置镜像代理

# 创建 Docker 配置目录(如果不存在)
sudo mkdir -p /etc/docker

# 创建/修改 daemon.json 配置文件
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://hub-mirror.c.163.com",
    "https://mirror.ccs.tencentyun.com",
    "https://05f073ad3c0010ea0f4bc00b7105ec20.mirror.swr.myhuaweicloud.com"
  ]
}
EOF

# 重新加载配置并重启 Docker
sudo systemctl daemon-reload
sudo systemctl restart docker

二、安装 docker集群 swarm

先准备好3台虚拟机,我这边主机名和ip地址信息如下:

docker-manager1: 192.168.16.129

docker-worker1: 192.168.16.130

docker-worker2: 192.168.16.132

# 修改账户名:
sudo hostnamectl set-hostname 新主机名
sudo hostnamectl set-hostname docker-manager1
sudo hostnamectl set-hostname docker-worker1
sudo hostnamectl set-hostname docker-worker2

1. docker集群的功能

  • 可伸缩性
  • 状态协调
  • 覆盖网络
  • 服务发现:内置DNS名称,可以做负载均衡
  • 滚动更新

2. 重要概念

管理者:管理其他工作者、给工作者派发执行容器应用的任务

工作者:按指示允许容器应用

管理节点:可以有多个,选出Leader, 可以把管理节点配置成仅管理

服务:指定容器镜像

全局服务:集群中所有主机都会运行一个实例的服务

入口负载均衡

3. 部署

# 最好是切换到root账户下
sudo -i
# 1. master安装
sudo docker swarm init --advertise-addr 192.168.16.129:2377
# 会打印token,如果忘记了可以使用下面指令生成
docker swarm join-token manager
docker swarm join-token worker
# 2. node节点上复制token内容,加入集群
docker swarm join --token SWMTKN-1-3ghw17tk57sthgsvfagtoeuo26sq1o0kqqd2v9xoeqwnl66bun-8e0xknvu8wx2oza3sr1ggn912 192.168.16.129:2377
# 查看集群
docker node ls
# 输出应显示所有节点及其状态(如 Ready/Active)。

4. 可选配置

  1. 开放防火墙端口

    # 对于管理节点
    sudo ufw allow 2377/tcp   # 集群管理通信
    sudo ufw allow 7946/tcp   # 节点间通信
    sudo ufw allow 7946/udp
    sudo ufw allow 4789/udp   # overlay网络
    
    # 对于工作节点
    sudo ufw allow 7946/tcp
    sudo ufw allow 7946/udp
    sudo ufw allow 4789/udp
    
  2. 创建 overlay 网络

    sudo docker network create --driver overlay my-overlay-network
    

5. 注意事项

  1. 确保所有虚拟机之间网络互通
  2. 时间同步很重要,建议在所有节点上安装并配置 NTP
  3. 生产环境建议配置至少 3 个管理节点以实现高可用
  4. 可以使用 docker swarm join-token --rotate 命令定期轮换加入令牌

三、配置nfs共享目录

1. 配置 NFS 服务

# 1. 安装 RPC 服务 和 nfs 服务
apt-get install rpcbind nfs-server -y

# 2. 分别为 RPC 服务和 NFS 服务设置开机启动服务
# 必须先启动RPC服务,  再启动NFS服务
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs-server && systemctl enable nfs-server

# 3. 创建一个共享文件夹,位置可以任意
mkdir /srv/nfs_data
ls /srv

# 4. 编辑NFS主配置文件,添加要共享的目录
vim /etc/exports
加上:/srv/nfs_data *(rw,sync,insecure,no_subtree_check,no_root_squash)
# 保存退出配置文件,重新加载配置 i,:wq
exportfs -rv
# 查看当前共享的目录列表,确保已生效
showmount -e

2. 部署 NFS 客户端

# 1. 连接工作节点,安装 nfs 客户端依赖包
apt install nfs-common -y
# 测试能不能获取到共享目录列表
showmount -e 192.168.16.129

# 2. 创建一个用于挂载远程共享目录的本地目录
mkdir /srv/nfs_data

# 3. 使用mount命令进行挂载
mount -t nfs 192.168.16.129:/srv/nfs_data /srv/nfs_data
# 使用 DF 命令查看挂载情况
df -hT

3. 测试

# 1. 管理节点添加测试文件
echo “Holle NFS” > /srv/nfs_data/hello.txt

# 2. 工作节点查看是否成功创建
cat /srv/nfs_data/hello.txt

4. 固化配置,系统启动时自动挂载

# 共享文件本来就在管理节点上,所以仅工作节点需要挂载
vim /etc/fstab
添加 192.168.16.129:/srv/nfs_data /srv/nfs_data nfs timeo=14,hard,bg,intr 0 0
# 保存并退出文件,下次重启时就会生效

四、部署 MySQL 主从复制

1. 知识点

  • 异步复制(默认)

  • 半同步复制

    以上两种都需要先找到主数据库的二进制日志名称+读取位置

  • GTID复制(全局事务标记)

    不需要知道二机制日志的位置,通过全局唯一的事务编号来判断从哪里开始同步数据

2. docker-compose文件(一主二从)

# 定义 Docker Compose 文件版本
version: '3.9'

# 定义网络配置
networks:
  mysql-cluster:  # 创建一个名为 mysql-cluster 的覆盖网络,用于容器间通信

# 定义服务
services:
  # 主数据库服务
  mysql-master:
    image: 'mysql:8.0.37'
    command:  # 启动 MySQL 时的命令行参数
      [
        '--server-id=100',  # 唯一服务器ID(主库通常设为100)
        '--gtid-mode=ON',  # 启用全局事务标识符(GTID)模式
        '--enforce-gtid-consistency=ON',  # 强制GTID一致性
        '--log-bin=mysql-bin',  # 启用二进制日志(主从复制必需)
        '--log-slave-updates=ON',  # 从库也记录二进制日志(级联复制时使用)
        '--binlog-format=ROW',  # 使用ROW格式的二进制日志(最安全的复制格式)
        '--sync-binlog=1'  # 每次事务都同步二进制日志到磁盘(最安全但性能略低)
      ]
    ports:
      - "3306:3306"  # 将容器3306端口映射到主机3306端口
    environment:  # 设置环境变量
      - TZ=Asia/Shanghai  # 设置时区为上海
      - MYSQL_ROOT_PASSWORD=root  # 设置root用户密码
      - MYSQL_USER=repl  # 创建普通用户(用于复制)
      - MYSQL_PASSWORD=123456  # 设置普通用户密码
    volumes:
      # 数据目录挂载
      - /var/lib/data/mysql:/var/lib/mysql
      # 挂载初始化SQL脚本
      - /srv/nfs_data/mysql-cluster/sql-scripts/master_init.sql:/docker-entrypoint-initdb.d/init.sql
    deploy:  # 部署配置(适用于Swarm模式)
      mode: replicated  # 复制服务模式
      replicas: 1  # 只运行1个副本
      placement:
        constraints: [node.hostname==docker-manager1]  # 指定运行在docker-manager1节点
      restart_policy:  # 重启策略
        condition: any  # 任何情况下都重启
        delay: 5s  # 重启间隔5秒
    networks:
      - mysql-cluster  # 连接到mysql-cluster网络

  # 从数据库服务1
  mysql-slave-1:
    image: 'mysql:8.0.37'
    command:
      [
        '--server-id=101',  # 从库服务器ID(必须与主库不同)
        '--gtid-mode=ON',  # 启用GTID模式(必须与主库一致)
        '--enforce-gtid-consistency=ON',  # 强制GTID一致性
        '--read-only=ON',  # 设置从库为只读模式(防止误操作)
        '--skip-slave-start=OFF',  # 启动时自动开始复制(设为OFF表示自动启动)
        '--relay-log-recovery=ON',  # 启用中继日志恢复(崩溃后自动恢复复制)
        '--master-info-repository=TABLE',  # 将主库信息存储在表中(非文件,更可靠)
        '--relay-log-info-repository=TABLE'  # 将中继日志信息存储在表中
      ]
    ports:
      - "3307:3306"  # 映射到主机3307端口(避免与主库冲突)
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=root
    volumes:
      - /var/lib/data/mysql:/var/lib/mysql
      - /srv/nfs_data/mysql-cluster/sql-scripts/slave_init.sql:/docker-entrypoint-initdb.d/init.sql
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.hostname==docker-worker1]  # 指定运行在docker-worker1节点
      restart_policy:
        condition: any
        delay: 5s
    networks:
      - mysql-cluster

  # 从数据库服务2
  mysql-slave-2:
    image: 'mysql:8.0.37'
    command:
      [
        '--server-id=102',
        '--gtid-mode=ON',
        '--enforce-gtid-consistency=ON',
        '--read-only=ON',
        '--skip-slave-start=OFF',
        '--relay-log-recovery=ON',
        '--master-info-repository=TABLE',
        '--relay-log-info-repository=TABLE'
      ]
    ports:
      - "3308:3306"  # 映射到主机3308端口
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=root
    volumes:
      - /var/lib/data/mysql:/var/lib/mysql
      - /srv/nfs_data/mysql-cluster/sql-scripts/slave_init.sql:/docker-entrypoint-initdb.d/init.sql
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.hostname==docker-worker2]  # 指定运行在docker-worker2节点
      restart_policy:
        condition: any
        delay: 5s
    networks:
      - mysql-cluster

master_init.sql脚本

-- 主库初始化脚本
-- 此脚本会在MySQL主库容器首次启动时自动执行

-- 为复制用户'repl'授予复制权限
-- REPLICATION SLAVE 权限允许该用户从主库读取二进制日志(binlog)
-- *.* 表示所有数据库的所有表
-- '%' 表示允许从任何主机连接(在生产环境中建议限制为从库IP)
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

-- 可选:刷新权限使授权立即生效
FLUSH PRIVILEGES;

-- 可选:创建用于监控或管理的其他用户
-- CREATE USER 'monitor'@'%' IDENTIFIED BY 'monitor_password';
-- GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO 'monitor'@'%';

slave_init.sql脚本

-- 从库初始化脚本
-- 此脚本会在MySQL从库容器首次启动时自动执行

-- 配置主从复制关系
CHANGE MASTER TO 
    MASTER_HOST='mysql-master',       -- 主库服务名称(Docker服务发现)
    MASTER_PORT=3306,                 -- 主库端口
    MASTER_USER='repl',               -- 复制用户名(与主库配置一致)
    MASTER_PASSWORD='123456',         -- 复制用户密码
    GET_MASTER_PUBLIC_KEY=1,          -- MySQL 8.0+的安全特性,自动获取主库公钥
    MASTER_AUTO_POSITION=1;           -- 使用GTID自动定位复制位置

-- 启动复制进程
START SLAVE;

-- 可选:查看复制状态(实际执行时可能需要注释掉,因为输出可能干扰初始化)
-- SHOW SLAVE STATUS;

-- 可选:设置从库的只读模式(已在docker-compose命令参数中设置)
-- SET GLOBAL read_only = ON;

-- 可选:配置复制过滤(如果需要)
-- CHANGE REPLICATION FILTER REPLICATE_DO_DB = (db1, db2);

3. 部署

# 1. 上传 docker-compose.yml 文件到任意目录
# 可能需要修改文件夹权限
chmod -R 777 /srv/nfs_data

# 2. 上传 mysql-cluster 目录到 NFS 共享目录。

# 3. 创建宿主机db文件挂载目录,mysql的数据库文件不需要挂载到nfs,因为固定了3个实例分别的服务器
# 创建专用数据目录
sudo mkdir -p /var/lib/data/mysql
# 设置正确权限
sudo chown -R 999:999 /var/lib/data	#mysql使用999用户
sudo chmod -R 750 /var/lib/data

# 4. 在管理节点中使用 docker stack 命令部署
docker stack up -c docker-compose.yml mysql
# -c 参数指定 mysql 服务栈的 compose 配置文件,文件路径修改为自己的目录
# 删除
docker stack rm mysql
rm -rf /var/lib/data/mysql/*

# 5. 查看状态
docker service ls
# 查看指定名称的容器堆栈
docker stack ps mysql
docker ps -a

# 6. 在MySQL主服务器查询当前的连接
SHOW PROCESSLIST
# 查看mysql复制状态
show replica status
执行  create database test_db  看看同步情况

4. 应用实现读写分离

  • 数据库代理中间件:MySQL Router 、 ProxySQL

    • 对代码没有侵入性、简单转发规则配置
    • 增加维护难度和架构复制度,占用更多资源,多一层转发,增加查询相应
  • 应用程序中使用支持读写分离ORM框架(如sqlsugar)

    • 不会增加额外的中间件,不占用而外资源,也不会增加查询响应
    • 代码侵入性,静态配置多个数据库连接字符串,无法动态伸缩
    • EF Core没有开箱即用的读写分离方案,不要用修改链接字符串的方式做读写分离。用得不好时会影响连接池

五、部署Redis集群

1. 哨兵模式的问题

  • 每台Redis服务器都存储相同的数据,浪费内存空间,容量越大越影响性能
  • 中心化的集群方案,每个从服务器和主服务器耦合度高,选主期间服务不可用
  • 只有一个Redis实例来接受和处理写请求,写操作受单机瓶颈影响

2. Cluster集群,又称分片集群

特点

  • 更适合海量数据、高并发、高可用
  • 去中心化的,分片集群至少需要2各节点,2个都是能够提供数据存储的主服务器
  • 每个分片都有自己的主从结构,各位提供实例
  • 多主多从架构,最小分片集群(三主三从)

两个问题

A:数据分片后,如何在多个实例之间分布数据,数据和实例如何对应?

  • 哈希槽:处理数据和实例之间的映射关系
  • 16384个哈希槽,全部分配到集群实例中
  • Key:映射到哈希槽,采用CRC6算法

B:客户端怎么确定要访问数据的位置(哪个实例)?

  • 客户端也可以使用CRC6算法得到key在哪个哈希槽中
  • 集群会把哈希槽点分配信息发送给客户端,并且缓存到本地
  • 实例和哈希槽对应关系会变的
    • 新增删除实例时
    • 为了负载均衡重新计算分配哈希槽
    • 哈希槽节点位置改变后,其他节点发请求到原节点时,会通知请求点新的节点位置
    • 如果是正常重新分配哈希槽,还没完成数据迁移。请求过来时只会告知正在迁移,不会缓存新的节点信息

3. docker-compose

version: '3.9'  # 使用Compose文件版本3.9

# 定义网络配置
networks:
  redis-cluster:  # 创建一个名为redis-cluster的网络,所有Redis节点将加入此网络

# 定义服务(容器)
services:
  # Redis节点1配置
  redis-node-1:
    image: redis:7.2  # 使用Redis 7.2官方镜像
    volumes:
      # 将宿主机/srv/nfs_data/redis-cluster/conf目录挂载到容器内的Redis配置目录
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-1:/data
    ports:
      # 将容器6379端口映射到宿主机6001端口
      - "6001:6379"
    command: 
      # 启动Redis服务器并指定配置文件路径
      - redis-server
      - /usr/local/etc/redis/redis.conf
      # 设置集群公告IP为服务名称(Docker内部DNS可解析)
      - --cluster-announce-ip redis-node-1
    environment:
      - TZ=Asia/Shanghai
    networks:
      # 将服务连接到redis-cluster网络
      redis-cluster:
    deploy:
      restart_policy:
        # 配置重启策略:任何情况下都尝试重启(包括正常退出)
        condition: any
        # 重启前等待5秒
        delay: 5s

  # Redis节点2配置
  redis-node-2:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-2:/data
    ports:
      - "6002:6379"
    command: 
      - redis-server
      - /usr/local/etc/redis/redis.conf
      - --cluster-announce-ip redis-node-2
    networks:
      redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s

  # Redis节点3配置
  redis-node-3:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-3:/data
    ports:
      - "6003:6379"
    command: 
      - redis-server
      - /usr/local/etc/redis/redis.conf
      - --cluster-announce-ip redis-node-3
    networks:
      redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s

  # Redis节点4配置
  redis-node-4:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-4:/data
    ports:
      - "6004:6379"
    command: 
      - redis-server
      - /usr/local/etc/redis/redis.conf
      - --cluster-announce-ip redis-node-4
    networks:
      redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s

  # Redis节点5配置
  redis-node-5:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-5:/data
    ports:
      - "6005:6379"
    command: 
      - redis-server
      - /usr/local/etc/redis/redis.conf
      - --cluster-announce-ip redis-node-5
    networks:
      redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s

  # Redis节点6配置
  redis-node-6:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-6:/data
    ports:
      - "6006:6379"
    command: 
      - redis-server
      - /usr/local/etc/redis/redis.conf
      - --cluster-announce-ip redis-node-6
    networks:
      redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s

4. conf配置

redis-7.2.conf

# 绑定的IP地址
bind 0.0.0.0
# 端口号
port 6379

# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000

# 集群通告:端口(与服务端口一致)
cluster-announce-port 6379
# 集群通告:总线端口(集群节点之间通信端口)
cluster-announce-bus-port 16379

# 数据库数量
databases 1

# 关闭保护模式
protected-mode no
# 开启AOF 持久化
appendonly yes

5. 部署

mkdir -p /srv/nfs_data/redis-cluster/data/{node-1,node-2,node-3,node-4,node-5,node-6}
chmod -R 777 /srv/nfs_data/redis-cluster/data

docker stack up -c docker-compose.yml redis
查看
docker stack ps redis
docker service logs redis_redis-node-1

  • No cluster configuration found , 没有找到集群配置
    • 因为还没有加入集群,我们需要手动加入集群
docker ps -a 
# 找到id,进入容器
docker exec -it 4ffd4f74b973 /bin/bash

redis-cli --cluster create redis-node-1:6379 redis-node-2:6379 redis-node-3:6379 redis-node-4:6379 redis-node-5:6379 redis-node-6:6379 --cluster-replicas 1

# 查看集群信息
cluster info
cluster nodes

6. 部署客户端工具

测试

set test01 张三
set test02 张三
set test03 张三
set test04 张三
set test05 张三
  • 有些会成功,有些失败
  • 实际情况是我们本机实例的能成功,其他实例的失败。这是因为,节点缓存了槽位的分布信息,但数据的定位需要客户端自己负责。
  • 它与ES不同,ES的数据定位是服务器端处理的,而redis是客户端自己负责。

部署客户端工具

version: '3.9'

networks:
  redis_redis-cluster:
    external: true
    
services:
  redisinsight:
    image: redis/redisinsight
    ports:
      - "5540:5540"
    networks:
      redis_redis-cluster:
    deploy:
      restart_policy:
        condition: any
        delay: 5s
  • docker stack up -c docker-compose-cli.yml redis-cli
  • 通过浏览器访问客户端 http://192.168.16.129:5540 访问
  • 应用程序连接集群时配置文件需要添加所有主服务器的连接信息

7. 部署外部访问

version: '3.9'

networks:
  redis-cluster:

services:
  redis-node-1:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-1:/data
    ports:
      - "6001:6379"
      - "16001:16379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6001
      - --cluster-announce-bus-port 16001
    environment:
      - TZ=Asia/Shanghai
    networks:
      redis-cluster:

  redis-node-2:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-2:/data
    ports:
      - "6002:6379"
      - "16002:16379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6002
      - --cluster-announce-bus-port 16002
    networks:
      redis-cluster:

  redis-node-3:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-3:/data
    ports:
      - "6003:6379"
      - "16003:16379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6003
      - --cluster-announce-bus-port 16003
    networks:
      redis-cluster:

  redis-node-4:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-4:/data
    ports:
      - "6004:6379"
      - "16004:16379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6004
      - --cluster-announce-bus-port 16004
    networks:
      redis-cluster:

  redis-node-5:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-5:/data
    ports:
      - "6005:6379"
      - "16005:6379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6005
      - --cluster-announce-bus-port 16005
    networks:
      redis-cluster:

  redis-node-6:
    image: redis:7.2
    volumes:
      - /srv/nfs_data/redis-cluster/conf:/usr/local/etc/redis
      - /srv/nfs_data/redis-cluster/data/node-6:/data
    ports:
      - "6006:6379"
      - "16006:16379"
    command: 
      - redis-server
      - --port 6379
      - --cluster-enabled yes
      - --cluster-config-file nodes.conf
      - --cluster-node-timeout 5000
      - --protected-mode no
      - --appendonly yes
      - --cluster-announce-ip 192.168.16.129
      - --cluster-announce-port 6006
      - --cluster-announce-bus-port 16006
    networks:
      redis-cluster:
redis-cli --cluster create 192.168.16.129:6001 192.168.16.129:6002 192.168.16.129:6003 192.168.16.129:6004 192.168.16.129:6005 192.168.16.129:6006 --cluster-replicas 1

六、搭建ES集群

1. 知识点

  • ES天生支持分布式,单台服务器也能做集群,1个主分片(默认)+1个副本分片
  • ES分片数量一旦确定就不能修改,所以ES要创建分片需要尽早配置
  • 假设分片数量是3个,最多分布在集群的3台服务器中,因此此时新增服务器是不能提高性能的
  • ES比较耗资源

2. docker-compose文件(一主一从)

version: "3.9"  # 使用 Docker Compose 版本 3.9 的语法

networks:
  es-cluster:  # Docker 网络,用于容器间通信

services:
  elasticsearch:
    image: elasticsearch:8.13.4  
    hostname: es0{{.Task.Slot}}  # 动态设置主机名,es01 或 es02(基于任务槽位)
    volumes:
      # 挂载 IK 分词器插件目录(NFS 共享存储)
      - /srv/nfs_data/es-cluster/ik/:/usr/share/elasticsearch/plugins/ik
      # 添加数据目录挂载
      - /srv/nfs_data/es-cluster/data/es0{{.Task.Slot}}:/usr/share/elasticsearch/data
    networks:
      - es-cluster  # 连接到 es-cluster 网络
    environment:
      - node.name=es0{{.Task.Slot}}  # 设置节点名称(动态)
      - discovery.seed_hosts=es01,es02  # 集群发现种子节点
      - cluster.initial_master_nodes=es01,es02  # 初始主节点候选列表
      - cluster.name=es-cluster  # 集群名称
      - network.host=0.0.0.0  # 监听所有网络接口
      - xpack.security.enabled=false  # 禁用 X-Pack 安全功能
      - bootstrap.memory_lock=true  # 锁定内存防止交换
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"  # JVM 堆内存设置,根据实际情况配置,这里配置最小/最大各512MB
    ulimits:
      memlock:
        soft: -1  # 软内存锁定限制(无限制)
        hard: -1  # 硬内存锁定限制(无限制)
    deploy:
      mode: replicated  # 部署模式为副本模式
      replicas: 2  # 部署2个副本(即2个ES节点)

  proxy:
    image: alpine:latest  # 使用 Alpine Linux 作为基础镜像
    command:
      - /bin/sh
      - -c
      - |
        # 容器启动时执行的命令:
        apk update  # 更新包索引
        apk add socat  # 安装 socat 网络工具
        # 将宿主机的9200端口流量转发到elasticsearch服务的9200端口
        socat TCP-LISTEN:9200,fork TCP:elasticsearch:9200
    ports:
      - 9200:9200  # 暴露9200端口到宿主机
    networks:
      - es-cluster  # 连接到 es-cluster 网络
  • 可以直接副本模式开2个, replicas:2
  • 代理服务为了外部访问,es不暴露端口
  • hostname: 动态生成主机名
  • 新增或删除节点,要配合修改cluster.initial_master_nodes=es01,es02

3. 部署

mkdir -p /srv/nfs_data/es-cluster/data
mkdir -p /srv/nfs_data/es-cluster/data/es01
mkdir -p /srv/nfs_data/es-cluster/data/es02
chown -R 1000:1000 /srv/nfs_data/es-cluster/data/   #es使用UID 1000运行

docker stack up -c docker-compose.yml es

# 查看日志
docker service logs es_elasticsearch

4. 创建索引

PUT appusers
{
    "settings": {
        "number_of_shards": 2,   // 设置主分片数量为2(数据将水平分割到这两个分片)
        "number_of_replicas": 1  // 每个主分片有1个副本(提供高可用性,总共会有4个分片:2主+2副本)
    },
    "mappings": {
        "properties": {
            "nickname": {
                "type": "text",                          // 字段类型为全文文本(text)
                "analyzer": "ik_max_word",               // 索引时使用ik_max_word分词器(最细粒度拆分)
                "search_analyzer": "ik_smart",           // 搜索时使用ik_smart分词器(智能分词)
                "term_vector": "with_positions_offsets"  // 存储词项向量(包含位置和偏移信息,用于高亮显示等操作)
            }
        }
    }
}

5. 查询集群状态

GET _cluster/health
GET _cat/nodes?v  

七、部署 Logstash

1. docker-compose

version: '3.9'  # 使用 Docker Compose 版本 3.9 的语法

networks:
  es_es-cluster:
    external: true  # 引用外部已存在的 Elasticsearch 集群网络
  mysql_mysql-cluster:
    external: true  # 引用外部已存在的 MySQL 集群网络

services:
  logstash:
    image: logstash:8.13.4  # 使用 Logstash 8.13.4 官方镜像(与 ES 版本保持一致)
    volumes:
      # 挂载 JDBC 驱动目录(用于数据库连接)
      - /srv/nfs_data/logstash/jdbc/:/opt/jdbc/
      # 挂载 Logstash 管道配置文件目录(包含数据处理逻辑)
      - /srv/nfs_data/logstash/pipeline/:/usr/share/logstash/pipeline/
      # 挂载 Logstash 主配置文件
      - /srv/nfs_data/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml
      # 挂载多管道配置文件(支持多数据处理流程)
      - /srv/nfs_data/logstash/config/pipelines.yml:/usr/share/logstash/config/pipelines.yml
    networks:
      - es_es-cluster  # 连接到 Elasticsearch 集群网络
      - mysql_mysql-cluster  # 连接到 MySQL 集群网络
    deploy:
      mode: replicated  # 部署模式为副本模式
      replicas: 1  # 部署1个副本(单节点运行)
  • 注意网络的设置

2. 管道配置(应位于挂载的 pipeline/ 目录中)

典型管道配置示例

# mysql-to-es.conf
input {
  jdbc {
    jdbc_driver_library => "/opt/jdbc/mysql-connector-java.jar"
    jdbc_connection_string => "jdbc:mysql://mysql_mysql-cluster:3306/database"
    # 其他JDBC参数...
  }
}
output {
  elasticsearch {
    hosts => ["es_es-cluster:9200"]
    # 其他ES输出参数...
  }
}

具体案例

input {
   jdbc {
      # 指定JDBC连接MySQL驱动程序库
      jdbc_driver_library => "/opt/jdbc/mysql-connector-j-8.2.0.jar"
      # MySQL驱动类
      jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
      # 数据库连接字符串
      jdbc_connection_string => "jdbc:mysql://mysql-slave-2:3306/XXX"
      # 数据库用户名
      jdbc_user => "root"
      # 数据库密码
      jdbc_password => "root"
      # 是否启用分页,建议启用,配合分页大小限制每次查询数据量,避免数据量过大引起超时
      jdbc_paging_enabled => "true"
      # 分页大小,根据时间间隔设置一个合理的值
      jdbc_page_size => "500"
      # 指定跟踪列,会根据这个列来查询最新数据
      tracking_column => "update_time"
      # 跟踪列类型,默认是numeric。
      tracking_column_type => "numeric"
      # 是否需要持久化跟踪列的值。当参数设置成true时,会持久化tracking_column参数所指定的列的最新的值,并在下一次管道执行时通过该列的值来判断需要更新的记录。
      use_column_value => "true"
      # 指定要运行的SQL语句,查询出来的数据会更新到es,:sql_last_value 表示记录的上次运行时跟踪列的最新值,筛选的数据区间为 update_time 大于上次时间,小于最新时间
      statement => "SELECT * FROM (
                     SELECT id,nickname,REPLACE(unix_timestamp(LastModifiedAt),'.','') as update_time
                     FROM AppUsers ) as AppUsers
                  WHERE update_time > :sql_last_value"
      # 定时运行SQL语句的时间表,采用 Cron 时间格式,这里设置为每分钟运行一次
      schedule => "* * * * *"
  }
}

output {
    elasticsearch {
        # 配置ES集群地址,logstash 同样可以运行在docker中,所以这里可以通过容器名称连接es
        hosts => ["elasticsearch:9200"]
        # 索引名称(必须小写)
        index => "appusers"
        # 数据唯一索引,一般使用主键 id
        document_id => "%{id}"
    }
   stdout {
      codec => json_lines
   }
}
  • 时间需要转换成时间搓数据

3. 部署(1个副本,不需要高可用)

docker stack up -c docker-compose.yml logstash