ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Redis集群实战:从哈希槽原理到故障转移的踩坑全记录

Redis集群实战:从哈希槽原理到故障转移的踩坑全记录 Redis集群这件事我踩过的坑比你面试背过的八股文都多。很多朋友一听到“Redis集群面试题”第一反应就是背哈希槽、背Gossip协议结果真到生产环境配集群的时候直接被节点状态、槽位迁移、failover这些实操细节按在地上摩擦。这篇东西我不打算给你整那种教科书式的copy咱们直接从一个经历过线上故障、扩容过节点、半夜爬起来看日志的人的角度把Redis集群的原理和实战经验掰开揉碎了聊一遍。不管你是准备面试、要搭生产集群还是集群已经跑着但心里没底都能从这里拿走点真东西。先说个最核心的认知Redis集群Redis Cluster不是简单的“多台Redis凑一块”它是一个去中心化的、基于哈希槽分片的、自带主从和故障转移能力的分布式解决方案。数据量大了、并发高了、单机内存扛不住写操作了这才是你考虑集群的根本原因。想明白这一点后面的所有设计选择就都能对上了。1. 核心原理Redis集群到底怎么协调这么多节点1.1 哈希槽slot才是集群的灵魂跟很多人的直觉不同Redis集群不是按照key的名字来直接映射节点而是搞了16384个哈希槽。写数据的时候key会被CRC16算法算出一个16位的数值然后对16384取模得出来的就是slot编号。集群再把16384个slot平均分配到各个主节点上。这个设计有什么好处最直接的一点是——数据分布非常均匀。只要你key的命名不是那种故意撞slot的情况CRC16的散列特性基本能保证每台机器只存分给自己的那一份数据。另外16384这个数字也是经过深思熟虑的它足够大能撑起上千个节点规模的集群又不会让节点之间交换的位图信息过大网络开销完全在可控范围内。动手验证一下你在任意一个集群节点上用cluster keyslot mykey命令能看到这个key落在哪个slot再用cluster slots命令能看到当前slot和节点的对应关系。这两个命令面试的时候讲原理生产的时候排查问题都实用到爆。1.2 节点是怎么知道彼此存在的Gossip协议去中心化就意味着没有“老大”每个节点都得知道整个集群里有哪些兄弟。Redis集群用的是Gossip协议来做这个事。节点之间默认每隔100ms就ping一次这个ping不是只发给某一个节点而是随机挑几个节点发被挑到的节点再把消息扩散出去。这里面有几个值得注意的细节cluster-node-timeout这个参数默认是15000ms它决定了一个节点多久没回应就算“疑似下线”。ping消息里带上的是节点的心跳信息、以及它知道的集群状态这样信息会像病毒一样迅速传遍整个集群。但是Gossip有个天生的延迟性节点发现“某个兄弟挂了”这个信息是有一段时间差的。你不能指望它秒级感知这在后面讲故障转移的时候是个关键点。1.3 主从复制数据安全的基本盘集群里的每个主节点可以带一个或多个从节点。主节点挂了从节点顶上主节点还在从节点就是它的备份配合AOF或RDB保证数据不丢。从节点什么时候能顶上去这里涉及一个选举过程。当一个主节点被判为“疑似下线”后它的从节点会尝试发起选举。选举的逻辑是从节点给集群里其它主节点发请求收到超过半数的投票它就能升级为主节点。跟Raft那个思路是一样的过半机制避免脑裂。我在生产环境里见过最经典的翻车现场就是一个3主3从的集群有人为了省机器把3个从节点放在了跟主节点同一台物理机上。结果那台物理机宕了一个主带一个从全挂整个集群虽然还能响应但数据安全已经裸奔了。等主节点failover的时候从节点也起不来直接导致部分slot不可写。所以从节点一定是跟主节点异机部署的这是底线没有商量余地。2. 实操前置动手搭一套3主3从集群要准备什么网上那些一键部署脚本我建议你慎用尤其是面试或者学习阶段。自己手推一遍你才会真正理解那768个槽位是怎么切分的。咱们用最传统的原生方式搭一套3主3从的集群部署在同一台机器上生产环境别这么干这里单纯是为了讲明白原理。2.1 环境说明与配置核心要素假设你手头已经装好了Redis我用的是6.x版本比5.x在集群管理上优雅很多需要准备6个实例端口从7000到7005。每个实例的redis.conf里关键配置是这样的port 7000 cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 5000 appendonly yes daemonize yes pidfile /var/run/redis_7000.pid logfile /var/log/redis_7000.log dir /data/redis/7000注意这几个参数cluster-enabled yes必须开不然实例只是个单机Redis不具备集群感知能力。cluster-config-file这个文件是自动生成的记录了该节点视角下的集群拓扑不要手动去改它节点自己会维护。cluster-node-timeout我建议生产环境不要设太长5秒左右比较合理。太短容易误判太长故障转移就慢了。appendonly yes配合集群使用尽量别只靠RDB快照开了AOF能够大幅降低主从切换时的数据丢失风险。dir每个实例的数据目录一定分开这个不用多解释避免互相覆盖。2.2 用redis-cli创建集群Redis 5.0以后创建集群变得非常简单不需要再用那些容易出错的ruby脚本。一条命令搞定redis-cli --cluster create \ 127.0.0.1:7000 \ 127.0.0.1:7001 \ 127.0.0.1:7002 \ 127.0.0.1:7003 \ 127.0.0.1:7004 \ 127.0.0.1:7005 \ --cluster-replicas 1--cluster-replicas 1的意思是每个主节点配一个从节点。命令执行后会给你列出一个哈希槽分配方案问你是否接受输入yes回车集群就建好了。这时候你可以用下面的命令检查集群状态redis-cli -p 7000 cluster info redis-cli -p 7000 cluster nodes输出内容里你应该能看到cluster_state:ok以及6个节点各自扮演的角色和它们管理的slot范围。3. 实战验证数据写入、读取和自动跳转集群建好之后如果你直接用redis-cli -p 7000去连7000端口然后执行set foo bar大概率会得到一个MOVED错误。这很正常因为foo这个key算出来的哈希槽不在7000这个节点上。这里有两套应对思路3.1 使用集群模式连接每次写命令都在命令后面加-c参数比如redis-cli -p 7000 -c 127.0.0.1:7000 set foo bar - Redirected to slot [12182] located at 127.0.0.1:7002 OK这就是集群模式。它跟普通模式的本质区别就是redis-cli会在收到MOVED或ASK错误时自动帮你重新路由到正确的节点。这就引出了一个特别重要的点在真正的业务代码里客户端必须使用带集群模式的Redis客户端比如JedisCluster、Lettuce的ClusterConnection、StackExchange.Redis等它们内部会自动处理槽位和节点的映射关系以及重定向。如果你用单机模式的客户端去连集群数据出错的概率非常大。这算是新手最容易踩的坑没有之一。3.2 哈希标签hash tag解决批量操作问题还有一个面试高频考点Redis集群模式下mget或者pipeline一次操作多个key这些key如果分布在不同节点上是没办法直接执行的会报CROSSSLOT错误。解决办法就是哈希标签。你只需要在key里面包含{}Redis就只会对大括号里的那部分内容做哈希。比如set user:{1001}:name Tom set user:{1001}:email tomexample.com这样两个key都被映射到同一个slot就能安全地放在一条pipeline或者mget里执行了。我个人给你的实操建议是从设计阶段就做好key规范凡是需要通过一个命令同时操作的多个key都加上业务ID作为哈希标签。否则等数据量大了再改key命名规则那迁移和兼容的痛谁改谁知道。4. 集群扩容与故障转移生产环境绕不开的两个操作4.1 扩容一套新从节点拆分哈希槽集群跑着跑着内存又不够了想加节点怎么办实操命令不难难的是理解背后的过程。新增节点分三步走。第一步启动一个空的Redis实例端口用7006配置和之前保持一致。第二步把它加入到集群中redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:70007006是新节点7000是任意一个已有节点告诉集群“我新来一台机器你们认识一下”。第三步给7006分配哈希槽。这里面有个--cluster reshard命令它是交互式的会让你输入要迁移多少个槽位、把槽位从哪些节点迁过来。如果你希望从每个已有主节点均匀迁出一部分槽位那交互的时候选all就行。但这里有个重要的细节新加入的节点默认是主节点但不会有任何slot。如果你只是想加一个从节点来给某个主节点做备份需要这样操作redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave这样加进来的就是一台纯从节点它会自动选择某个master进行复制。4.2 手动故障转移把一个节点从“主”踢成“从”有时候你要对某个主节点所在的机器做维护需要让它的从节点提前顶上而不是等它真挂了才failover。这个过程不复杂登录到从节点执行一条命令redis-cli -p 7003 cluster failover手动failover有个好处它比自动failover更加平滑会先把主节点上的数据同步过来然后才切换角色丢数据的可能性很小。如果你是在维护窗口内主动切换强烈建议用这种方式。4.3 故障转移后的排查日志里藏的信息我处理过一个典型的自动故障转移场景。某天凌晨主节点7000突然失联。第二天一看日志从节点7003自动变成了master信息大概是这样的# Failover auth granted to 7a8f4d38... for slot range ... # Starting a failover election for epoch 12 # Failover election won with 2 votes从节点争取到了另外两个主节点的投票成功上位。整个故障转移过程大概用了十几秒。如果我发现从节点没有自动接管第一件事就是检查cluster-node-timeout是不是配得太大了导致故障感知太慢第二件事是检查从节点的cluster-config-file是不是损坏或者从节点本身状态就已经是down了。5. 数据同步与持久化避免集群里的“隐形数据坑”5.1 RDB与AOF的取舍Redis集群本质上是很多个独立的Redis实例在协同工作所以持久化策略也是每个节点单独配置的。这里常见的问题是开了AOF之后重写AOF是否会阻塞主线程AOF每次写盘是always还是everysec我的建议是appendfsync everysec是平衡安全和性能的选择。极端情况下会丢1秒数据但是对绝大多数业务场景来说可以接受。不要用always除非你做的是金融级不可丢数据的业务。否则集群在高峰期的写性能会断崖式下降。主节点可以不开AOF但从节点一定要开。如果主节点也想开最好用everysec同时给磁盘IO留足缓冲。5.2 增量同步的底层机制主从之间的数据同步分全量同步和增量同步两种。全量同步发生在从节点刚接入或者主节点同步缓冲区被冲掉时主节点会通过生成RDB快照的方式把数据一次性发给从节点。增量同步则是主节点把写命令写进一个repl_backlog的环形缓冲区里从节点持续拉取。这个缓冲区的大小是个关键参数repl-backlog-size 64mb如果主节点写并发很高而repl-backlog-size太小缓冲区很可能在从节点还没拉完之前就被覆盖了导致从节点频繁要求全量重同步这在高负载下是非常致命的。生产环境里我建议至少配128MB同时监控master_repl_offset和slave_repl_offset的差差距一直拉大的话就要考虑扩容了。6. 实际运维经验生产环境中我踩过的Redis集群坑6.1 网络抖动与脑裂最开始用Redis集群的时候我一度觉得自动故障转移是万能的。后来一次机房网络闪断几十秒的抖动直接触发了多个主节点同时判定对方下线。结果就是集群里出现了两个“主”各写各的等网络恢复之后为了数据一致性选出了新的主另一个“主”就变成了从但它的数据已经跟新主对不上了。方案只有两个在Redis层面配置合理的cluster-node-timeout和cluster-require-full-coverage no的组合让网络抖动时不至于立刻触发大规模failover。业务层面做缓存时本身就允许Redis有短暂的数据回退或者丢失这样就不怕脑裂带来的覆盖。把Redis当缓存用不要当数据库用这是最保险的认知。6.2 手动维护槽位时数据访问中断之前做reshard迁移大量槽位的时候我观察到业务侧出现了明显的访问超时。后来定位发现新版Redis在槽位迁移的过程中对于正在迁移的slot会返回ASK错误。懂协议的客户端能自动识别并重定向但旧版本的客户端库如果不支持就会出现间歇性失败。所以大范围reshard尽量选在业务低峰期并且确保客户端依赖的Redis库版本足够新。6.3 集群模式下scan命令的效率问题线上排查问题的时候我习惯用scan命令遍历某个前缀的key。但集群模式下scan是单个节点视角的你连7000只能扫到7000上的slot。所以要遍历整个集群你得挨个节点去scan。我写过一个简单的小循环脚本连上每个master执行scan 0 count 1000 match user:*然后把结果汇总。虽然繁琐但对数据量没那么大的集群完全够用。更专业的做法是使用redis-cli的--cluster scan参数它会自动遍历所有主节点输出全局key列表。6.4 集群模式下分布式锁的实现现在不少系统用Redis做分布式锁。经典的SET lock:key unique_id NX PX 3000在单机和哨兵模式下问题不大但是集群下有个隐患如果master还没把锁信息同步到slave就挂了slave升级成master后锁就丢了。要么用Redlock这种由客户端协调多节点加锁的算法虽然它在极端情况下仍存在争议但在大多数业务场景下够用要么就别用Redis做分布式锁改用etcd或ZooKeeper。作为面试题你可以提Redlock但要说得清楚它不能百分百保证绝对安全只能降低概率。6.5 性能优化避免大key与热key集群可以把数据分到不同节点但如果一个key特别大或者一个key被大量线程同时读写它就是集群里的“单点瓶颈”。大key还会拖慢主从同步因为同步每次都要传完整的数据变更。我曾处理过一个大key引发的故障一个list里塞了几百万条消息每次LRANGE都要遍历很久。这个key的slot所在的那个节点CPU直接打满其它节点再闲也帮不上忙。解决办法只能是把大key拆小比如按时间维度拆成多个list或者用hash来分片。热key也是类似思路可以在key后加上随机后缀把访问分散到多个slot上代价是读的时候要读多份再合并。7. 面试实战这些高频的Redis集群问题你可以这么答很多人面试的时候最怕被问细节一问就露怯。我把过去几年面试别人和被面试时碰到的题目做个小总结问题回答思路Redis集群为什么是16384个槽从心跳包大小、集群规模上限、取模运算效率几个角度说保证它够用且不会浪费网络资源客户端怎么知道key在哪个节点客户端启动时拉取slot和节点的映射表本地缓存遇到MOVED错误就更新映射表遇到ASK错误就临时重定向主从切换会丢数据吗会。取决于min-replicas-to-write、同步策略、failover时机标准答案是“默认情况下可能丢”然后给出优化策略集群最多能支撑多少节点理论上是16384个主节点实际建议控制在1000以内因为Gossip消息量会随节点数上升而急剧膨胀Redis集群哨兵vs集群谁更好各有利弊。哨兵模式适用于小规模、不需要分片集群适用于大数据量自动分片。集群本身已内置可用性保障没必要再叠一套哨兵面试官问这些问题的潜台词其实是想知道你有没有真正在生产环境玩过集群而不是只看过文档。8. 最后一招集群出问题时怎么快速定位我现在排查集群问题有一套标准动作分享给你首先看cluster info。如果cluster_state:ok说明集群整体没问题如果显示fail多半是有slot没有被覆盖。再看cluster nodes。看每一个节点的角色和状态哪台是master哪台是slave有没有disconnected、fail?这样的标记。然后检查机器层面redis-cli -p port info stats看瞬时QPS、内存和网络IOredis-cli --latency看响应延迟。最后查日志。Redis的日志在集群模式下会记录选举、节点切换、槽位迁移这些关键事件很多时候比看监控还直观。如果确认是某个slot出了问题可以用cluster getkeysinslot slot 100查看这个slot下面存了哪些key快速判断是不是那个业务的数据。说到底Redis集群是一个工程问题不是一道数学题。你把数据分片、节点通信、故障切换这三件事搞明白了再在真实环境里折腾几遍面试答什么题都不虚。别背八股没有技术是只靠想象就能掌握的。先拿自己笔记本搭一个3主3从亲手杀一个主节点看它怎么抖升再亲手迁移几个槽位比你看十篇文章都管用。
返回列表