你觉得RedisCluster的从节点可以进行读操作嘛?
1. 前言
数据库读写分离是应用程序性能优化的一个手段,读写分离原理:master
节点负责应用的写操作(也处理实时性要求高的读场景)、slave
节点负责应用程序的读操作。RedisCluster
同样包含master
、slave
节点,其中的slave
节点是否和数据库的slave
节点一样可以处理读请求,从而实现读写分离?
2.RedisCluster
原理
public void afterPropertiesSet() {
// 1.如果配置了连接池并且不是集群模式,则直接创建连接池
if (getUsePool() && !isRedisClusterAware()) {
this.pool = createPool();
}
// 2.如果是集群模式,则创建集群
if (isRedisClusterAware()) {
this.cluster = createCluster();
this.topologyProvider = createTopologyProvider(this.cluster);
this.clusterCommandExecutor = new ClusterCommandExecutor(this.topologyProvider,
new JedisClusterConnection.JedisClusterNodeResourceProvider(this.cluster, this.topologyProvider),
EXCEPTION_TRANSLATION);
}
}
2.1 连接节点
private void initializeSlotsCache(Set<HostAndPort> startNodes,
int connectionTimeout, int soTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
// 1.遍历所有的集群节点
for (HostAndPort hostAndPort : startNodes) {
Jedis jedis = null;
try {
// 2.根据节点信息创建Jedis对象,用于操作redis
jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
// 3.初始化slot
cache.discoverClusterNodesAndSlots(jedis);
break;
} catch (JedisConnectionException e) {
// try next nodes
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
2.2 初始化slot
在分析初始化slot
代码之前,先来执行一下cluster slots
命令
cluster slots
会返回slot
的开始、结束值,所在master
的ip、端口、id以及slave
的ip、端口、id。了解返回结果后再来分析源码会相当轻松
public void discoverClusterNodesAndSlots(Jedis jedis) {
w.lock();
try {
reset();
// 1.执行clster slots命令
List<Object> slots = jedis.clusterSlots();
// 2.遍历命令返回结果
for (Object slotInfoObj : slots) {
List<Object> slotInfo = (List<Object>) slotInfoObj;
if (slotInfo.size() <= MASTER_NODE_INDEX) {
continue;
}
// 3.获取对应的slot数量,例如第一个对应的slot数量为501
List<Integer> slotNums = getAssignedSlotArray(slotInfo);
// hostInfos
int size = slotInfo.size();
// 4.从master信息开始处理
for (int i = MASTER_NODE_INDEX; i < size; i++) {
// 5.获取对应的ip、端口、id
List<Object> hostInfos = (List<Object>) slotInfo.get(i);
if (hostInfos.isEmpty()) {
continue;
}
// 6.根据ip、端口组装HostAndPort
HostAndPort targetNode = generateHostAndPort(hostInfos);
// 7.存放ip:端口与JedisPool的映射关系
setupNodeIfNotExist(targetNode);
// 8.如果是master,存放slot与JedisPool的映射关系,因此JedisPool创建的连接只能操作mater节点
if (i == MASTER_NODE_INDEX) {
assignSlotsToNode(slotNums, targetNode);
}
}
}
} finally {
w.unlock();
}
}
2.3 ip:端口与JedisPool的映射关系
2.4 slot与JedisPool的映射关系
2.5 计算key对应的slot
public static int getSlot(byte[] key) {
if (key == null) {
throw new JedisClusterOperationException("Slot calculation of null is impossible");
}
int s = -1;
int e = -1;
boolean sFound = false;
for (int i = 0; i < key.length; i++) {
if (key[i] == '{' && !sFound) {
s = i;
sFound = true;
}
if (key[i] == '}' && sFound) {
e = i;
break;
}
}
if (s > -1 && e > -1 && e != s + 1) {
return getCRC16(key, s + 1, e) & (16384 - 1);
}
return getCRC16(key) & (16384 - 1);
}
执行命令时对key进行CRC
并进行与运算得到slot
值
2.6 获取对应的JedisPool
public JedisPool getSlotPool(int slot) {
r.lock();
try {
return slots.get(slot);
} finally {
r.unlock();
}
}
有了slot
值,就可以从2.4
章节的映射关系中获取对应的JedisPool
,得到的JedisPool
是根据master
的地址构建的,因此可以知晓RedisCluster
从节点是不提供读服务,只做高可用。
转载自:https://juejin.cn/post/6961980011330928671