https://cloud.tencent.com/developer/article/1605813
Redis Cluster的客户端采用直连Redis的方式。
一、MOVED重定向
Redis Cluster发送redis指令时,先根据key计算出对应的slot,在根据slot从客户端的slot与node的映射表中找到对应的node,然后发送redis指令:
- 如果指令在这个node上,则处理指令;
- 如果不在这个node上,则redis会返回给客户端MOVED重定向错误,通知客户端重新请求正确的node, 这个过程称为MOVED重定向。
重定向信息中包含了key, slot,node的地址,根据这些信息,客户端就可以去请求正确的node。
在使用redis-cli时,可以加入-c参数,支持自动重定向,简化手动发起重定向的操作:
这里redis-cli自动帮我们连接到了正确的node, 这个过程是redis-cli内部维护的,他先收到了MOVED信息,然后向新node发起请求。
二、key命令执行分2步
1、计算slot
根据key的有效部分使用CRC16函数计算出散列值,在对16383取余,得到slot编号。这样的key都会映射到0~16383槽范围内:
2、查找slot对应的node
根据MOVED重定向机制,Redis客户端可以随机连接集群内任一redis节点获取其slot所在的node,这种客户端又叫Dummy(傀儡)客户端,他优点是代码实现简单,对客户端协议影响小,只需要根据重定向信息再次发送请求即可。
但他的弊端也很明显,就是需要重定向才能找到要执行命令的节点,额外增加了IO,这不是高效的方式,所以Redis Cluster通常采用Smart客户端。
三、Smart客户端
Smart客户端通过内部维护slot–>node的映射关系,本地就可以根据key找到node,从而避免额外IO,而MOVED重定向负责协助Smart客户端更新slot–>node映射。
3.1、JedisCluster操作集群的过程
首先JedisCluster初始化时,会选择一个正在运行的node, 初始化slot和node的映射关系,使用cluster slots命令完成:
Jedis解析Cluster slots结果,并缓存到本地,并为每个节点创建唯一的JedisPool连接池。映射关系在JedisClusterInfoCache类中
JedisCluster执行命令的过程:
public abstract class JedisClusterCommand<T> {
// 集群节点连接处理器
private JedisClusterConnectionHandler connectionHandler;
// 重试次数,默认5次
private int redirections;
private ThreadLocal<Jedis> askConnection = new ThreadLocal<Jedis>();
// 模板回调方法
public abstract T execute(Jedis connection);
// 利用重试机制运行命令
private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {
if (redirections <= 0) {
throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
}
Jedis connection = null;
try {
if (asking) {
// TODO: Pipeline asking with the original command to make it
// faster....
connection = askConnection.get();
connection.asking();
// if asking success, reset asking flag
asking = false;
} else {
if (tryRandomNode) {
// 随机获取活跃节点连接
connection = connectionHandler.getConnection();
} else {
// 使用 slot 缓存获取目标节点连接
connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
}
}
return execute(connection);
} catch (JedisConnectionException jce) {
if (tryRandomNode) {
// maybe all connection is down
throw jce;
}
// release current connection before recursion
releaseConnection(connection);
connection = null;
// retry with random connection
// 出现连接错误使用随机连接重试
return runWithRetries(key, redirections - 1, true, asking);
} catch (JedisRedirectionException jre) {
// if MOVED redirection occurred,
if (jre instanceof JedisMovedDataException) {
// it rebuilds cluster's slot cache
// recommended by Redis cluster specification
// 如果出现 MOVED 重定向错误 , 在连接上执行 cluster slots 命令重新初始化 slot 缓存
this.connectionHandler.renewSlotCache(connection);
}
// release current connection before recursion or renewing
releaseConnection(connection);
connection = null;
if (jre instanceof JedisAskDataException) {
asking = true;
askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));
} else if (jre instanceof JedisMovedDataException) {
} else {
throw new JedisClusterException(jre);
}
// slot 初始化后重试执行命令
// 每次命令重试对redirections参数减1
return runWithRetries(key, redirections - 1, false, asking);
} finally {
releaseConnection(connection);
}
}
private void releaseConnection(Jedis connection) {
if (connection != null) {
connection.close();
}
}
}
整个流程为:
1、根据key计算出slot, 并根据slot找到node建立连接,执行命令
2、如果连接出现错误,则使用随机连接重新执行命令,每次命令重试对redirections参数减1
3、捕获到MOVED重定向错误,使用cluster slots命令更新slots缓存
4、重复执行前3步,知道命令执行成功,或者当redirections<=0 时抛出异常。
四、ASK重定向
4.1、客户端ASK重定向流程
当slot中的数据从源节点到目标节点迁移过程中,客户端需要保证key的命令可正常执行。
例如,当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分key在源节点,而另一部分key在目标节点,这时,客户端key命令的执行流程为:
1、客户端根据本地slot与node的映射关系,发送请求到node上,如果该node上存在key对象,则直接执行并给客户端返回结果
2、如果该node上key不存在,则可能存在于目标节点,这时源节点会回复ASK重定向异常,格式如下:
(error) ASK {
slot} {
targetIP}:{
targetPort}
3、客户端收到ASK重定向指令后,先去目标节点执行一个不带参数的 asking 命令,然后在目标节点执行原理的操作请求指令。
说明:
- 客户端先执行一个 asking 指令的原因是,在迁移完成之前,按道理来说,这个slot还是不属于目标节点的,于是目标节点会跟客户端返回 -MOVED 指令,让客户端去源节点执行操作,这样就会形成“互相推脱”的重定向循环。
- asking 指令就是告诉目标节点,我的指令必须你处理,请求slot就当成是你的吧
4、如果目标节点存在这个key,就执行命令,不存在则返回不存在信息。
5、迁移会影响指令执行的效率,在正常情况下,一次请求就可以完成操作,而迁移过程中,客户端需要做3次请求(发送给源节点,asking指令到目标节点,发送给目标节点真正的处理请求)
ASK与MOVED虽然都是客户端重定向指令,但有着本质的区别:
- ASK重定向:说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只是临时性重定向,客户端不会更新slots缓存。
- MOVED重定向:说明key对应的slot已经明确到了新的节点,因此需要更新slots缓存。
4.2、slot迁移感知
如果某个slot已经迁移完了,客户端如何才能知道slot与node关系的变化呢?即何时更新客户端上slot–>node的映射关系表呢?
在RedisCluster客户端接收到-MOVED重定向后,会执行刷新缓存slot–>node映射表