Redis 集群中,数据读写时定位到具体节点的过程依赖于 哈希槽(Hash Slot) 机制和 客户端的路由逻辑。以下是详细的工作原理和流程:
一、核心机制
1. 哈希槽(Hash Slot)
- Redis 集群将数据划分为 16384 个哈希槽(编号 0~16383)。
- 每个键(Key)通过 CRC16 算法 计算哈希值,再对 16384 取模,得到对应的槽位:
def get_slot(key): hash_value = crc16(key) # CRC16 算法 slot = hash_value % 16384 return slot - 示例:
- 键
user:1001的有效部分为user:1001(若未使用{}标签),计算后得到槽位X。 - 键
{user102}:last.name的有效部分为user102,计算后得到槽位Y。
- 键
2. 槽位分配
- 每个主节点负责一部分槽位(例如 3 个主节点可能分别负责 05460、546110922、10923~16383)。
- 槽位与节点的映射关系由 集群元数据 维护,节点间通过 Gossip 协议同步该信息。
二、客户端定位数据的流程
1. 客户端直连节点
- 客户端直接连接集群中的任意节点(如
192.168.1.101:6379)。 - 客户端首次请求时,会缓存 槽位→节点 的映射关系(称为 槽位表)。
2. 计算键对应的槽位
- 客户端根据键名的有效部分,使用 CRC16 算法计算槽位:
redis-cli -c -h 192.168.1.101 -p 6379 127.0.0.1:6379> CLUSTER KEYSLOT "user:1001" (integer) 9189
3. 查询槽位表
- 客户端从本地缓存的槽位表中查找该槽位对应的节点地址(如
192.168.1.102:6379)。 - 如果未找到或缓存过期,客户端会向当前连接的节点发送请求,节点会返回 MOVED 错误,并附上正确的节点地址。
4. 处理重定向
- MOVED 重定向:
- 表示槽位的归属节点已变更(如扩容或故障转移)。
- 客户端更新本地槽位表,并重试请求到新的节点。
- 示例:
127.0.0.1:6379> GET user:1001 (error) MOVED 9189 192.168.1.102:6379
- ASK 重定向:
- 表示槽位正在迁移(如数据迁移中)。
- 客户端临时向目标节点发送
ASKING命令,再执行操作(不更新槽位表)。 - 示例:
127.0.0.1:6379> GET user:1001 (error) ASK 9189 192.168.1.102:6379
5. 最终定位节点
- 客户端将请求发送到正确的节点,节点处理数据读写操作。
三、优化策略
1. 哈希标签(Hash Tag)
- 通过
{}显式指定键的有效部分,确保相关键分配到同一槽位:String sessionKey = "user:{1001}:session"; String profileKey = "user:{1001}:profile";- 两个键的有效部分均为
1001,因此分配到同一槽位,支持多键操作(如MGET)。
- 两个键的有效部分均为
2. 客户端缓存槽位表
- 客户端(如 Jedis、Lettuce)会缓存槽位表,减少频繁查询带来的重定向开销。
- 示例(Jedis 源码):
public class JedisSlotCache { private volatile Map<Integer, JedisPool> slots = new HashMap<>(); public Jedis getJedisByKey(String key) { int slot = JedisClusterCRC16.getSlot(key); return slots.get(slot).getResource(); } }
3. 热点数据分片
- 对高频访问的键添加随机后缀,分散压力:
int shard = userId.hashCode() % 10; String shardedKey = "hot:user:" + userId + ":" + shard;
四、集群扩容与迁移
1. 槽位迁移
- 新增节点时,通过
CLUSTER SLOTS命令获取槽位分配,逐步将槽位从旧节点迁移至新节点。 - 迁移期间,客户端可能收到
ASK重定向,需临时向目标节点发送请求。
2. 客户端自动感知
- 客户端通过
MOVED错误更新槽位表,无需人工干预。 - 大厂实践(如阿里云):
- 使用 ZooKeeper 或 etcd 作为槽位配置中心,实现动态更新。
- 对批量操作(如
MGET)进行智能分组,减少跨节点请求。
五、实际应用示例
1. 数据存储位置查询
- 使用
CLUSTER SLOTS查看槽位分布:127.0.0.1:6379> CLUSTER SLOTS 1) 1) (integer) 0 2) (integer) 5460 3) 1) "192.168.1.101" 2) (integer) 6379 2) 1) (integer) 5461 2) (integer) 10922 3) 1) "192.168.1.102" 2) (integer) 6379
2. Lua 脚本保证原子性
- 对跨槽位的多键操作,使用 Lua 脚本强制绑定到同一节点:
EVAL "redis.call('SET', KEYS[1], ARGV[1]); redis.call('EXPIRE', KEYS[1], ARGV[2])" 1 {user}:1001:lock true 30000
六、总结
Redis 集群通过 哈希槽 和 客户端路由 实现数据定位:
- 客户端计算键的槽位,并查询本地槽位表定位节点。
- 若槽位表过期,通过
MOVED或ASK重定向更新映射。 - 使用哈希标签、缓存优化和 Lua 脚本提升性能与一致性。
- 扩容时通过槽位迁移实现平滑扩展,客户端自动感知变化。
这种方式既保证了高可用性,又避免了代理层的性能瓶颈,是分布式 Redis 的核心设计之一。
328

被折叠的 条评论
为什么被折叠?



