[{"data":1,"prerenderedAt":327},["ShallowReactive",2],{"content:\u002F2024\u002Fredis_master_slave":3,"surround:\u002F2024\u002Fredis_master_slave":316},{"id":4,"title":5,"body":6,"categories":293,"date":295,"description":296,"draft":297,"extension":298,"image":299,"meta":300,"navigation":302,"path":303,"permalink":299,"published":299,"readingTime":304,"recommend":299,"references":299,"seo":309,"sitemap":310,"stem":311,"tags":312,"type":314,"updated":299,"__hash__":315},"content\u002Fposts\u002F2024\u002Fredis_master_slave.md","搭建 Redis 主从模式",{"type":7,"value":8,"toc":281},"minimark",[9,13,22,24,27,30,41,44,50,53,59,62,69,75,81,83,86,99,102,109,112,205,210,228,234,237,245,249,252,266,269],[10,11,12],"h2",{"id":12},"前言",[14,15,16,17,21],"p",{},"Redis 是内存数据库，我们知道可以通过持久化机制，保存快照或者保存记录日志的方式，将数据持久化到磁盘。但是，如果机器故障了或磁盘坏了，数据就不就全没了吗？这种情况应该怎么办呢？别担心，考虑",[18,19,20],"strong",{},"主从模式","。",[10,23,20],{"id":20},[14,25,26],{},"给主节点 Master 配置一个从节点 Slave，当 Master 挂了以后，Slave 可以顶上。通常如果是小规模应用，从节点只配置一个，提供基础备份功能。",[14,28,29],{},"假设我们现在有两台机器，A 和 B。在 B 机器上，通过如下命令，可以将 B 设置为 A 的从节点：",[31,32,38],"pre",{"className":33,"code":35,"language":36,"meta":37},[34],"language-bash","# 连接 B 机器的 Redis 命令交互行\n$ redis-cli\n# 将 B 设置为 A 的从节点\n127.0.0.1:B> slaveof 127.0.0.1 A\n# 验证主从配置\n127.0.0.1:B> info replication\n# 输出如下信息代表设置成功\nrole:slave\nmaster_host:127.0.0.1\nmaster_port:6379\nmaster_link_status:up\n...\n","bash","",[39,40,35],"code",{"__ignoreMap":37},[14,42,43],{},"接下来，让我们一起通过 Docker 进行实验：",[31,45,48],{"className":46,"code":47,"language":36,"meta":37},[34],"# 创建一个网络\n$ docker network create redis-net\n# 启动 Master 节点\n$ docker run -d --name redis-master \\\n--network redis-net \\\n    -p 6379:6379 \\\n    redis redis-serve\n# 启动 Slave 节点\n$ docker run -d --name redis-slave \\\n--network redis-net \\\n    -p 6380:6379 \\\n    redis redis-server --slaveof redis-master 6379\n\n# 连接主节点并验证主从配置\n$ docker exec -it redis-slave redis-cli -h redis-master\n# 验证主从配置\n127.0.0.1:6379> info replication\n# 输出如下信息代表设置成功\n# Replication\nrole:master\nconnected_slaves:1\nslave0:ip=172.18.0.3,port=6379,state=online,offset=42,lag=0\n\n# 连接从节点同上，不赘述，输出如下信息代表设置成功\n# Replication\nrole:slave\nmaster_host:redis-master\nmaster_port:6379\nmaster_link_status:up\n",[39,49,47],{"__ignoreMap":37},[14,51,52],{},"验证从节点是否同步了主节点的数据：",[31,54,57],{"className":55,"code":56,"language":36,"meta":37},[34],"127.0.0.1:B> GET skey\n(nil)\n127.0.0.1:A> SET skey abc\nOK\n127.0.0.1:B> GET skey\n\"abc\"\n",[39,58,56],{"__ignoreMap":37},[14,60,61],{},"通过上述实验，我们可以看到，从节点已经同步了主节点的数据。那主节点之前就有的数据，是否也会同步到从节点呢？",[14,63,64,65,68],{},"答案是",[18,66,67],{},"会","。让我们再进行一次实验：",[31,70,73],{"className":71,"code":72,"language":36,"meta":37},[34],"# 将 B 从 A 的从节点设置为独立节点\n127.0.0.1:B> SLAVEOF on one\nOK\n# 输出如下信息代表设置成功\n127.0.0.1:B> info replication\n# Replication\nrole:master\nconnected_slaves:0\nmaster_failover_state:no-failover\n...\n# 在主节点 A 新增键值对并查询（请自己新增数据，这里忽略了）\n127.0.0.1:A> keys *\n1) \"skey\"\n2) \"bkey\"\n3) \"akey\"\n# 在 B 节点查询键值对，只有之前同步的键值对\n127.0.0.1:B> keys *\n1) \"skey\"\n# 重新将 B 设置为 A 的从节点\n127.0.0.1:B> slaveof redis-master 6379\nOK\n# 在 B 节点查询键值对，所有键值对都同步过来了\n127.0.0.1:B> keys *\n1) \"skey\"\n2) \"bkey\"\n3) \"akey\"\n",[39,74,72],{"__ignoreMap":37},[14,76,77,78,21],{},"有了从节点，在主节点发生故障的时候，我们就可以把项目中 Redis 的连接配置，从原来的主节点修改为从节点。但是，我们什么时候才知道主节点发生故障呢？最简单的方式就是写一些脚本进行监测，当主节点挂了以后，自动将项目中 Redis 的连接配置从主节点修改为从节点。不过，这种监控以及故障转移的能力，Redis 已经有了完整解决方案：",[18,79,80],{},"哨兵模式",[10,82,80],{"id":80},[14,84,85],{},"它由一个或多个哨兵实例组成，用于监控 Redis 主节点和从节点，并在主节点发生故障时自动进行故障转移。本质上，Redis 的哨兵模式就是一个 Redis 进程，只是启动参数不同和职责不同。哨兵模式的 Redis 进程不负责数据存储，它主要负责三件事：",[87,88,89,93,96],"ol",{},[90,91,92],"li",{},"监控主从节点是否正常运行；",[90,94,95],{},"通过发布订阅模式，将故障转移的结果通知给订阅者；",[90,97,98],{},"当主节点发生故障时，自动进行故障转移，选择一个最合适的从节点升级为主节点，并通知应用方更新配置。",[14,100,101],{},"下图为哨兵和主从的工作模式：",[14,103,104],{},[105,106],"img",{"alt":107,"src":108},"哨兵和主从的工作模式","https:\u002F\u002Fimg.51shazhu.com\u002Fautoupload\u002FZ3wg1auvHGH_fxQcOFgj2SfNcKcqEnRmcljopnyJoMs\u002F20250801\u002FTDTa\u002F1341X850\u002Fleader-01.png",[14,110,111],{},"接下来，我们继续用 Docker，搭建一个哨兵集群，用于监控我的主从同步 Redis 集群，然后，模拟主从同步中的主节点挂掉了，从节点被选举为新的主节点。",[87,113,114,121,130,157,187,196],{},[90,115,116,117,120],{},"启动 Redis 主从集群",[118,119],"br",{},"前面已经介绍过，不再赘述。",[90,122,123,124],{},"查看 Redis 主节点 IP",[31,125,128],{"className":126,"code":127,"language":36,"meta":37},[34],"$ docker inspect redis-master | grep IPAddress\n",[39,129,127],{"__ignoreMap":37},[90,131,132,133,139,141,142],{},"创建三个哨兵的配置文件",[31,134,137],{"className":135,"code":136,"language":36,"meta":37},[34],"# 创建三个哨兵的配置文件，172.18.0.2 请修改为自己的 Redis 主节点 IP\n$ cat > sentinel1.conf \u003C\u003C EOF\nsentinel monitor mymaster 172.18.0.2 6379 2\nsentinel down-after-milliseconds mymaster 5000\nsentinel failover-timeout mymaster 60000\nsentinel parallel-syncs mymaster 1\nEOF\n\n# 复制配置文件\n$ cp sentinel1.conf sentinel2.conf\n$ cp sentinel1.conf sentinel3.conf\n",[39,138,136],{"__ignoreMap":37},[118,140],{},"配置说明：",[143,144,145,148,151,154],"ul",{},[90,146,147],{},"sentinel monitor mymaster 172.18.0.2 6379 2：表示监控的主节点 IP 为 172.18.0.2，端口为 6379，至少有 2 个哨兵认为主节点挂了，才能进行故障转移；",[90,149,150],{},"sentinel down-after-milliseconds mymaster 5000：哨兵发现主节点 5 秒没有响应，就认为主节点故障；",[90,152,153],{},"sentinel failover-timeout mymaster 60000：故障转移超时时间；",[90,155,156],{},"sentinel parallel-syncs mymaster 1：表示在进行故障转移时，最多有 1 个从节点参与同步。",[90,158,159,160,166,168,169,175,177,178,180,181,183],{},"启动三个哨兵",[31,161,164],{"className":162,"code":163,"language":36,"meta":37},[34],"$ docker run -d --name sentinel1 \\\n--network redis-net \\\n-p 26379:26379 \\\n-v $(pwd)\u002Fsentinel1.conf:\u002Fetc\u002Fredis\u002Fsentinel.conf \\\nredis redis-sentinel \u002Fetc\u002Fredis\u002Fsentinel.conf\n\n$ docker run -d --name sentinel2 \\\n--network redis-net \\\n-p 26380:26379 \\\n-v $(pwd)\u002Fsentinel2.conf:\u002Fetc\u002Fredis\u002Fsentinel.conf \\\nredis redis-sentinel \u002Fetc\u002Fredis\u002Fsentinel.conf\n\n$docker run -d --name sentinel3 \\\n--network redis-net \\\n-p 26381:26379 \\\n-v $(pwd)\u002Fsentinel3.conf:\u002Fetc\u002Fredis\u002Fsentinel.conf \\\nredis redis-sentinel \u002Fetc\u002Fredis\u002Fsentinel.conf\n",[39,165,163],{"__ignoreMap":37},[118,167],{},"启动后，你会看到哨兵容器日志中，有如下信息：",[31,170,173],{"className":171,"code":172,"language":36,"meta":37},[34],"WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy\n",[39,174,172],{"__ignoreMap":37},[118,176],{},"这个警告信息可以忽略，不影响使用，这个警告是因为在 Docker 容器中，Redis Sentinel 无法将更新后的配置写回配置文件。这是因为我们使用的是只读卷挂载。",[118,179],{},"到此为止，我们启动的容器如下图所示。",[118,182],{},[105,184],{"alt":185,"src":186},"启动的容器","https:\u002F\u002Fimg.51shazhu.com\u002Fautoupload\u002FZ3wg1auvHGH_fxQcOFgj2SfNcKcqEnRmcljopnyJoMs\u002F20250801\u002FBMhg\u002F1138X588\u002Fdocker-01.png",[90,188,189,190],{},"模拟主节点故障",[31,191,194],{"className":192,"code":193,"language":36,"meta":37},[34],"# 停止 Redis 主节点\n$ docker stop redis-master\n\n# 连接原从节点验证是否已升级为主节点\n$ docker exec -it redis-slave redis-cli\n127.0.0.1:6379> info replication\n# Replication\nrole:master\nconnected_slaves:0\n...\n",[39,195,193],{"__ignoreMap":37},[90,197,198,199],{},"恢复原主节点",[31,200,203],{"className":201,"code":202,"language":36,"meta":37},[34],"# 启动原主节点\n$ docker start redis-master\n\n# 查看原主节点状态（此时变成了从节点）\n$ docker exec -it redis-master redis-cli\n127.0.0.1:6379> info replication\n# Replication\nrole:slave\nmaster_host:172.18.0.3\nmaster_port:6379\n...\n",[39,204,202],{"__ignoreMap":37},[206,207,209],"h3",{"id":208},"leader-哨兵的选举策略","Leader 哨兵的选举策略",[14,211,212,213,216,217,220,221,224,225,227],{},"发生故障后，哨兵集群会动态产生一个 ",[18,214,215],{},"Leader 哨兵","，由它执行主从同步的故障转移，即最适合的从节点升级为新主节点，并通知其他哨兵和客户端配置更新。请注意，Leader 身份是",[18,218,219],{},"临时的","，",[18,222,223],{},"动态的","，完成故障转移后，所有哨兵又恢复平等地位，下次故障时重新选举，这种设计是为了避免多个哨兵同时执行故障转移和故障转移的一致性。",[18,226,215],{}," 的选举如下图所示。",[14,229,230],{},[105,231],{"alt":232,"src":233},"Leader 哨兵的选举","https:\u002F\u002Fimg.51shazhu.com\u002Fautoupload\u002FZ3wg1auvHGH_fxQcOFgj2SfNcKcqEnRmcljopnyJoMs\u002F20250801\u002FTLWO\u002F1168X652\u002Fsentinel-01.png",[14,235,236],{},"当一个哨兵节点确定 Redis 集群的主节点下线后，会请求其他哨兵将自己选举为 Leader，被请求的哨兵如果没有同意过其他哨兵节点的选举请求，就会同意请求，也就是选票+1。",[14,238,239,240,244],{},"如果某个哨兵节点获得了超过半数哨兵节点的选票，且大于 quorum 配置值",[241,242,243],"span",{},"1","，就会成为 Leader 哨兵，否则重新进行选举。",[206,246,248],{"id":247},"leader-哨兵选择新主节点策略","Leader 哨兵选择新主节点策略",[14,250,251],{},"当哨兵集群选举出 Leader 哨兵后，它将从主从同步的 Redis 集群中，选择一个节点作为新的主节点，选择策略优先级如下：",[87,253,254,257,260,263],{},[90,255,256],{},"过滤故障节点，故障节点包括网络状态不好的节点；",[90,258,259],{},"选择 replica-priority 优先级最高的从节点。replica-priority（Redis 旧版本叫作 slave-priority）是 Redis 从节点的一个启动配置参数，默认值 100，取值范围 0-100；",[90,261,262],{},"选择偏移量最大的从节点。偏移量记录写了从节点写了多少主节点的数据，偏移量越大，同步的数据越多，主从偏移量相同，则数据完全同步；",[90,264,265],{},"选择 runId 最小的从节点作为主节点。runId 是 Redis 每次启动随机生成的 Redis 标识。",[10,267,268],{"id":268},"参考资料",[14,270,271,273,274],{},[241,272,243],{}," ",[275,276,280],"a",{"href":277,"rel":278},"https:\u002F\u002Fwww.w3cschool.cn\u002Fredis_all_about\u002Fredis_all_about-9tgp271d.html",[279],"nofollow","Redis 集群配置：https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Ftopics\u002Fcluster-config\u002F",{"title":37,"searchDepth":282,"depth":282,"links":283},4,[284,286,287,292],{"id":12,"depth":285,"text":12},2,{"id":20,"depth":285,"text":20},{"id":80,"depth":285,"text":80,"children":288},[289,291],{"id":208,"depth":290,"text":209},3,{"id":247,"depth":290,"text":248},{"id":268,"depth":285,"text":268},[294],"技术","2024-11-23 19:53:37","主从模式可以避免机器故障引发的数据丢失。",false,"md",null,{"slots":301},{},true,"\u002F2024\u002Fredis_master_slave",{"text":305,"minutes":306,"time":307,"words":308},"10 min read",9.835,590100,1967,{"title":5,"description":296},{"loc":303},"posts\u002F2024\u002Fredis_master_slave",[313],"Redis","story","WQ3HxdEylmYHBRNS797Km1eGmYGD3jAYx9iZg-OAN_U",[317,322],{"title":318,"path":319,"stem":320,"date":321,"type":314,"children":-1},"游戏帧同步","\u002F2023\u002Fmultiplayer_frame_sync_strategies","posts\u002F2023\u002Fmultiplayer_frame_sync_strategies","2023-09-11 18:34:07",{"title":323,"path":324,"stem":325,"date":326,"type":314,"children":-1},"读薄《代码大全2》","\u002F2025\u002Fcode-complete-2-notes","posts\u002F2025\u002Fcode-complete-2-notes","2025-08-05 19:53:37",1781779103066]