Redis-Cluster集群模式下Redis客户端如何获得slot的路由信息
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc
1. 引言
我们都知道在Redis-Cluster集群模式下,集群中有18634个slot,slot分布在集群多个实例上,当执行一个Command
时,Redis客户端会提取Command
中的key
根据下面的算法得出key所属的slot
slot=CRC16(key)&16383
在根据客户端中的路由表,找到slot所在的Redis实例
这里的路由表存储的就是
slot -> redis-instance
那么问题来了redis客户端是如何得到这个路由表的呢?
2. 分析
下面以go-redis/redis的代码为例,谈谈Redis客户端如何获取和维护slot路由信息。
2.1 存储结构
type clusterClient struct {
opt *ClusterOptions
nodes *clusterNodes
state *clusterStateHolder // 在这里
cmdsInfoCache *cmdsInfoCache //nolint:structcheck
}
type clusterState struct {
nodes *clusterNodes
Masters []*clusterNode
Slaves []*clusterNode
slots []*clusterSlot // 路由信息存储在这里
generation uint32
createdAt time.Time
}
type clusterSlot struct {
start, end int
nodes []*clusterNode
}
2.2 命令执行过程
- 1)通过key计算出对应slot
- 2)通过路由表查找到对应的node信息
- 3)向node发送CMD
其实第2步根据slot从clusterState
中查询对应clusterNode
func (c *clusterState) slotNodes(slot int) []*clusterNode {
i := sort.Search(len(c.slots), func(i int) bool {
return c.slots[i].end >= slot
})
if i >= len(c.slots) {
return nil
}
x := c.slots[i]
if slot >= x.start && slot <= x.end {
return x.nodes
}
return nil
}
2.3 初始化slot路由信息
在处理任意Command时,如果路由信息为空,会触发以下动作
ClusterClient.process() -> ClusterClient.cmdNode() -> clusterStateHolder.Get() -> clusterStateHolder.Reload() -> ClusterClient.loadState()
最终调用了
func (c cmdable) ClusterSlots(ctx context.Context) *ClusterSlotsCmd {
cmd := NewClusterSlotsCmd(ctx, "cluster", "slots")
_ = c(ctx, cmd)
return cmd
}
CLUSTER SLOTS的返回大体如下:
192.168.132.114:6403> cluster slots
1) 1) (integer) 0 // Start slot range
2) (integer) 2730 // End slot range
3) 1) "192.168.134.95" // Redis实例-IP(主)
2) (integer) 6403 // Redis实例-端口
3) "702608b2556413c6f8927ef06e86e5c0e869a07d" // node ID
4) 1) "192.168.134.109" // Redis实例-IP(从)
2) (integer) 6403 // Redis实例-端口
3) "d950357fd1e0010646b32d3397ced3e52b1322fd"
2) 1) (integer) 8192
2) (integer) 10922
3) 1) "192.168.134.95"
2) (integer) 6403
3) "702608b2556413c6f8927ef06e86e5c0e869a07d"
4) 1) "192.168.134.109"
2) (integer) 6403
3) "d950357fd1e0010646b32d3397ced3e52b1322fd"
3) 1) (integer) 2731
2) (integer) 8191
3) 1) "192.168.132.114"
2) (integer) 6403
3) "fccdf478a66f2a11713111b5567e3afa2be6c3ab"
4) 1) "192.168.132.123"
2) (integer) 6403
3) "83600e144b488ad324235d84dca9ae6f02928715"
4) 1) (integer) 10923
2) (integer) 16383
3) 1) "192.168.132.124"
2) (integer) 6403
3) "f4347dfea9bf425281df393976b02547fa45c7ce"
4) 1) "192.168.132.128"
2) (integer) 6403
3) "aba3f6cda55c0595a82d0ddea864a58c21008fb3"
start | end | node |
---|---|---|
0 | 2730 | 192.168.134.95/192.168.134.109 |
2731 | 8191 | 192.168.132.114/192.168.132.123 |
8192 | 10922 | 192.168.134.95/192.168.134.109 |
10923 | 16383 | 192.168.132.124/192.168.132.128 |
问题来了,在成功获取slot的路由信息后,集群中slot的分布情况是会发生变化的。比如Redis集群的管理员通过指令移动一部分slot。客户端怎么感知到这种变化?
(redis1)$ redis-cli --cluster reshard 192.168.11.131:6379 \
--cluster-from node1,nod2,node3 \
--cluster-to node4 \
--cluster-slots 3276 \
--cluster-yes
2.4 更新slot路由信息
1) 当slot路由信息已经超过10秒没有更新了,将触发LazyReload()
func (c *clusterStateHolder) Get(ctx context.Context) (*clusterState, error) {
...
if time.Since(state.createdAt) > 10*time.Second {
c.LazyReload()
}
...
}
2) 执行Command的过程中,如果收到MOVED或ASK重定向,将触发LazyReload()
从moved的ERROR中可以获取slot所在的新的地址, 同时触发LazyReload()
func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
...
moved, ask, addr = isMovedError(lastErr)
if moved || ask {
c.state.LazyReload()
var err error
node, err = c.nodes.GetOrCreate(addr)
if err != nil {
return err
}
continue
}
...
}
192.168.132.114:6403> get a
(error) MOVED 15495 192.168.132.124:6403
MOVED重定向过程
3. 参考资料
1.CLUSTER SLOTS命令
2.Redis 5.0 redis-cli --cluster help说明
3.Redis Cluster 重定向问题 - Moved/Ask重定向