Skip to content

![[Pasted image 20251231141326.png]]

您的提问非常专业且切中要害。您的观点是完全正确的。

在 Redis Cluster(集群模式)下,确实存在 Key 必须落在同一个 Slot(哈希槽)才能在同一个 Lua 脚本中执行 的限制。如果不处理这个问题,V3 策略中的“无锁化 Lua 脚本”确实会失效。

以下是针对这个问题的详细分析、解决方案以及潜在风险:

1. 问题根源:Redis Cluster 的分片机制

Redis Cluster 将数据划分为 16384 个 Slot。默认情况下,Redis 使用 CRC16 算法对 Key 进行哈希计算 CRC16(key) % 16384 来决定该 Key 存储在哪个节点上。

  • 如果 Key A 算出在 Node 1。
  • 如果 Key B 算出在 Node 2。
  • 当 Lua 脚本试图同时操作 Key AKey B 时,Redis 无法在一次请求中跨节点协调这两个 Key,会抛出 CROSSSLOT Keys in request don't hash to the same slot 错误。

2. 解决方案:Hash Tag(哈希标签)

要解决这个问题,必须强制所有参与 Lua 脚本计算的 Key 落在同一个 Slot 中。Redis 提供了 Hash Tag 机制。

原理: 如果 Key 中包含 {},Redis 只会计算 {} 内部字符串的哈希值来定位 Slot。

改造您的 Key 设计: 您简历中提到的 Key 设计如下:

d_mai_program_ticket_remain_number_resolution_节目id_票档idd_mai_program_seat_no_sold_resolution_hash_节目id_票档id

为了保证针对同一个节目的操作都在同一个节点,您需要在 Key 中加入 {}

方案 A:以“节目ID”为锚点(粗粒度,推荐用于涉及全节目的校验)

将 Key 修改为:

  • 余票:d_mai_program_ticket_remain_{节目id}_票档id
  • 座位:d_mai_program_seat_{节目id}_票档id

这样,无论票档 ID 是多少,只要 节目 ID 相同,CRC16(节目id) 的结果就相同,所有关于该节目的 Key 都会存储在同一个 Redis 节点上。

方案 B:以“节目ID + 票档ID”为锚点(细粒度,推荐用于仅涉及单票档的购买)

如果您的 Lua 脚本逻辑完全独立于其他票档(即购买一等票不需要检查二等票的状态),则可以将 Hash Tag 设为 {节目id_票档id}

  • Key:d_mai_program_{节目id_票档id}_remain

这通常更优,因为它能把同一个热门节目的不同票档分散到不同节点,减轻单点压力。但前提是业务逻辑不需要跨票档(例如不需要校验“该用户在该节目下总共只能买2张,不限票档”)。

3. V3 策略的完善(面试应答话术)

在面试中,当面试官问到这个问题,或者您主动介绍 V3 策略时,可以这样补充:

“您说得非常对。在设计 V3 无锁化策略时,我也考虑到了 Redis Cluster 的 CROSSSLOT 问题。

为了确保 Lua 脚本能原子性地操作‘库存 Key’和‘座位 Key’,我使用了 Redis 的 Hash Tag 机制。

我将所有相关的 Key 进行了改造,例如在 Key 中嵌入 {ProgramID}。这样 Redis 在计算哈希槽时,只会根据 {} 内的 ProgramID 进行计算,从而强制同一个节目的所有相关数据(库存、座位表)都落到同一个 Redis 节点上。

这样既利用了集群的容量优势,又保证了单次购票请求在单个节点内的原子性执行,避免了分布式锁的开销。”

4. 引入的新风险:数据倾斜(Data Skew)与热点 Key

使用了 Hash Tag 后,必须面对一个新的挑战:热点问题(Hot Key)

  • 现象:如果有一个超级明星的演唱会(热门 ProgramID),由于使用了 {ProgramID} 作为 Hash Tag,该演唱会所有的库存、座位读写请求都会打向同一个 Redis 节点
  • 后果:该特定节点的 CPU 和网络可能被打满,而集群中其他节点却很空闲。

应对思路(进阶回答):

  1. 细化 Hash Tag 粒度:尽量按 {节目ID_票档ID} 分片。这样一等票在 Node A,二等票在 Node B,分担了压力。
  2. 本地缓存(Caffeine):正如您简历中提到的,利用本地缓存挡住大量的“读”请求(如查询余票、查询座位图),只有真正的“写”请求(下单)才打到 Redis。
  3. 业务分片(大招):如果是极度热门的秒杀(如周杰伦演唱会),甚至可以将同一个票档的库存拆分成多份(Key_1, Key_2...),但这会极大增加 Lua 脚本和业务的复杂度,通常对于常规高并发售票,配合本地缓存和限流已经足够。

总结

您的 V3 策略是可行的,但必须配合 Hash Tag 设计才能在 Redis Cluster 中落地。指出这一点不仅不会减分,反而证明您对 Redis 底层原理(集群分片、CRC16 算法)有深刻的理解。