知识卡片

分布式场景下MSET失去原子语义与hashtag解决多key路由

普通读书笔记卡

内容

单机Redis上MSET/MGET是原子操作,但一旦数据被水平拆分到多个分片,这个原子性保证就必然被打破——因为不同key很可能落在不同的存储节点上,一次MSET要同时写多个节点,中途某个节点失败就会出现部分key成功、部分失败的中间状态,Codis没办法在Proxy层伪造出跨节点的原子性,只能如实告诉用户MSET/MGET在分布式部署下退化成了非原子的批量操作。这是一条具有普遍意义的规律:单机上天然具备的原子性、事务性保证,一旦引入分片,几乎全部需要重新审视,不能想当然地认为分布式版本和单机版本行为完全一致。与之相关的另一个典型问题是Lua脚本:Lua脚本内部经常需要操作多个key,但如果这些key按普通哈希规则分散到不同分片,脚本本身就无法执行(Redis的Lua脚本要求涉及的key必须在同一个节点上)。Codis的解决办法是引入hashtag机制——只要key中包含用花括号包裹的子串(如user_{1000}中的{1000}),分片路由就只依据花括号内的部分计算哈希,而不是整个key,这样业务只要把需要放在同一个Lua脚本里操作的key用相同的hashtag命名,就能保证它们始终落在同一分片上,从而让跨key的Lua脚本得以正常执行。这个思路的本质,是把”路由粒度”从”单个key”人为提升到”一组相关key”,用命名约定换取局部的原子操作能力。

参考来源

- 位置:《高可用架构(第1卷)》第2章《高可用架构原理与分布式实践》"2.1 Codis作者细说分布式Redis架构设计"节,"2.1.2 Codis的核心设计"(源文件:_epub-src/OEBPS/Text/Chapter2_1_3.xhtml) - 结论依据:原文说明"MSET、MGET等操作原来是原子的,但是在分布式的情况下,是没有办法保证的",以及"如果一个Lua脚本涉及的key不在一个数据分片上……可以通过加入hashtag的方式,将这一部分key都路由到同一台机器上",共同支撑本卡片结论。 - 原始内容:MSET、MGET等操作原来是原子的,但是在分布式的情况下,是没有办法保证的……可以通过在key中加入hashtag(比如user_{1000}),这样分片时只根据花括号里的内容计算哈希,从而保证这些key落在同一个分片上。