十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Docker搭建Redis Cluster集群:从零实现3主3从与故障转移

用Docker搭建Redis Cluster集群:从零实现3主3从与故障转移 搞Redis集群这件事很多人一听就头大——要准备多台机器要配置各种参数还要担心节点挂了怎么办。但如果你用Docker来做Redis Cluster集群环境这事儿就能被拆得明明白白。我用Docker在一台开发机上完整搭过一套Redis Cluster集群环境3主3从、槽位分配、故障转移全都验证了一遍整个过程一个多小时就能搞定每一步命令、每一个预期输出我都给你列出来比自己截图还直观。这篇文章的核心思路很简单利用Docker的bridge网络模拟出6台独立的Redis服务器在一台物理机上完成整套Redis Cluster集群环境的搭建、验证和故障演练。你不需要额外购买服务器不需要折腾虚拟机一台普通的开发电脑就能跑全套。无论你是刚接触Redis的初学者还是在准备面试时需要动手实操的开发者这篇文章都能让你少踩很多坑。1. 确定整体思路为什么用Docker来搭Redis Cluster1.1 痛点与方案选型Redis Cluster最少需要6个节点因为我计划搭建3主3从的高可用架构。拿6台物理机或者6台虚拟机来练手成本太高特别是对个人开发者来说开6个虚拟机光是内存就吃不消。用Docker就完全不一样了每个Redis容器只占用几十MB内存6个节点加起来还不到500MB普通电脑跑起来毫无压力。更重要的是Docker容器之间天然实现了网络隔离和资源隔离这正好模拟了真实生产环境中多台服务器各司其职的状态。通过自定义bridge网络容器之间可以用容器名互相通信这比传统的IP直连方式更贴近生产环境的服务发现机制。我选择Docker来搭集群的另一个原因是可复用性太强了——所有配置都存在conf文件和启动脚本里下次想搭一套全新的集群跑一遍脚本就搞定不需要重新敲命令。有人可能会问直接用Linux的host网络模式不是更接近真实环境吗确实host模式在性能上和真实环境几乎一致但有两个明显的坑第一host网络模式下所有Redis节点共享宿主机的网络栈6个节点都要监听6379端口你不改端口就必然冲突第二Windows和Mac上的Docker Desktop根本不支持host网络模式。所以我最终选用bridge网络加端口映射的方式既能跨平台运行又能保证节点间相互隔离。1.2 环境准备与版本选择动手之前先把环境检查一遍免得中途卡住。我使用的是Redis 7.x系列镜像具体选择的是redis:7.2-alpine这个镜像体积小整体才40MB左右拉取速度快而且7.x版本对Redis Cluster的支持已经非常成熟。Docker环境方面Windows和Mac用户建议直接安装Docker DesktopLinux用户安装Docker Engine即可。安装完成后先跑一下docker version确认环境正常docker version只要能看到Client和Server两部分版本信息就说明Docker服务正常。如果发现只有Client信息、Server部分报错大概率是Docker Desktop没启动成功或者Linux上Docker服务没开。这一步不确认好后面所有操作都白搭。另外因为我用的是Redis 7.2镜像redis-cli客户端工具也会以7.2版本运行而创建集群用的redis-cli --cluster create命令是Redis 5.0之后引入的版本上完全没有问题。如果你机器上装了老版本的redis-cli建议直接用容器内的命令比如docker exec -it redis-1 redis-cli这样能保证客户端版本和集群版本一致。2. Redis Cluster核心概念与端口规划2.1 集群是怎样工作的槽位、主从与选举在敲命令之前我建议先花几分钟把Redis Cluster的原理弄清楚。很多人照着教程搭完集群结果一问槽位是什么、主节点挂了怎么恢复还是一头雾水那这个集群搭了等于白搭。Redis Cluster的数据分片核心是哈希槽hash slot。整个集群固定有16384个槽位每个key通过CRC16算法计算出一个哈希值再对16384取模得到的结果就是该key应该存放的槽位。集群创建时这16384个槽位会被平均分配给所有主节点比如3个主节点的场景下每个主节点分到约5461个槽位。当你向集群写入一个key时Redis会根据哈希结果计算出槽位然后判断这个槽位属于哪个主节点。如果恰好属于你连接的节点就直接写入如果不属于节点会返回一个MOVED错误并告诉你正确的节点地址。这正是Redis Cluster要求客户端必须支持自动重定向的原因redis-cli -c中的-c参数就是开启集群模式的开关它会自动处理这种重定向逻辑。主从复制的原理也不复杂。每个主节点可以配置一个或多个从节点从节点通过异步复制同步主节点的数据。当主节点宕机时从节点会根据集群内所有主节点的投票结果完成故障转移把自己提升为新的主节点。这里有硬性要求Redis Cluster至少要3个主节点才能正常工作因为故障转移需要多数节点参与投票如果有两个主节点其中一个挂了就无法达成多数共识集群就瘫痪了。2.2 端口规划客户端端口和集群总线端口一个都不能少这是整个搭建过程中最容易踩坑的地方我特意单独拿出来讲。Redis Cluster里的每个节点需要占用两个端口客户端通信端口默认6379用来接收客户端读写请求。集群总线端口默认则是客户端端口加10000即16379用来进行节点间的通信包括故障检测、配置同步、槽位迁移等。很多人在搭建时只映射了6379端口结果创建集群时报错或者集群搭完后一重启就出问题根本原因就是16379端口没通。我这里的端口映射方案如下节点名称容器内端口宿主机客户端端口宿主机集群总线端口redis-16379637116371redis-26379637216372redis-36379637316373redis-46379637416374redis-56379637516375redis-66379637616376这样设计的好处是我在宿主机上可以直接用redis-cli -p 6371连接第一个节点不需要进入容器内部操作验证和调试都方便。同时每个节点的端口都不一样避免和本机其他Redis服务冲突。另外注意一个细节集群初始化时redis-cli --cluster create命令使用的是映射到宿主机的6371到6376端口但集群内部节点之间进行通信时会通过容器名称自动解析到容器内的6379和16379端口。Docker的bridge网络在这里起到了虚拟网络隔离的作用让6个容器就像6台独立服务器一样通过网络互访。2.3 网络规划创建自定义bridge网络默认的bridge网络虽然能用但我强烈建议为集群创建一个独立的网络原因有两个第一自定义网络支持容器名DNS解析容器之间可以通过名称通信而默认bridge网络只能通过IP访问第二独立网络隔离性好不会和宿主机上其他Docker容器互相干扰。docker network create --driver bridge --subnet172.20.0.0/16 redis-cluster-net这里我把子网指定为172.20.0.0/16主要是为了方便管理后续如果想给某个容器设置固定IP可以直接在这个子网内分配。创建完成后可以用docker network ls确认网络已存在用docker network inspect redis-cluster-net查看详细信息。3. 实操从零开始搭建Redis Cluster集群3.1 编写redis.conf配置文件每个参数都有讲究在启动容器之前需要先准备Redis配置文件。我习惯把所有配置文件按节点编号存放这样后续排查问题时一眼就能看出哪个节点的配置有问题。先在宿主机创建统一的目录结构我这里以/data/redis-cluster为例mkdir -p /data/redis-cluster/redis-{1,2,3,4,5,6}然后为每个节点写入redis.conf配置。这里先贴出redis-1的配置port 6379 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 appendonly yes appendfsync everysec protected-mode no这6个参数每一个都值得展开说一下port 6379容器内Redis监听的端口。由于每个容器网络栈独立所以6个容器都可以同时监听6379不会冲突。cluster-enabled yes核心开关必须设置为yes否则Redis实例会以单机模式启动无法加入集群。cluster-config-file nodes.conf集群节点配置文件Redis会自动维护这个文件记录当前节点的ID、槽位分配信息等。注意这个文件会被Redis自动写入不需要手动编辑。cluster-node-timeout 15000节点超时时间单位是毫秒。如果一个主节点15秒内没有响应集群总线上的心跳消息从节点就会发起故障转移。这个值是根据实际场景调的我测试时发现15秒比较均衡既不会因为网络抖动动不动就切换也不会让故障恢复等太久。appendonly yes和appendfsync everysec开启AOF持久化每秒同步一次。这样即使容器重启数据也不会丢太多。学习环境虽然对数据安全要求不高但从一开始就用持久化跑能避免很多“重启后数据没了”的误会。写配置的时候注意每个节点都要生成一份相同的配置。我通常用一段循环脚本批量生成省得手动敲6遍for port in 1 2 3 4 5 6; do cat /data/redis-cluster/redis-${port}/redis.conf EOF port 6379 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 appendonly yes appendfsync everysec protected-mode no EOF doneprotected-mode no这个参数要特别提醒一下如果不设置的话Redis默认只允许本机访问而Docker容器内所有外部访问都算“非本机”这样宿主机上的redis-cli就没办法连接容器了。当然这个参数也意味着没有开启密码保护所以生产环境千万别这么搞后续我会在避坑部分专门讲安全问题。3.2 启动6个Redis容器用循环脚本批量创建配置文件写好后就可以启动容器了。这里最推荐的做法是写一个循环脚本一次启动6个节点而不是逐个手动docker run效率高且不易出错。for port in 1 2 3 4 5 6; do docker run -d \ --name redis-${port} \ --network redis-cluster-net \ -p 637${port}:6379 \ -p 1637${port}:16379 \ -v /data/redis-cluster/redis-${port}/redis.conf:/etc/redis/redis.conf:ro \ -v /data/redis-cluster/redis-${port}/data:/data \ redis:7.2-alpine \ redis-server /etc/redis/redis.conf done这个脚本里有几个关键点需要说明第一-p参数做了两组端口映射分别是客户端端口637x和集群总线端口1637x这两个缺一不可。第二-v参数把宿主机上的配置文件和数据目录都挂载进容器配置文件以只读方式挂载:ro防止容器内误修改而数据目录挂在/data下对应Redis的工作目录AOF持久化文件会写到这里。第三命令末尾的redis-server /etc/redis/redis.conf覆盖了镜像默认的启动命令确保Redis会加载我们的自定义配置。启动完成后先用docker ps看看6个容器是否都处于Up状态docker ps正常输出中应该有6个名为redis-1到redis-6的容器都在运行。如果某个容器启动了又立刻退出用docker logs redis-1查看日志最常见的报错是配置文件权限问题或者端口被占用。3.3 执行集群初始化一条命令搞定3主3从6个Redis容器都运行起来后它们目前还是相互独立的单机实例并没有组成集群。接下来就需要用redis-cli的集群管理命令把6个节点组合成一个集群。先在宿主机上找到任意一个节点的地址比如127.0.0.1:6371然后执行redis-cli --cluster create \ 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 \ 127.0.0.1:6374 127.0.0.1:6375 127.0.0.1:6376 \ --cluster-replicas 1这里--cluster-replicas 1的意思是每个主节点配备1个从节点6个节点会形成3主3从的结构。命令执行后redis-cli会先做一轮节点健康检查然后给出槽位分配方案和主从对应关系最后会提示你是否接受这个方案需要输入yes确认。输入yes后集群会自动完成槽位分配和主从绑定整个过程大约几秒钟。最终输出中会看到类似“All 16384 slots covered”的提示这表示所有槽位都已成功分配集群创建完成。在这里我特意要强调一个细节我使用的是宿主机上安装的redis-cli连接的是映射到宿主机的6371到6376端口。但--cluster create命令真正做的事情是让这6个节点通过集群总线端口16371到16376相互见面并交换集群信息。所以如果你在创建集群时卡住或报错优先检查的绝对应该是16371到16376这些集群总线端口是否放行了。4. 集群验证与故障转移演练4.1 检查节点状态与槽位分配集群搭建完成之后不要急着写数据先用集群命令确认一下状态。下面这些都是必看的redis-cli -c -p 6371 cluster info重点关注cluster_state:ok如果这里显示的是fail说明集群健康检查没通过。再查看cluster_slots_assigned:16384这个数值代表所有16384个槽位都已分配完成如果少于16384说明分配不完整。再看节点的具体分布redis-cli -c -p 6371 cluster nodes输出中会列出6个节点包含每个节点的ID、IP和端口、角色master或slave、所属主节点ID、槽位范围等信息。正常情况下应该是3个master节点每个master节点下面挂着一个slave。例如master节点后面的槽位显示为[0-5460]slave节点后面会显示对应master的节点ID。我还习惯用redis-cli -p 6371 cluster slots查看更直观的槽位分布表它会以列表形式展示每个槽位区间的起始位置、结束位置和对应的主从节点地址对理解集群架构特别有帮助。4.2 读写测试观察key是如何被重定向的集群状态正常后做一轮读写测试。这里必须使用-c参数连接也就是集群模式否则Redis直接返回MOVED错误新手很容易被吓到以为集群坏了。redis-cli -c -p 6371 set name redis-cluster-test运行这条命令时Redis会根据key“name”计算哈希槽然后判断槽位属于哪个节点如果属于当前节点就直接写入如果不属于redis-cli会自动跟随重定向并写入到正确的节点。执行完再读取redis-cli -c -p 6371 get name命令输出会显示“redis-cluster-test”说明数据写入和读取都成功了。为了验证数据确实被分布到了不同节点可以多写几个不同的key然后分别通过不同端口的客户端读取。你会发现有的key存储在6371节点上有的key存储在6372节点上这就是哈希槽自动分布的直观体现。给一个我实际测试时经常用来观察重定向的操作在集群模式客户端中执行写入时会出现- Redirected to slot [13441] located at 127.0.0.1:6373之类的提示。这是正常的不是报错它只是告诉你这个key的槽位不在当前节点客户端自动转向了正确的节点。4.3 高可用验证杀掉一个主节点会发生什么Redis Cluster最吸引人的能力就是高可用故障转移这一步一定要亲手验证一次。我在测试中杀掉了redis-1对应的主节点前三个槽位区间中的第一个。具体操作docker stop redis-1等大约15到20秒超过cluster-node-timeout的15000毫秒再连接任意一个存活的节点查看状态redis-cli -c -p 6372 cluster nodes这时你会看到redis-1对应的节点标记为fail原本挂在它下面的从节点比如redis-4已经被提升为新的主节点并且接管了redis-1原有的全部槽位。整个过程不需要人工干预完全由集群自动完成。接下来再验证故障恢复。把redis-1容器重新启动docker start redis-1等待几秒后再次查看cluster nodes你会发现redis-1以从节点的身份重新加入集群并自动挂载到新主节点redis-4下面数据也会从主节点同步过来。到这里就可以确认这套集群的故障转移和自动恢复机制都已经正常工作。5. 常见问题与排查实录5.1 Docker Desktop启动失败怎么办很多Windows用户在启动Docker Desktop时遇到过类似的报错virtualization support wasnt detected或者提示需要启用Hyper-V、WSL2等功能。这个问题本质上是因为Docker Desktop依赖操作系统的虚拟化能力而虚拟化功能没有完全打开。排查顺序我建议是先进BIOS确认CPU虚拟化开关Intel VT-x或AMD-V已经开启然后确认Windows的“虚拟机平台”和“适用于Linux的Windows子系统”两个功能都已启用。完成这些操作后重启电脑再启动Docker Desktop绝大多数问题都能解决。如果还不行检查WSL2的内核更新是否安装。从实际经验来看80%以上的Docker Desktop启动失败都是这三个原因之一。5.2 初始化集群时报Connection refused执行redis-cli --cluster create报错Could not connect to Redis at 127.0.0.1:6371: Connection refused这个先不用怀疑配置多半是容器根本没起来。先用docker ps看看容器状态发现容器处于Exited状态的话用docker logs redis-1看日志。最常见的两种情况是Redis配置文件里写了错误参数导致启动失败或者宿主机端口被占用。还有一种容易被忽略的情况容器是起来了但redis-server进程还没完全启动你马上执行cluster create就会连不上。这种情况等两三秒再试一次就好。另外我在README式的教程里反复强调要映射两个端口有些人只映射了6379端口创建集群时会报节点握手失败但这种情况报错往往是Timeout而不是Connection refused注意区分。5.3 创建集群时提示Not all 16384 slots are covered这个报错意味着集群中有些槽位没有被任何节点接管整个集群处于不完整状态拒绝提供服务。触发这个问题的场景一般有两种一是集群创建过程中节点间通信失败槽位分配中断二是搭建后手动修改了节点配置导致槽位信息丢失。最直接的解决办法是全部推倒重来。但要注意执行redis-cli --cluster create前如果发现节点之前已经加入过集群需要先对每个节点执行redis-cli --cluster reset否则会报节点非空错误。实在不行把挂载的数据目录下的nodes.conf文件删掉重启容器让Redis重新生成节点配置这样最干净。5.4 容器重启后节点掉线或拒绝加入集群这是我在实际环境中踩过最大的坑用docker stop和docker start重启容器没有问题因为容器的IP没有变但如果用docker rm删掉容器再重新docker run容器IP几乎一定会变化而Redis的nodes.conf文件里记录的是旧IP新节点拿着旧IP去通信自然连不上集群。解决办法有三个可选最简单的是尽量用docker stop/start别轻易删容器其次是创建容器时指定固定IP地址例如docker run --ip 172.20.0.11这样无论容器怎么重建IP都不会变最后就是每次容器重建后到数据目录里删除nodes.conf文件让节点以全新身份加入集群。我在日常实验中优先用前两种方案第三种作为兜底。5.5 安全提醒端口别裸奔这套教程里使用的配置把所有端口直接映射到了宿主机的所有网卡上意味着局域网内任何机器都能访问这些Redis节点。在本地学习环境里问题不大但如果你是在公司内网或者云服务器上做实验这就太危险了。Redis默认没有密码保护一旦被扫描到数据泄露、被勒索挖矿都是真实发生过的事。我的建议是端口映射时明确绑定到本机回环地址比如-p 127.0.0.1:6371:6379这样只有本机可以访问容器端口外部网络完全看不到。如果是生产环境还需要在Redis配置里加上requirepass设置密码并在集群节点间配置masterauth。这套安全机制需要单独整理一篇来讲但至少本地实验时养成绑定127.0.0.1的习惯是绝对有好处的。最后再分享一个我自己的习惯搭建集群这类多节点环境时我永远把配置目录、数据目录、脚本文件放在同一个固定地方比如/data/redis-cluster下每个节点一个子目录。这么做的好处是实验失败后可以直接删除整个目录重来日志和配置也都在一眼能看到的地方。如果你也想把这套流程固化成自己的工具箱强烈建议从一开始就保持这个目录结构后面做自动化脚本会非常省事。
返回列表