Skip to content

Daily Study

更新: 2/1/2026 字数: 0 字 时长: 0 分钟

Daily Plan

#todo

Redis回顾

高QPS场景设计

从架构层、数据层、网络层、稳定性四个层面进行设计:

架构层,单机QPS的极限是8万-10万,要达到百万级,必须进行分片。

  • Redis Cluster:将数据划分为16384个槽,分布在多节点上。Cluster设计建议3主3从起步。
  • 读写分离:针对读多写少的场景,1主多从设计,但是这里主从复制存在延迟和额外的开销。

数据层:避免阻塞,Redis核心命令执行是单线程,在高qps下,要避免耗时命令,造成排队,引发雪崩

  • 解决 Hot Key:Local Cache/本地缓存,在应用服务器加一层缓存(例如Go的sync.Map 或 BigCache)将 hot_key 复制为 hot_key1, hot_key2, hot_key3 将流量分散到不同节点,客户端随机访问。
  • 解决 Big Key:拆分(如一个大 Hash user_list 拆分成 user_list_001, user_list_002) 异步删除,使用 UNLINK 进行删除(让后台线程来回收内存),严禁全量查询

客户端层:减少RTT,Reidis 在内存中查询很快,瓶颈往往在网络I/O,一次 TCP 往返(RTT)通常需要 0.2ms - 1ms。 如果发一个命令等一个回复,QPS 实际上被网络延迟锁死了。

  • 连接池:TCP 三次握手很慢。必须使用长连接池。
  • 管道:客户端将一批命令(比如 100 个 SET)打包,一次性发给 Redis,Redis 执行完后一次性返回结果。将100次网络 IO 变为 1次,吞吐量提升10倍以上。
  • Lua Script:将复杂的逻辑(如“检查库存 -> 扣减库存 -> 记录日志”)封装成 Lua 脚本发送给 Redis。利用Lua的原子性,整个脚本作为一个整体执行,不用担心并发竞态,多条命令合并为一次请求,但是脚本不能执行太久,否则会阻塞主线程。
  • Redis 6.0 多线程 IO:将 网络读写 (Socket Read/Write)协议解析 任务交给一组后台 IO 线程处理。命令执行 依然是单线程的(保证了线程安全,无需锁)

稳定性:避免缓存雪崩与击穿

  • 缓存穿透 (查不存在的数据):
    • 使用 Bloom Filter (布隆过滤器) 拦截不存在的 ID。
    • 缓存空对象(key: null, TTL: 30s)
  • 缓存击穿 (热点 Key 过期):
    • 互斥锁:当发现缓存失效时,只让一个线程去查 DB,其他线程等待。
    • 逻辑过期:数据里包含一个“逻辑过期时间”,物理 TTL 设为永不过期。后台异步刷新数据。
  • 缓存雪崩 (大量 Key 同时过期):随机 TTL:给过期时间加一个随机值(如 10分钟 + random(60s))让过期时间分散开。

总结

  • 架构层面:我会采用 Redis Cluster 进行水平分片,将流量分散到多台物理机。对于读多写少的场景,设计1主多从。
  • 网络层面:鉴于网络 RTT 是主要瓶颈,我会大量使用 Pipeline 批量操作,并利用 Lua 脚本 将复杂的多步逻辑合并为一次网络请求,保证原子性的同时减少 IO。如果是 Redis 6.0+,我会开启 Threaded I/O 来分担主线程的网络压力。
  • 数据层面:坚决避免 Big Key,使用 UNLINK 异步删除;对于 Hot Key,我会引入本地缓存做一级屏障,避免单分片热点倾斜。
  • 稳定性:为了防止缓存击穿和雪崩,我会使用 Bloom Filter随机 TTL 策略,并配置合理的连接池参数,确保系统的高可用。

Redis cluster原理

目的是利用分布式架构来解决单机内存限制和单点故障问题,分为数据分片,节点通信,客户端路由和高可用与故障转移四个核心原理。

数据分片

引入了哈希槽的概念(升级版的一致性哈希方法)

分片

  • 全集:Redis Cluster 定义了 16384 ($2^{14}$) 个逻辑槽位。
  • 分配:这些槽位被平均(或按权重)分配给集群中的所有 Master 节点。例如:节点 A 负责 0-5000,节点 B 负责 5001-10000,节点 C 负责 10001-16383。

映射算法:当客户端存取一个 Key 时,Redis 如何知道去哪个节点找?

  • 公式:Slot = CRC16(key) % 16384
  • Hash Tag :如果 Key 中包含 {},则只计算 {} 内部的字符串的 Hash。作用:保证不同的 Key(如 user:{100}:profileuser:{100}:orders)会被计算到同一个 Slot,从而落到同一个节点上,以便支持批量操作(MGET)或 Lua 脚本。

节点通信

Redis Cluster 是 去中心化的 P2P 架构。没有像 Zookeeper 或 Sentinel 那样的中心管理节点。

  1. 集群总线:每个节点除了监听客户端端口(如 6379),还会开启一个 +10000 的端口(如 16379)使用二进制协议,带宽更低,处理更快。节点间通过这个端口互发 Gossip 消息。
  2. Gossip 协议作用:状态交换:我是谁、我负责哪些 Slot、我的 Slave 是谁。故障检测:我发现某个节点 PFAIL 了,你们确认一下它是不是真挂了 (FAIL)。

客户端路由

分为 MOVED 和 ASK 两种请求:

  • MOVED是永久重定向,当 Key 所属的 Slot 明确 归属于另一个节点 B,但客户端请求发给了节点 A 时,会回复客户端此类型消息,客户端收到后会更新本地的 Slot 表。
  • ASK是临时重定向,当集群正在扩容/缩容,Slot 正在从 A 迁移到 B。Key 可能在 A,也可能在 B。客户端不会更新本地映射表,下一次还是会先访问 A。

高可用与故障转移

  • 判定下线:
    • PFAIL (疑似下线):单节点视角。我 ping 不通你。
    • FAIL (确诊下线):集群内 半数以上 的 Master 都标记你为 PFAIL,通过 Gossip 传播后,你被标记为 FAIL。
  • 选举新主 :
    • 挂掉的 Master 的 Slave 发现主挂了。
    • Slave 发起选举,请求其他 Master 给自己投票。
    • 拥有最新数据的 Slave 更有可能当选。
    • 当选后,接管旧主的 Slot,广播状态,集群恢复。

总结

手动构建步骤

  1. 配置开启:在 redis.conf 中设置 cluster-enabled yes
  2. 节点握手 :在节点 A 上执行:CLUSTER MEET <ip_B> <port_B>。让孤立的节点互相认识,形成一个空集群。
  3. 分配槽位 :最关键的一步。集群刚建立时是不可用的(FAIL 状态),因为 16384 个 Slot 还没分。需要写脚本,给节点 A 分配 0-5460,节点 B 分配 5461-10922... 命令:CLUSTER ADDSLOTS 0 1 2 ... 5460
  4. 指定主从 :在从节点执行:CLUSTER REPLICATE <node_id_of_master>

Redis Cluster 的构建核心在于 Sharding(分片)Gossip(通信)

  1. 它利用 16384 个 Hash Slots 将数据解耦,通过 CRC16 算法实现无中心化的数据分布。
  2. 客户端通过 MOVEDASK 机制实现智能路由,ASK 专门处理数据迁移时的临时状态。
  3. 节点间通过 16379 端口 使用 Gossip 协议 交换状态,实现了去中心化的故障检测和自动 Failover。

在实际生产中,我们通常使用 redis-cli --cluster 命令一键搭建,并确保 Master 节点分布在不同的物理机架上以实现容灾。

菜就多练

本站访客数 人次 本站总访问量