4. Redis Cluster实战:数据分片机制、节点通信与故障转移、在风控中的典型用法

好,咱们进入实战环节。Redis Cluster,说白了就是解决单机内存不够、单点故障这两个问题。我最早接触风控系统时,数据量一上来,单机Redis直接OOM,那叫一个尴尬。后来上了Cluster,才算真正把心放肚子里。

4.1 数据分片机制:槽位是怎么分的?

Redis Cluster不是简单的一致性哈希。它用的是16384个哈希槽。每个key通过CRC16算法算出一个值,然后对16384取模,就知道该去哪个槽了。

核心公式: HASH_SLOT = CRC16(key) mod 16384

每个节点负责一部分槽。比如3个节点,可以平均分:节点A负责0-5460,节点B负责5461-10922,节点C负责10923-16383。

我在项目中遇到过一个问题:某个业务key的命名不规范,大量key都落到了同一个槽上,导致那个节点CPU飙高。后来我们强制要求key里加随机后缀,才把负载打散。

避坑指南: 我曾经因为没注意hash tag的用法,导致批量操作的key分散在不同节点,性能直接崩了。如果你需要保证多个key在同一个槽,可以用 {} 包裹公共部分,比如 user:{10001}:nameuser:{10001}:age 会落在同一个槽。

4.2 节点通信:Gossip协议怎么玩?

节点之间怎么知道谁活着、谁挂了?靠的是Gossip协议。每个节点每秒随机选几个节点,互相交换状态信息。说白了就是八卦传播——你告诉我,我告诉他,很快全集群都知道了。

通信消息主要有几种:

  • PING: 探测对方是否活着
  • PONG: 回复PING,顺便带上自己的状态
  • MEET: 新节点加入时,打个招呼
  • FAIL: 某个节点挂了,广播通知

嗯,这里要注意:Gossip协议不是实时的。默认情况下,一个节点挂了,可能要等十几秒甚至更久,其他节点才会感知到。所以风控场景下,如果你对故障恢复时间要求特别高,得配合哨兵或者客户端重试机制。

4.3 故障转移:主挂了怎么办?

每个主节点可以配一个或多个从节点。主挂了,从节点会顶上。流程是这样的:

  1. 集群中超过半数的主节点认为某个主节点挂了(通过Gossip传播)
  2. 从节点开始竞选,谁的复制偏移量最新,谁优先
  3. 竞选成功的从节点变成主节点,接管槽位

注意: 如果主节点和从节点同时挂了,那这部分数据就丢了。我在风控系统里,给每个主节点配了2个从节点,虽然浪费点内存,但心里踏实。

4.4 在风控中的典型用法

风控系统里,Redis Cluster最常见的三个场景:计数器、名单、限流。我一个个说。

4.4.1 计数器:实时统计用户行为

比如统计用户1分钟内登录次数。用INCR加EXPIRE,简单粗暴。

// 用户登录次数统计
String key = "login:count:user:" + userId;
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
    redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}
if (count > 5) {
    // 触发风控,限制登录
}

为什么用Cluster?因为用户量大了,单机扛不住。分片后,不同用户的key分散到不同节点,压力自然分散。

4.4.2 名单:黑名单/白名单快速匹配

风控里经常要查某个IP、手机号、设备ID是否在黑名单里。用Set或者String存,O(1)时间复杂度,爽得很。

// 黑名单检查
String key = "blacklist:ip:" + clientIp;
Boolean isBlocked = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(isBlocked)) {
    // 拒绝请求
}

我建议名单数据用单独的Redis Cluster实例,别和业务数据混在一起。为什么?因为名单更新频繁,而且一旦误操作清空,影响面太大。隔离起来,出事也好排查。

4.4.3 限流:滑动窗口与令牌桶

限流是风控的标配。我常用的两种方式:

方式 原理 适用场景
滑动窗口 用ZSET记录时间戳,统计窗口内请求数 接口限流、登录限流
令牌桶 用List存令牌,定时补充 突发流量控制

滑动窗口的代码示例:

// 滑动窗口限流
String key = "rate:limit:api:" + apiName;
long now = System.currentTimeMillis();
long windowSize = 1000L; // 1秒窗口

// 移除窗口外的记录
redisTemplate.opsForZSet().removeRangeByScore(key, 0, now - windowSize);
// 添加当前请求
redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
// 统计窗口内请求数
Long count = redisTemplate.opsForZSet().zCard(key);
if (count > 100) {
    // 触发限流
}

个人经验: 限流key的过期时间一定要设置。我曾经忘了设过期时间,结果ZSET越积越大,最后内存爆了。现在我的习惯是:每次操作完,顺手设个过期时间,比如窗口大小的2倍。

4.5 架构图:Redis Cluster在风控中的部署

下面这张图展示了我常用的风控Redis Cluster部署架构。你看,数据分片、主从复制、故障转移,一目了然。

风控系统Redis Cluster部署架构 风控应用客户端 分片1 (槽0-5460) 主节点A 从节点A1 分片2 (槽5461-10922) 主节点B 从节点B1 分片3 (槽10923-16383) 主节点C 从节点C1 Gossip协议通信 故障转移机制 当主节点A挂掉 → 从节点A1通过选举成为新主节点 → 接管槽0-5460 集群自动更新槽位映射,客户端无需感知 典型用法:计数器(INCR) | 名单(Set) | 限流(ZSET滑动窗口)

你看,整个架构其实不复杂。客户端通过CRC16算槽位,直接连到对应的主节点。主节点挂了,从节点顶上。数据分片、故障转移,都是自动完成的。

总结一下: Redis Cluster在风控系统里,核心价值就三个——水平扩展解决内存瓶颈,自动故障转移保证高可用,数据分片分散压力。但要注意,跨节点的批量操作和事务支持有限,设计时得提前规划好key的分布。

好了,这一章就到这里。下一章咱们聊聊Redis Cluster的运维监控和性能调优,那才是真正考验架构师功底的地方。

专注资料整理