Featured image of post 双机房双集群架构:Redis Cluster 6 节点 + 多服务同步

双机房双集群架构:Redis Cluster 6 节点 + 多服务同步

同城双机房 K8s 集群、Redis Cluster 6 节点跨机房部署、nacos/mysql/mqtt/influxdb 多服务双活

双机房双集群是什么

同城双机房(Active-Active)是国内中大型生产环境的标配——一个机房挂了,另一个机房能继续服务。K8s 时代实现双机房有几个关键挑战:

  • 集群独立:两个 K8s 集群,apiserver / etcd 都独立
  • 服务双活:nacos / mysql / mqtt / redis 在两边都部署
  • 数据同步:MySQL binlog、MinIO rsync、Loki/Grafana 数据可选同步
  • VIP + 防火墙:公网域名 + 内网 VIP 飘移

适用版本:K8s 1.28 / Redis 6.2 / EMQX 5.5.1 / 双机房(同城)


1. 整体架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                        公网用户
                  ┌─────────▼─────────┐
                  │   DNS / 防火墙     │
                  │   (双 VIP 映射)    │
                  └──┬──────────────┬──┘
                     │              │
            ┌────────▼───┐    ┌────▼───────┐
            │  机房 1    │    │  机房 2    │
            │  10.8.33.x │    │  10.8.35.x │
            │ ┌────────┐ │    │ ┌────────┐ │
            │ │ K8s    │ │    │ │ K8s    │ │
            │ │ cluster│ │    │ │ cluster│ │
            │ └────────┘ │    │ └────────┘ │
            │ ┌────────┐ │    │ ┌────────┐ │
            │ │ nacos  │◄┼────┼►│ nacos  │ │
            │ │ mysql  │◄┼────┼►│ mysql  │ │
            │ │ redis  │◄┼────┼►│ redis  │ │
            │ │ minio  │◄┼────┼►│ minio  │ │
            │ └────────┘ │    │ └────────┘ │
            └────────────┘    └────────────┘
                     ▲              ▲
                     └────── 双向同步 ──────┘

2. Redis Cluster 6 节点跨机房

2.1 为什么是 6 节点

Redis Cluster 用 hash slot(16384 个槽位)分片。3 master + 3 slave 是最稳的:

  • 3 master:分别负责 0-5460 / 5461-10922 / 10923-16383
  • 3 slave:每个 master 1 个,热备
  • 任意一个 master 挂了,对应 slave 自动升级

跨机房时——机房 1 3 节点(1 master + 1 master + 1 master),机房 2 3 节点(3 slave)。这样机房 1 整体挂了,机房 2 还在(但只有 slave,新写会失败)。

生产更稳的方案:机房 1 2 master + 1 slave,机房 2 1 master + 2 slave,跨机房分布副本——但 6 节点就够覆盖大部分场景。

2.2 节点规划

机房IP端口角色存储
机房 110.8.33.67360master 0/home/docker/redis-cluster
机房 110.8.33.77370master 1/home/docker/redis-cluster
机房 110.8.33.87380master 2/home/docker/redis-cluster
机房 210.8.35.57550slave of 7380/home/docker/redis-cluster
机房 210.8.35.67560slave of 7360/home/docker/redis-cluster
机房 210.8.35.77570slave of 7370/home/docker/redis-cluster

实际 IP 用占位符 10.8.33.x 10.8.35.x 替代。

2.3 镜像准备

1
2
3
docker pull redis:6.2.17
docker tag redis:6.2.17 <harbor>:13001/base/redis:6.2.17
docker push <harbor>:13001/base/redis:6.2.17

2.4 6 个节点的 redis.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cat > /home/docker/redis-cluster/redis.conf << "EOF"
port 7360                              # 每节点不同
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly no
save ""
daemonize no
protected-mode no
pidfile /data/redis.pid
EOF

2.5 6 个节点的 docker run

1
2
3
4
5
6
7
8
docker run -d \
  --restart=always \
  --privileged=true \
  --name redis-7360 \
  --net host \
  -v /home/docker/redis-cluster:/data \
  <harbor>:13001/base/redis:6.2.17 \
  redis-server /data/redis.conf

6 节点命令相同,只改 --nameport

2.6 创建集群

1
2
3
4
5
6
7
8
mkdir -p /home/docker/redis-cluster
# 6 节点都要建这个目录

docker exec -it redis-7360 \
  redis-cli -p 7360 --cluster create \
  10.8.33.6:7360 10.8.33.7:7370 10.8.33.8:7380 \
  10.8.35.5:7550 10.8.35.6:7560 10.8.35.7:7570 \
  --cluster-replicas 1

输出(关键部分):

1
2
3
4
5
6
7
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.8.35.6:7560 to 10.8.33.6:7360
Adding replica 10.8.35.7:7570 to 10.8.33.7:7370
Adding replica 10.8.35.5:7550 to 10.8.33.8:7380

确认 yes 后:

1
2
3
4
5
>>> Performing Cluster Check (using node 10.8.33.6:7360)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

2.7 验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
docker exec -it redis-7360 redis-cli -p 7360 -c

# 集群信息
127.0.0.1:7360> cluster info
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_known_nodes:6
# cluster_size:3

# 节点列表
127.0.0.1:7360> cluster nodes
# 02f0476e155902b1eda400ee84602ace90e821cd 10.8.35.5:7550@17550 slave 952938798efe17832a3c9492be9a45675c58fe69 ...

2.8 Spring Boot 客户端配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
spring:
  redis:
    timeout: 10s
    cluster:
      nodes:
        - 10.8.33.6:7360
        - 10.8.33.7:7370
        - 10.8.33.8:7380
        - 10.8.35.5:7550
        - 10.8.35.6:7560
        - 10.8.35.7:7570
      max-redirects: 3
    jedis:
      pool:
        max-active: 500
        max-wait: -1ms
        max-idle: 200
        min-idle: 100

2.9 单 db0 的坑

Redis Cluster 只支持 db0——所有 key 都在 db0。如果你的应用假设"按 db 切分数据"(如 db:4),需要重构。


3. nacos 双活

nacos 双机房同时部署,应用启动时配两个 server:

1
spring.cloud.nacos.discovery.server-addr=10.8.33.6:8848,10.8.35.5:8848

每个机房都跑 nacos,互相不依赖——一个挂掉另一个继续工作。

机房对外地址部署方式账户
机房 1http://10.8.33.250:30680/nacosk8snacos/nacos
机房 2http://10.8.35.250:30680/nacosk8snacos/nacos

4. MySQL 双主架构

双机房 MySQL 同步有 2 种思路:

4.1 双主互相同步

1
2
3
机房1-Master1 ────→ 机房2-Master2
  ↑                   │
  └───────────────────┘

每个机房都是 Master,写自己 + 同步到对方。风险:双写冲突、循环同步。

解决:每张表用 server_id 区分,比如 ID 奇数写机房 1,偶数写机房 2。

4.2 主从单向同步

1
机房1-Master ────→ 机房2-Slave

机房 1 是主,机房 2 是从,只读不写。机房 1 挂掉时人工切主。

4.3 多租户 MySQL 部署

1
2
3
4
5
volumes:
  - name: mysql-data
    nfs:
      server: 10.8.33.111
      path: /nfs/park-prod/mysql/data
1
2
3
4
5
volumes:
  - name: mysql-data
    nfs:
      server: 10.8.33.111
      path: /nfs/safety-dev/mysql/data

/etc/mysql/conf.d 走 ConfigMap 注入。

机房用途部署方式
机房 1 10.8.33.8:3309多租户库 + powerjob + nacos二进制
机房 2 10.8.35.6:3309多租户库二进制
机房 2 10.8.35.250:30606powerjob + nacosk8s

5. EMQX MQTT 集群

5.5.1 用 static 集群发现策略,所有节点都列在 seeds 里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: "3"
services:
  emqx1:
    image: emqx:5.5.1
    container_name: emqx1
    environment:
      - EMQX_NODE_NAME=emqx@10.8.33.6
      - EMQX_CLUSTER__DISCOVERY_STRATEGY=static
      - EMQX_CLUSTER__STATIC__SEEDS=[emqx@10.8.33.6,emqx@10.8.33.7,emqx@10.8.33.8,emqx@10.8.35.5,emqx@10.8.35.6,emqx@10.8.35.7]
    network_mode: "host"
    healthcheck:
      test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
      interval: 5s
      timeout: 25s
      retries: 5

5.1 客户端配置

1
2
3
4
客户端账号: <your-org> / {{MQTT_PASSWORD}}
机房 1 节点: 10.8.33.6, 10.8.33.7, 10.8.33.8
机房 2 节点: 10.8.35.5, 10.8.35.6(不可用), 10.8.35.7
公网入口: tcp://mq.cloud.example.com:1885

防火墙映射:公网 1885 → 10.8.33.110:1883 → 10.8.33.6:1883

MQTT 密码用占位符 {{MQTT_PASSWORD}} 替代。

5.2 客户端连接示例

1
2
3
4
5
6
7
8
9
# 加入集群
docker exec -it emqx1 emqx ctl cluster join emqx@10.8.35.5

# 看集群状态
docker exec -it emqx1 emqx ctl cluster status

# 重启
docker-compose -f /home/docker-compose-emqx.yml down
docker-compose -f /home/docker-compose-emqx.yml up -d

6. nginxwebui 负载均衡

服务协议策略负载备注
mqtt-wshttpip6 节点 8083前端订阅
mqtt-tcptcp-6 节点 1883后端订阅
gatewayhttpip双 VIP:30697网关
location-nettytcp-双 VIP:30687定位基站
minioapihttpip10.8.33.254:9091文件服务
safety-pchttpip双 VIP:30618PC 入口
safety-apph5httpip双 VIP:30628APP 入口
webrtc-stuntcp/udp-10.8.33.127:3478 + 10.8.35.7:3478STUN
webrtchttp-10.8.33.127:8000 + 10.8.35.7:8000虚拟 ip:443

7. 防火墙映射

公网到机房 1:

1
公网 <your-public-ip>:1885 → 10.8.33.110:1883 → 10.8.33.6:1883

公网到机房 2:

1
公网 <your-public-ip>:xxxx → 10.8.35.2:xxxx → 机房 2 服务

实际 IP 用占位符替代。

机房 2 防火墙登录:https://10.8.35.2,admin / {{FIREWALL_PASSWORD}}


8. 同步策略

资源同步方式
nacos 配置nacos 自身多节点 + 应用端多 server-addr
MySQL 数据binlog 主从 / 双主 + server_id 区分
Redis 数据Cluster 自动跨节点同步
MinIO 文件rsync + lsyncd 双向(已有方案)
MQTT 消息EMQX 集群内自动同步
应用镜像都从同一个 harbor 私有仓库拉
Prometheus 数据可选 federation / remote_write
Loki 日志可选都指向同一个对象存储

9. VIP 与 keepalived 跨机房

unicast_src_ip + unicast_peer 模式(见 2015-02-15 文章)。

机房 1:

1
2
3
4
unicast_src_ip 10.8.33.253
unicast_peer {
   10.8.35.7
}

机房 2:

1
2
3
4
unicast_src_ip 10.8.35.7
unicast_peer {
   10.8.33.253
}

不同机房 VIP 飘移。


10. 排错

现象原因解决
Redis Cluster CLUSTERDOWN跨机房链路断检查防火墙 / 路由
MySQL 双主冲突两个机房同时写改 server_id 区分写
nacos 注册的服务没同步客户端没配双 server-addr应用配置加 , 分隔
EMQX 节点反复脱离集群网络抖动调大 cluster.node_timeout
公网 502keepalived 没漂看 nginx / keepalived 日志
机房 1 全挂后机房 2 写不了MySQL 单向同步改双向 / 切主

11. 小结

同城双机房 K8s 集群的核心是"两边都能独立工作":

  1. Redis Cluster 6 节点 跨机房分布是基础
  2. nacos / MySQL / EMQX 都做双活
  3. MinIO 用 rsync 双向同步
  4. VIP + 防火墙 让公网无感
  5. 故障演练——定期模拟机房 1 挂掉

这是 K8s 系列博客的最后一篇——到此生产 K8s 集群从 0 到异地多活的完整链路已经走通。

使用 Hugo 构建
主题 StackJimmy 设计