资讯详情

资讯详情

Docker搭建Redis集群实战:从端口映射到故障转移全攻略

1. 为什么要用 Docker 搭 Redis 集群先聊一个比较实际的问题Redis 集群这玩意儿用传统方式在一台物理机或者多台服务器上手工部署光环境准备就够折腾半天——下载 Redis、编译、改配置、逐台启动、再合成集群中间任何一步的 Redis 版本不一致、端口被占、防火墙挡了集群总线都会让后续排查变得非常痛苦。用 Docker 来跑最大的价值在于把每个节点放进了独立的容器里环境彻底隔离版本统一端口映射可控整套流程可以直接写成命令复现。尤其适合下面三种场景本机想做集群功能开发、验证槽位分配和故障转移逻辑不想污染宿主机环境需要在 CI 流程里临时拉起一套多节点集群跑集成测试用完即焚正式环境想把 Redis 集群部署在容器化平台上直接用 Docker 先验证部署方案再平移进容器编排系统。另外借助 Docker 的端口映射在一台机器上就能模拟出分布在多台服务器上的效果——每个容器映射不同的宿主机端口彼此通过网络互通。当然这里要提前打个预防针Docker 环境跑 Redis 集群最大的坑不是 Redis 本身而是网络。Redis Cluster 的节点之间有两种流量一种是客户端读写用的普通端口默认 6379另一种是节点间集群内部通信的 Cluster Bus 端口默认是普通端口加 10000也就是 16379。Docker 如果不把这两个端口同时映射出去节点之间互相发现不了集群根本创建不起来。这一点后面实操部分会反复提到。这套方案适合谁适合已经对 Redis 基本命令有一定了解、但没怎么碰过集群部署的开发者也适合准备上生产、想先用容器做一番可行性验证的运维同学。我下面的步骤都是在一台 Linux 服务器上实测过的命令直接复制就能用但我会把每一步背后的逻辑讲清楚免得你只知道敲命令、不知道出了事怎么排查。2. 搭集群前的准备工作与版本选型2.1 镜像选型和节点规划Redis 官方镜像在 Docker Hub 上长期维护选redis官方镜像就好。这里有个小建议不要随手拉 latest因为 Redis 大版本之间的集群命令和参数有差异例如 Redis 7 对集群总线地址的处理比旧版本更灵活。我自己用的是 7.x 版本命令语法和配置项相对现代也避开了老版本的一些历史包袱。集群节点规划上Redis Cluster 要求至少 3 个主节点才能形成完整的槽位分配每个主节点又可以带一个从节点。所以最标准的测试环境就是 6 个节点3 主 3 从。先想清楚端口规划这决定了后面每一条命令的参数容器角色容器内端口宿主机映射端口节点间通信node-1主6379 / 163796391 / 16391node-2主6379 / 163796392 / 16392node-3主6379 / 163796393 / 16393node-4从6379 / 163796394 / 16394node-5从6379 / 163796395 / 16395node-6从6379 / 163796396 / 16396宿主机端口故意不用 6379 等标准端口是为了避免和宿主机上已有的 Redis 实例冲突。容器内统一用 6379保持镜像默认行为少改配置少出幺蛾子。每个容器需要同时映射两个端口也就是-p 6391:6379 -p 16391:16379这样的组合前面是宿主机端口后面是容器内端口。有个很容易踩的认知误区在这里说一下很多人以为 Redis Cluster 的节点通信端口是可以单独在配置文件里指定的其实 Redis 节点启动时会根据port参数自动决定 Cluster Bus 端口是port 10000。如果你在配置里写了cluster-port那是另一码事建议保持默认否则端口映射又要跟着改容易出现映射错位。2.2 Docker 网络模式的选择搭建多容器集群第一反应是用--link或者容器的 IP 直连这两种方案我都不推荐。--link已经是 Docker 官方的过时特性在新版本里能不用就不用。直接使用docker inspect查容器 IP 再写进配置也不是不可以但容器一旦重建IP 会变集群配置里的节点信息就全废了。正确做法是创建一个自定义 bridge 网络让容器之间通过服务名互相解析。Docker 自带的 DNS 解析会自动把容器名映射成对应 IP这样即使容器重建、IP 变化服务名不变Redis 节点之间的通信配置也就不会失效。创建网络的命令很简单docker network create redis-cluster-net创建完成后启动每个容器时用--network redis-cluster-net指定容器之间就能互称服务名了。后面组建集群的时候直接写redis-node-1:6379这样的地址就行。还有一点需要提前说明如果你用的是 Docker DesktopmacOS / Windows容器里访问宿主机端口的方式和 Linux 上不太一样但那是在跨宿主机部署时才会遇到的事。本文的步骤全部是在单台 Linux 主机上完成的先把这套跑通再去考虑跨机扩展思路会更清晰。2.3 准备 Redis 配置文件每个 Redis 节点需要一份自己的配置文件哪怕内容几乎一样。我习惯在宿主机上建一个统一的目录里面按节点分出 6 个子目录每个子目录放一份redis.conf和后续需要挂载的数据目录。这么做的好处是容器删除后数据还在宿主机上想重置集群也只删目录就好。一份最精简的 cluster 模式配置文件长这样port 6379 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 appendonly yes appendfsync everysec protected-mode no dir /data逐行解释一下关键配置cluster-enabled yes这是开启集群模式的总开关不写这个后面的所有集群命令都会报错节点根本不会以集群模式启动。cluster-config-file nodes.confRedis 节点运行时自己生成的集群状态文件记录当前节点视角下整个集群的拓扑信息包括哪些节点是主、哪些是从、槽位怎么分布。这个文件不需要手动编辑Redis 自己维护。cluster-node-timeout 15000节点心跳超时阈值单位毫秒。超过这个时间没有收到某节点的 Pong 响应就判定该节点不可达触发故障转移流程。测试环境可以设短一点比如 5000生产环境建议 15000 以上避免网络抖动导致的误判。appendonly yesappendfsync everysec开启 AOF 持久化保证容器挂了重启后数据不丢太多。每秒刷盘一次是性能和可靠性之间的平衡点生产环境如果要更稳可以设成 always但写性能会明显下降。protected-mode no关闭保护模式。这是为了让集群节点可以从容器网络外部访问注意生产环境不要这么干应该通过防火墙或者容器编排的网络策略来控制访问。dir /data指定持久化文件落盘目录这个目录稍后挂载到宿主机目录。这里也顺便提一下 Redis 7 的默认配置变化如果你的 Redis 版本是 7.xdocker 镜像里默认的配置可以通过启动命令追加参数覆盖不一定非要把整个配置文件挂进去。我可以给你一个更偷懒的方案直接在docker run命令后面跟一堆--cluster-enabled yes --appendonly yes这样的参数只要不涉及特殊配置效果和配置文件一模一样。3. 核心实操容器启动、集群创建与验证3.1 启动 6 个 Redis 节点容器我按顺序逐个启动容器一步一个坑地验证。以 node-1 为例完整命令如下docker run -d \ --name redis-node-1 \ --network redis-cluster-net \ -p 6391:6379 \ -p 16391:16379 \ -v /data/redis-cluster/node-1/data:/data \ -v /data/redis-cluster/node-1/redis.conf:/usr/local/etc/redis/redis.conf \ redis:7.2 \ redis-server /usr/local/etc/redis/redis.conf这里几个细节要说清楚-v挂载了两个路径一个是配置文件的只读挂载一个是数据目录的持久化挂载。数据目录挂载非常重要否则容器一删AOF 和 RDB 文件全没想排查问题连日志都找不到。配置文件的路径我习惯放在/usr/local/etc/redis/redis.conf但这不是死的你自己定了路径、启动命令里指向同一个路径就行。启动后立刻要看一眼日志确认是否正常进入了 cluster 模式docker logs redis-node-1正常情况下日志里会出现一行类似Node configuration loaded或者Ready to accept connections的内容。如果出现No cluster configuration found也正常因为第一次启动还没有生成nodes.conf。另外 5 个容器照葫芦画瓢只需要改名字、端口、挂载路径。这里我踩过的坑是曾经图省事写了个 for 循环批量启动结果因为配置文件的挂载路径拼接出错6 个容器起来后全是同一个配置文件后面创建集群时互相不认账排查老半天。所以这里还是建议你老老实实逐条执行或者脚本里仔细检查路径生成逻辑。全部起来后用一条命令确认 6 个容器都在运行docker ps --filter nameredis-node3.2 创建 Redis Cluster旧版 Redis 是用redis-trib.rb脚本来创建集群的这个脚本依赖 Ruby 环境新版本里官方已经不再推荐取而代之的是 Redis 5.0 开始内置的redis-cli --cluster子命令。我们直接用redis-cli即可。需要进入其中一个容器执行命令因为创建集群时要连上 6 个节点的端口在宿主机上直接连映射端口也可以但有些人为了省事不映射 Cluster Bus 端口那就必须进容器连容器网络的端口。我习惯直接进容器顺便体验一下节点视角。docker exec -it redis-node-1 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-replicas 1表示每个主节点配 1 个从节点命令执行后会自动进行主从分配。它会打印出每个节点将被分配的角色并且在最终确认前问你一句Can I set the above configuration? (type yes to accept):这时输入yes回车。然后会看到每个主节点被分配到的槽位范围大致长这样 Performing hash slots allocation on 6 nodes... Master[0] - Slots 0 - 5460 Master[1] - Slots 5461 - 10922 Master[2] - Slots 10923 - 16383 Adding replica redis-node-5 to redis-node-1 Adding replica redis-node-6 to redis-node-2 Adding replica redis-node-4 to redis-node-3如果这里报错最常见的错误是[ERR] Not all 16384 slots are covered by nodes这个先不慌多半是网络问题后面排查部分会展开。3.3 验证集群状态集群创建完成后验证是必不可少的。先看整体状态docker exec -it redis-node-1 redis-cli cluster info重点看几行输出cluster_state:ok状态正常的关键标志只有这一行是 ok客户端才能正常读写。cluster_slots_assigned:16384说明全部 16384 个槽位都已经分配给了主节点。cluster_known_nodes:6当前集群拓扑里有 6 个节点。再看每个节点的角色和连接情况docker exec -it redis-node-1 redis-cli cluster nodes输出里每个节点一行内容包括节点 ID、IP:端口、角色标识master还是slave、从节点对应的主节点 ID、槽位范围等。重点关注是不是每个主节点都有对应从节点有没有节点显示disconnected。最后做个读写测试验证集群路由是否正常。连接到任意节点写入数据再换一个节点读取——注意因为集群的槽位分布在不同节点上你连到 node-1 写一个 key如果它的槽位不在 node-1 上Redis 会返回MOVED错误。正常客户端比如官方 redis-cli 的-c模式会自动处理重定向所以我们测试时要用集群模式连docker exec -it redis-node-1 redis-cli -c进去后依次执行127.0.0.1:6379 set name docker-redis-cluster OK 127.0.0.1:6379 get name docker-redis-cluster如果都能正确返回说明集群读写链路是通的。用-c模式连进去即使 key 的槽位不在当前节点客户端也会自动跳转。3.4 验证故障转移集群搭建完了还不够最好再验证一下故障转移能力不然不知道从节点能不能自动顶上。模拟方法很简单把某个主节点的容器停掉。比如停掉 redis-node-1 这个容器docker stop redis-node-1等十几秒具体时间取决于配置的cluster-node-timeout再看集群节点状态docker exec -it redis-node-2 redis-cli cluster nodes正常情况下会发现原先的从节点已经变成了主节点接替了故障节点的槽位。再把原来的 node-1 重新启动docker start redis-node-1启动后 Redis 会发现集群里已经有一个新主节点接管了自己的槽位它会自动以从节点的身份加入集群完成故障恢复。这一套流程在测试环境跑通后后续上生产才敢放心。4. 实操过程中的网络与配置难题4.1 端口映射不全会导致集群创建失败这是我在 Docker 下搭建 Redis 集群踩过最深的一个坑值得单独拿出来说。第一次搭建时我只映射了6379端口心想反正是同一个宿主机上的容器通过自定义网络应该能直接访问彼此的 6379。结果redis-cli --cluster create执行到一半就报错[ERR] Node redis-node-2:6379 is not empty或者偶发出现节点联系不上、集群创建被中止。查了很久才发现问题根源Redis Cluster 的节点在启动后会立即开始向其他节点发送 cluster bus 消息而这个消息默认走的是port 10000端口。在 Docker 网络内部容器的 16379 端口确实开着但如果宿主机没有把这个端口映射出来从容器外部或者通过某些网络配置访问就会碰壁。而且更迷惑的是有些情况下集群创建的第一步会因为部分端口能通而显得一切正常等到槽位分配时才彻底暴露。解决方式很简单就是我在前文反复强调的每个容器都要映射两个端口7000 系列和 17000 系列同时映射。所以如果你复制了我的命令建议回头检查一遍自己的docker run里是不是两个-p都写了。4.2 节点网络地址与容器 IP 的不可靠性另一个非常常见的坑是在创建集群时用了127.0.0.1:6379这样的地址来指定节点。如果你是在宿主机上执行redis-cli --cluster create连到映射端口集群会记录下 127.0.0.1 作为节点地址并被广播给其他节点。其他节点尝试连接 127.0.0.1 时连到的是它们自己的容器内部而不是宿主机结果就是各节点间互相联系不上。正确的做法是进到容器里用服务名来指定节点地址例如redis-node-1:6379。这样集群拓扑里记录的是容器网络内的地址节点之间可以直接路由。如果必须在宿主机上执行创建命令也有一种办法每个容器启动时额外配置--network-alias或者用extra_hosts把宿主机 IP 映射进去但复杂度高了一截不建议新手折腾。这个坑还会在容器重建后二次爆发如果某个容器因为异常被删除了重新创建得到的 IP 可能变了而集群状态文件里的节点地址还是旧 IP。这时候轻则该节点联系不上重则整个集群处于 fail 状态。处理思路后面会在常见问题里详解。4.3 容器重启后的集群恢复问题如果只是docker stop/docker start容器 IP 不会变集群能正常恢复。但如果docker rm之后重新docker run哪怕配置和数据目录都一样容器 IP 大概率会变集群拓扑信息就陈旧了。举例来说某个节点的nodes.conf文件里记录的是旧 IP但其他节点还在用新 IP 和它通信彼此握手失败。这种情况的排查特征很典型cluster nodes里该节点显示为disconnectedcluster info显示cluster_state:fail。处理办法有两种第一种如果集群已经整体失败最干净的办法是清空数据目录重新创建集群——这个只适合测试环境。第二种手工修正节点信息。操作思路是进入其他健康节点用cluster forget删除故障节点的信息然后重新加回。但这要求新节点的 IP 已经能被其他节点访问到且nodes.conf里的地址能通过cluster meet重新注册。操作细节多容易出错生产环境不建议这么手搓应该让编排平台保证容器 IP 稳定比如使用有状态工作负载的固定网络标识或者直接依赖服务名和稳定的解析机制。4.4 关于密码的坑如果你给 Redis 配置了requirepass集群模式下还必须在所有节点同时设置masterauth否则主从复制和故障转移时会因为无法认证而失败。具体来说requirepass控制客户端访问需要密码masterauth控制当前节点连接其他主节点或从节点进行同步时使用的密码。集群模式下这两个参数必须同时配置且所有节点的密码必须一致。很多人只设了requirepass结果从节点同步数据时一直报MASTER - REPLICA sync failed: NOAUTH Authentication required。对应的docker run启动参数示例docker run -d ... \ redis:7.2 \ redis-server /usr/local/etc/redis/redis.conf \ --requirepass yourpassword \ --masterauth yourpassword配置完成后用redis-cli -c连接时也得带上密码docker exec -it redis-node-1 redis-cli -c -a yourpassword顺便提醒一句-a参数明文传密码会留在 shell 历史里测试环境无所谓生产环境应该用REDISCLI_AUTH环境变量。5. 配置检查单与故障速查表5.1 最小配置检查单在启动节点前建议对照下面这份检查单逐项确认能省掉后面 80% 的排查时间镜像版本所有节点使用同一个 Redis 镜像版本不要一个 6.x 一个 7.x网络所有容器加入同一个自定义 network端口映射同时覆盖 6379 和 16379配置文件cluster-enabled yes已开启dir指向挂载的数据目录数据持久化每个节点有独立的宿主机数据目录禁止多节点共享同一个数据目录密码配置如果设置requirepass必须同步设置masterauth且全集群一致防火墙宿主机防火墙放行映射出来的端口范围尤其是集群总线端口。5.2 故障速查表现象可能原因排查/解决办法创建集群时报Node is not empty节点数据目录里有旧的 RDB/AOF 或 nodes.conf 残留确认目录为空或清空后重启容器再创建生产环境不要随意清空创建集群时报Could not connect to ...端口映射缺失、防火墙拦截、地址写错检查两个端口是否都映射改用容器服务名而非 127.0.0.1cluster info显示cluster_state:fail槽位缺失或主节点不可达用cluster nodes查看具体节点状态确认是否需要故障转移或恢复节点从节点一直显示disconnected容器 IP 变化、nodes.conf里地址失效检查节点间能否 ping 通必要时重启容器让节点重新握手主从同步报NOAUTH缺少masterauth配置或密码不一致为所有节点补上masterauth重启容器生效客户端写入报MOVED客户端未使用集群模式使用redis-cli -c或集成支持集群协议的客户端这张表并没法覆盖所有场景但足以应付测试环境的绝大多数异常。6. 生产环境部署的几个延伸思考测试环境的 Docker 集群跑通之后不少人会直接想把同样一套方案放到生产环境。这里我基于自己的实践多补充几个延展点。6.1 容器与持久化生产环境的 Redis 容器数据目录要使用持久化存储否则容器一旦重建数据全部丢失。在单机 Docker 上可以用-v挂载宿主机目录在多节点编排上要使用持久化卷插件确保数据不跟容器生命周期绑定。即使使用了持久化卷容器的 IP 可能还是会变因此不要用 IP 作为集群节点的唯一标识而是依赖稳定的域名或服务发现机制。Redis 7 做了不少改进但底层依然是 IP 和端口组成的节点地址部署上需要格外注意。6.2 跨宿主机部署与 Cluster Bus 端口如果要在多台物理机上部署 Docker 集群端口映射之外还要考虑跨机网络互通。几个容器分散在不同宿主机上它们之间的 Cluster Bus 消息要能互相到达。最简单的做法是使用宿主机网络模式--network host但这样就没有端口隔离了。相对复杂的做法是使用 Overlay 网络配合编排平台统一管理。无论哪种方案都要保证集群总线端口对目标节点可达。6.3 监控与告警容器化集群的一大问题是节点故障转移后外部感知滞后。建议在节点上起 Redis Exporter定时采集cluster_info指标特别是cluster_state。当集群状态从 ok 变成 fail立刻触发告警。不然等到客户端报错才发现集群挂了故障时间已经拉得很长。6.4 备份与恢复就算有集群备份依然重要。Redis 集群的备份不是只备份一个节点而是要备份所有节点的 RDB/AOF 文件恢复时保证槽位分布一致。更推荐的是使用 Redis 官方或者生态里的备份工具定期把数据导出到外部的存储避免集群整体崩溃后的全部丢失。7. 我的一点实际体会整套流程走下来我最深的感受是Docker 让 Redis 集群的搭建从“手工部署服务器”变成“编排一组容器”极大降低了环境层面的复杂度但并没有降低对网络原理的要求。反而因为网络层多加了一层你反而更要搞清楚 Redis 节点之间是怎么通信的Cluster Bus 端口从哪来到哪去容器 IP 和宿主机端口映射之间是什么关系。如果只从这篇内容里记住一句话那就是先想清楚网络再启动容器。端口映射缺一个、地址写错一个后面所有操作都会给你颜色看。但如果这些网络层面的坑都填平了Docker 里的 Redis 集群就是一套可以被反复拉起和销毁的测试利器对日常开发和排障的帮助非常大。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →