资讯详情

资讯详情

Docker部署Redis Cluster实战:从零搭建高可用分布式缓存集群

做这套东西之前我其实已经在本地用裸机部署过好几次Redis Cluster每次都要处理redis.conf、节点握手、槽位分配一整套下来至少折腾两个小时。后来接了个模拟项目X要求把缓存集群环境快速交付给测试团队而且最好能一键重建我就索性迁移到了Docker方案选了当时稳定的Redis 7.0.7版本。这篇文章就是把我实际踩过的坑、验证过的命令和最终的安装流程完整记录下来给同样想用Docker搭Redis Cluster的读者一个可以直接抄作业的参考。如果你的目标是学会在Docker里搭建一个高可用的Redis Cluster集群那么这套流程包含了网络规划、容器化配置、集群初始化、持久化和问题排查基本覆盖了从零到可用的所有环节。整个方案不依赖某个特定的云平台本机和任意Linux服务器都能跑。我会尽量把每一条命令背后的原因讲清楚避免你照着敲完却不知道为什么能通。1. 环境准备与容器化选型1.1 为什么选择Docker部署Redis Cluster我刚开始学Redis Cluster时习惯在网上看到教程后直接用yum install redis装好三个实例手动改配置文件再用redis-cli --cluster create组集群。这种方式的弊端很暴露多个Redis实例的配置文件分散在宿主机不同目录端口也不统一一旦服务器IP变了集群重建成本很高。Docker方案的优势在于每个Redis实例都封装成独立的容器端口映射、数据存储和配置都集中在同一个目录下清理和重建只需几秒。还有一点很关键Redis Cluster要求每个节点必须能通过TCP与所有其他节点通信尤其是集群总线端口。用Docker时如果只映射了6379端口而遗漏了16379集群会反复握手失败。这一点在裸机部署时不容易踩坑但在Docker里非常常见后面会重点说明。1.2 镜像版本与基础环境本次选用的镜像为redis:7.0.7这是官方镜像不是某个第三方封装的带集群插件的版本。选择官方镜像的原因是安全可控、更新及时而且Redis内置的cluster功能已经足够不需要额外插件。宿主机操作系统我使用的是某Linux发行版的稳定版本内核版本在4.19以上。如果你用的是低版本内核某些网络特性可能不支持建议先升级。还需要确认宿主机已经安装好DockerDocker版本不低于23.0因为旧版本对网络命令的支持不够完整。安装前先做一个简单检查docker --version docker compose version输出正常就可以继续。最好确保宿主机没有占用6379到6384这些端口以及对应的集群总线端口16379到16384。如果端口被占用后续容器启动会报错。1.3 集群拓扑规划我们计划部署一个3主3从的集群共6个Redis节点端口规划如下容器角色端口集群总线端口数据目录master-1637916379/data/redis-cluster/6379master-2638016380/data/redis-cluster/6380master-3638116381/data/redis-cluster/6381slave-1638216382/data/redis-cluster/6382slave-2638316383/data/redis-cluster/6383slave-3638416384/data/redis-cluster/6384这里的主从关系不会在容器创建时指定而是在集群初始化时由Redis自动分配。我们只需要提供6个对等的节点然后让集群自己决定谁是主谁是从。这样的好处是不会让某个主节点的从节点和它落在同一台宿主机上容灾效果更好。2. 核心原理与配置文件准备2.1 Redis Cluster的工作模式简述Redis Cluster把数据分成16384个槽位每个节点负责一部分槽位。写入数据时客户端通过CRC16(key) % 16384计算出该key应该落在哪个槽位再由槽位到节点的映射关系找到对应节点。这里的核心点在于槽位的分配是集群级别的全局状态必须保证所有节点看到的信息一致。所以在Docker方案中必须让每个节点能通过一个固定的IP和端口被其他节点访问到。这里有一个容易混淆的坑Docker的bridge网络默认会给容器分配随机IP容器重建后IP会变化但集群节点之间记录的是启动时的IP。解决办法是创建自定义网络用--ip显式指定每个容器的IP并在Redis配置里使用这些固定IP。2.2 关键的redis.conf配置项我使用了一个公共的配置模板文件然后在创建容器时通过-v挂载到容器内。配置内容如下# 基础配置 bind 0.0.0.0 protected-mode no port 6379 daemonize no dir /data appendonly yes appendfilename appendonly.aof appendfsync everysec # 集群配置 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 cluster-announce-ip 172.28.0.2 cluster-announce-port 6379 cluster-announce-bus-port 16379每个节点需要将cluster-announce-ip改成自己的固定IPcluster-announce-port和cluster-announce-bus-port改成对应端口。为什么需要这三个cluster-announce参数因为容器有自己的网络命名空间宿主机上的端口映射可能导致节点之间记录的IP和端口不可达。通过显式声明对外地址可以让节点间通信和客户端重定向都指向正确的端点。2.3 集群网络规划与IP分配我们使用的Docker网络子网是172.28.0.0/16网关为172.28.0.1。实际分配如下容器名IPredis-master-1172.28.0.2redis-master-2172.28.0.3redis-master-3172.28.0.4redis-slave-1172.28.0.5redis-slave-2172.28.0.6redis-slave-3172.28.0.7固定IP非常关键。如果你的集群只跑在单个宿主机上也可以直接用容器名解析但固定IP的方案在后续扩展、迁移和诊断时会更直观。我曾经试过使用docker run --network redis-cluster-net --name redis-master-1而不指定IP结果容器重启后IP变化集群直接进入fail状态排查了很久才定位到问题。3. 一步一步用Docker启动Redis节点3.1 创建Docker网络在启动容器前先创建一个自定义的bridge网络docker network create --subnet172.28.0.0/16 redis-cluster-net创建网络时如果你不指定--subnetDocker会自动选择一个网段但固定IP配置就没法保证。自定义网段后我们才能在docker run命令中使用--ip参数。检查网络是否创建成功docker network ls | grep redis-cluster-net一旦网络存在就可以创建所有Redis实例。如果之前有残留节点建议先清理干净docker rm -f $(docker ps -aq --filter nameredis)这一步很容易被忽略。我之前遇到的最常见问题就是端口占用或节点名称冲突导致新容器起不来。清理时注意只清理咱们自己计划中使用的名字别误删其他正在运行的容器。3.2 创建6个Redis实例接下来就是启动容器。我先展示第一个master节点的完整命令其余节点只需修改IP、端口、目录和容器名。docker run -d \ --name redis-master-1 \ --network redis-cluster-net \ --ip 172.28.0.2 \ -p 6379:6379 \ -p 16379:16379 \ -v /data/redis-cluster/6379/data:/data \ -v /data/redis-cluster/6379/redis.conf:/etc/redis/redis.conf \ redis:7.0.7 \ redis-server /etc/redis/redis.conf这里面每个参数都有讲究--network redis-cluster-net把容器加入我们创建的网络保证节点之间能互相通信。--ip 172.28.0.2给容器指定固定IP。-p 6379:6379把容器内的Redis端口映射到宿主机方便宿主机和外部客户端连接。-p 16379:16379集群总线端口映射这是Redis节点之间用于检测故障、同步状态的端口。-v挂载数据目录和配置文件。其余5个节点可以按同样的模板快速创建我在这里列出它们的相同参数方便你复制时对照# master-2 docker run -d \ --name redis-master-2 \ --network redis-cluster-net \ --ip 172.28.0.3 \ -p 6380:6379 \ -p 16380:16379 \ -v /data/redis-cluster/6380/data:/data \ -v /data/redis-cluster/6380/redis.conf:/etc/redis/redis.conf \ redis:7.0.7 \ redis-server /etc/redis/redis.conf同样的方式启动redis-master-3、redis-slave-1、redis-slave-2、redis-slave-3。注意把端口映射中的宿主端口分别设为6381-6384集群总线端口设为16381-16384挂载目录也改成对应端口目录。实际生产环境中如果Redis节点要对外提供服务宿主机防火墙需要放行这些端口。我一般会先关掉防火墙或者放行全部自定义端口等集群调通后再按需收紧策略。3.3 检查容器状态所有容器创建完毕后使用以下命令检查docker ps --format table {{.Names}}\t{{.Status}}\t{{.Ports}}正常状态下6个容器都是Up状态。然后将所有运行的容器IP确认一遍docker inspect -f {{.Name}} - {{.NetworkSettings.Networks.redis-cluster-net.IPAddress}} $(docker ps -q --filter nameredis)得到的IP应该和我们规划的一致。如果发现某个节点IP不是固定IP大概率是命令中漏了--ip参数或者子网不匹配需要重建容器。4. 初始化集群与验证读写4.1 创建集群当容器都运行起来后进入任意一个Redis容器执行集群初始化命令docker exec -it redis-master-1 redis-cli --cluster create \ 172.28.0.2:6379 172.28.0.3:6379 172.28.0.4:6379 \ 172.28.0.5:6379 172.28.0.6:6379 172.28.0.7:6379 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点配1个从节点。Redis会自动分配槽位。执行过程中会提示你确认分配方案输入yes即可。需要注意这里使用的是容器内网IP172.28.0.x不是宿主机的127.0.0.1。如果你用127.0.0.1集群会尝试通过宿主机回环地址通信但其他容器无法访问必然失败。这是新手最容易犯的错误之一。4.2 验证集群状态创建完成后进入任意节点查看集群信息docker exec -it redis-master-1 redis-cli --cluster info 172.28.0.2:6379输出会显示集群状态、槽位分配情况和节点数量。如果看到Cluster state:ok说明集群正常。也可以用cluster nodes查看每个节点的角色和IPdocker exec -it redis-master-1 redis-cli -h 172.28.0.2 -p 6379 cluster nodes你会看到类似这样的一行行节点信息节点ID、IP:端口、角色、master节点ID等。确认主从关系合理后可以做一些读写测试。4.3 测试集群读写直接使用redis-cli往一个key写入数据因为集群槽位在不同的主节点上需要使用-c参数开启集群模式客户端会自动处理重定向docker exec -it redis-master-1 redis-cli -h 172.28.0.2 -p 6379 -c进入交互界面后执行set city beijing get city再设置一个不同的key比如set country china你会发现命令执行后Redis会返回类似“Redirected to slot ... located at 172.28.0.3:6379”的信息。这说明集群已经正确路由。到这里Redis Cluster在Docker中的安装和初始化已经完成。但一个真正能上线的集群还需要考虑持久化恢复、重启自启和常见故障处理。下面继续分享我实践过程中比较关键的部分。5. 持久化、重启策略与常见问题排查5.1 数据持久化与AOF配置在上面的配置中我已经打开了appendonly yes。这意味着每个节点会持续将写操作追加到AOF文件里。Docker容器的数据目录通过-v挂载到宿主机所以即使容器被删除重建只要挂载目录还在数据就不会丢失。建议定期备份AOF文件比如用redis-cli BGSAVE生成RDB快照。脚本化的备份可以这样写for port in 6379 6380 6381 6382 6383 6384; do docker exec redis-master-1 redis-cli -p $port BGSAVE done注意redis-master-1这个容器里能不能访问其他端口不太现实上面的命令只是示意实际上备份每个节点的数据需要进入对应容器。更简单的方式是直接拷贝挂载目录中的AOF文件。我的经验是重要数据还是依赖RDB和AOF双保险AOF文件可以通过config rewrite触发一次重写。另外在redis.conf中我设置了dir /data所以AOF文件和RDB文件都会写到容器内的/data目录也就是宿主机挂载出来的目录。如果要调整持久化频率可以把appendfsync从everysec改成always但性能会明显下降建议根据业务容忍度权衡。5.2 容器的重启策略Docker容器在宿主机重启后不会自动启动除非设置了--restart策略。我在启动命令中其实漏掉了这个参数所以实际线上使用时建议全部加上--restartunless-stopped。这个参数的含义是只要不是手动停止容器Docker就会在守护进程启动或容器退出时自动拉起它。例如重建一个节点时可以这样追加--restart unless-stopped如果你已经创建了容器可以通过docker update快速修改docker update --restart unless-stopped $(docker ps -q --filter nameredis)这样宿主机重启后Redis集群可以自动恢复只要挂载目录和IP配置没有丢失。5.3 常见问题一容器之间网络不通在创建集群或客户端连接时如果报错提示节点无法通信首先要检查容器网络docker exec -it redis-master-1 ping 172.28.0.3如果ping不通说明两个容器不在同一个网络里或者网络配置异常。可以检查网络连接docker network inspect redis-cluster-net准确来说你应该看到6个容器都连接着这个网络。如果某个容器不在用docker network connect redis-cluster-net 容器名把它连进来。还有一种情况是容器虽然在同一网络但Redis的cluster-announce-ip还是旧IP导致其他节点访问不到。这种问题通过查看cluster nodes就能判断节点信息中显示的IP和端口必须是当前容器的实际IP和端口。5.4 常见问题二集群总线端口未映射创建集群时如果提示类似“Waiting for the cluster to join”并且卡了很久大概率是集群总线端口不通。Redis Cluster节点之间除了普通6379端口还会用16379端口通信。在Docker中如果不把16379端口映射到宿主机或暴露到网络节点就收不到对端的心跳包。我的排查方式是在某个节点容器里执行docker exec -it redis-master-1 redis-cli -h 172.28.0.2 -p 6379 cluster nodes如果节点状态一直是handshake或者fail那么基本可以确定总线端口有问题。对于跨主机的Docker集群尤其要注意cluster-announce-bus-port必须设置为容器外可达的端口。比如上面配置中我将cluster-announce-bus-port设为16379同时宿主机映射了16379端口。这样节点之间才能顺利通信。单机Docker环境下如果用的是自定义网络映射之后一般就没问题。5.5 常见问题三客户端连接并拿到不正确的重定向地址如果客户端从宿主机连接某个映射端口比如redis-cli -p 6379当写入一个不在该节点槽位的key时Redis会返回一个IP和端口例如172.28.0.3:6379。这个地址是容器内网地址宿主机客户端无法直接访问因为它不在容器网络中。解决方法是在客户端容器里连接或者把宿主机也加入Docker网络又或者直接用-h 127.0.0.1 -p 6380来访问映射后的端口。更好的方案是使用支持Redis Cluster的客户端库比如redis-py中的RedisCluster它会根据集群拓扑自行处理重定向。我个人的习惯是将宿主机加入redis-cluster-net网络这样宿主机可以直接访问容器内网IPdocker network connect redis-cluster-net host_name执行后宿主机就能通过172.28.0.x访问所有Redis节点。不过Docker容器跨主机访问时这种方案就不适用了需要使用其他服务发现机制。5.6 常见问题四AOF文件权限和目录权限导致Redis启动失败如果挂载目录的属主不是redis用户容器内的Redis会因为没有权限写入AOF文件而启动失败。我在第一次重启时遇到过这个错误提示Cant open the append-only file: Permission denied排查后发现是宿主机目录属主为root而容器内Redis以redis用户运行。最简单的解决方法是给宿主机挂载目录开放写权限chmod -R 777 /data/redis-cluster或者更严谨一点找到容器内Redis的用户ID然后用chown调整属主。官方redis容器默认用户通常是redis用户ID一般为999。可以这样设置chown -R 999:999 /data/redis-cluster这个方法在宿主机和容器用户ID冲突时需要注意。我建议直接使用chmod 777因为这个目录通常只有Redis进程在写权限放宽不会造成太多风险。5.7 常见问题五内存资源不足Docker容器默认没有限制内存但宿主机内存不足时进程容易被OOM Killer杀掉。Redis容器如果被杀掉集群会进入fail状态。建议在docker run时加上内存限制--memory1g同时在Redis配置中设置maxmemorymaxmemory 512mb maxmemory-policy allkeys-lru这里需要注意的是Redis Cluster不支持maxmemory-policy noeviction以外的部分策略在某些场景下的预期效果但通常用allkeys-lru可以满足缓存场景。如果节点内存不足集群会自动进行故障转移把从节点提升为主节点。如果没有从节点写入就会直接报错。6. 集群扩展、升级与维护建议6.1 动态扩容新增主从节点Redis Cluster扩容不需要停服。先在Docker中新增两个容器配置成与现有节点一致但IP和端口不同。然后进入任意现有节点用redis-cli --cluster add-node把新节点加入集群。以新增一个主节点为例docker exec -it redis-master-1 redis-cli --cluster add-node \ 172.28.0.8:6379 172.28.0.2:6379这条命令的意思是将新节点172.28.0.8:6379添加到以172.28.0.2:6379为代表的集群中。添加后新节点还没有槽位所以不会分担压力。需要手动迁移槽位redis-cli --cluster reshard 172.28.0.2:6379按提示选择要移动的槽位数量、接收节点ID等。这个操作比较繁琐但可以按交互流程来。如果只是临时性的扩容建议完全交给工具完成不要自己手动计算槽位。新增从节点也类似使用add-node --cluster-slave参数docker exec -it redis-master-1 redis-cli --cluster add-node \ 172.28.0.8:6379 172.28.0.2:6379 \ --cluster-slave这个命令会把新节点作为某个主节点的从节点。如果不指定--cluster-master-idRedis会随机选择一个主节点作为它的主节点。生产环境建议手动指定便于运维管理。6.2 集群版本升级Redis 7.0.7并非最新版如果后续要升级到更高版本可以逐个替换节点。基本步骤是在需要升级的从节点上执行redis-cli CLUSTER FAILOVER让它成为主节点。停止对应旧容器用新版本镜像重新启动并挂载相同的数据目录。等待节点重新加入集群再对其他主节点执行同样的操作。这种滚动升级方式可以保证集群始终可用不需要一次性全部停止。但前提是数据目录的格式兼容Redis 7.0.x内部数据文件格式在7.0系列内是稳定的跨大版本升级则需要更多验证。6.3 集群安全加固默认情况下Redis没有密码访问控制。如果部署到内网或云环境建议开启密码。在redis.conf中配置requirepass yourpassword masterauth yourpasswordrequirepass用于客户端访问验证masterauth用于主从同步时从节点认证主节点。配置后使用redis-cli -a yourpassword连接或者在客户端库中设置密码。注意如果你在集群开启密码后没有设置masterauth从节点无法和主节点完成同步整个集群会出现主从切换失败的问题。这个我实际遇到过配置masterauth后集群才恢复正常。6.4 日志监控与告警Docker容器日志采集很方便可以实时观察Redis节点的运行情况docker logs -f redis-master-1日志中会记录节点握手、槽位迁移、故障检测等信息。建议将容器日志接入统一的日志管理平台或者定期检查包含关键字fail、error、cluster state changed的日志。我个人的经验是在集群环境中最应该关注的是cluster-nodes.conf文件的状态这个文件记录了集群拓扑如果该文件损坏或内容异常节点启动会遇到很多问题。可以定期备份/data目录下的nodes.conf文件。7. 几个容易被忽略的细节补充7.1 时区与系统时间同步Redis Cluster节点之间的故障检测依赖节点心跳和超时时间如果宿主机之间的系统时间偏差太大可能导致集群误判故障甚至出现脑裂问题。建议在所有宿主机上开启NTP时间同步。在容器内默认时区可能不是本地时区但这不影响Redis运行。你可以通过挂载/etc/localtime来同步容器时区-v /etc/localtime:/etc/localtime:ro这个操作主要方便日志时间查看不设置也不会影响集群功能。7.2 文件描述符限制Redis高并发运行时会占用大量文件描述符。如果宿主机的ulimit -n设置过小可能导致客户端连接被拒绝。用ulimit -n 65535调整或者修改容器启动时的--ulimit参数--ulimit nofile65535:65535否则当连接数超过默认限制时Redis日志中会出现“Too many open files”的报错。这个问题在压力测试时最容易暴露。7.3 关于容器网络模式的选择我使用的是自定义bridge网络这样容器有独立IP。如果使用--network host模式容器会直接共享宿主机网络性能更高配置也更简单因为不需要端口映射和cluster-announce-ip设置。但host模式也有缺点每个Redis节点都要占用宿主机不同的端口起多个容器时端口冲突概率大而且无法像bridge网络那样灵活限制网络访问。对于单机多节点的集群场景bridge网络更合适。如果是跨主机多节点的高性能场景可以考虑host网络或overlay网络。个人体会是Docker部署Redis Cluster最痛苦的一环并不是命令数量而是网络拓扑和节点配置的一致性。我在配置完6个容器之后花了很多时间在排查一个隐藏坑cluster-announce-ip写成了6379而不是172.28.0.x结果手动初始化时总是提示节点握手失败。后来我养成一个习惯每启动一个节点就立即docker exec进去查看cluster nodes确认输出里面的IP端口正确然后再启动下一个节点这样能把问题定位在源头。还有一次因为某个容器使用同样的redis.conf文件但忘了修改其中的port参数导致两个节点都在端口6379上监听整个集群一直无法加入。这种问题在批量生成配置文件时很容易出现建议用脚本统一生成配置内容而不是手工一个个改。下面我把生成配置的脚本思路分享给你方便直接复用。for port in $(seq 6379 6384); do busport$((port 10000)) mkdir -p /data/redis-cluster/$port/data cat /data/redis-cluster/$port/redis.conf EOF bind 0.0.0.0 protected-mode no port $port daemonize no dir /data appendonly yes appendfilename appendonly.aof appendfsync everysec cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 cluster-announce-ip 172.28.0.$((port - 6377)) cluster-announce-port $port cluster-announce-bus-port $busport EOF done通过这个脚本每个节点的配置都会自动带上对应端口和IP减少手工操作造成的低级失误。你只需要对着前面表格里的IP分配表微调一下cluster-announce-ip即可如果觉得172.28.0.2到172.28.0.7不够直观也可以根据端口号推导IP。更重要的是这个脚本在以后重新搭建时可以直接复用不仅节省时间还能避免配置不一致的问题。如果后续你想把这个集群从模拟项目X迁移到另一个环境不需要重新初始化数据只需要确保挂载数据目录完整然后重新启动相同拓扑的容器即可。Redis集群的节点ID记录在nodes.conf中只要该文件存在节点启动后会自动寻找原集群成员不需要重新执行cluster create。这种“全容器化”的好处是环境可复制、恢复速度快、团队协作时只需要同步配置文件和数据目录。我后来甚至把整个部署过程写成了一个部署脚本一条命令完成网络创建、配置生成和容器启动测试环境几秒钟就能搭好一套全新的集群。最后再补充一点小技巧如果发现集群状态长时间是ok但实际访问异常不要一股脑重删重建先看日志。docker logs和redis-cli cluster info往往能告诉你真正的故障点。还有不要忽略docker update --restart unless-stopped否则宿主机重启后六个容器全没了集群和数据都处于停止状态重新拉起后还得人工确认节点状态非常被动。我实际情况中吃过这个亏后来第一件事就是设置好重启策略。这套基于Redis 7.0.7的Docker Cluster方案适合单机多实例演练、测试环境部署也适合中小规模生产使用。希望这份记录能帮你少走弯路如果照着操作还是卡在某一步大概率是网络层面的配置细节不妨对照cluster nodes输出逐项检查IP和端口问题一般都能水落石出。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →