最近在使用 JedisCluster 时, 对 集群模式下的 Redis key 的设置策略比较感兴趣,跟踪了一下执行 set 方法的 主要源码,特此记录一下。
跟踪的源码版本
pom.xml
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
追踪的源头代码:
package com.yaobaling.td.blacklist.redis;
import com.yaobaling.td.blacklist.config.ConfigCenter;
import org.junit.Test;
/**
* Created by szh on 2018/10/11.
*/
public class RedisClusterTest {
@Test
public void testCluster() throws Exception {
RedisCluster redisCluster = new RedisCluster(ConfigCenter.getInstance().getRedisConfig());
redisCluster.getClusterConn().set("test_string", "2222");
String tmp = redisCluster.getClusterConn().get("test_string");
System.out.println(tmp);
}
}
我们主要追踪这一行代码:
redisCluster.getClusterConn().set("test_string", "2222");
首先,点击 ctrl 点击 set 方法,
可以看到执行了,如下代码:
public String set(final String key, final String value) {
return (String)(new JedisClusterCommand<String>(this.connectionHandler, this.maxAttempts) {
public String execute(Jedis connection) {
return connection.set(key, value);
}
}).run(key);
}
我们再跟中下 run 方法:
可以看到方法内部,主要执行如下方法 :
this.runWithRetries(SafeEncoder.encode(key), this.maxAttempts, false, false);
我们继续 跟踪 runWithRetries 方法 与 SafeEncoder.encode(key) 方法:
-------------------------------------------------------------------------
首先我们跟踪 runWithRetries 方法 :
private T runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking) {
if(attempts <= 0) {
throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
} else {
Jedis connection = null;
Object var7;
try {
if(asking) {
connection = (Jedis)this.askConnection.get();
connection.asking();
asking = false;
} else if(tryRandomNode) {
connection = this.connectionHandler.getConnection();
} else {
connection = this.connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
}
Object var6 = this.execute(connection);
return var6;
} catch (JedisNoReachableClusterNodeException var13) {
throw var13;
} catch (JedisConnectionException var14) {
this.releaseConnection(connection);
connection = null;
if(attempts <= 1) {
this.connectionHandler.renewSlotCache();
throw var14;
}
var7 = this.runWithRetries(key, attempts - 1, tryRandomNode, asking);
} catch (JedisRedirectionException var15) {
if(var15 instanceof JedisMovedDataException) {
this.connectionHandler.renewSlotCache(connection);
}
this.releaseConnection(connection);
connection = null;
if(var15 instanceof JedisAskDataException) {
asking = true;
this.askConnection.set(this.connectionHandler.getConnectionFromNode(var15.getTargetNode()));
} else if(!(var15 instanceof JedisMovedDataException)) {
throw new JedisClusterException(var15);
}
var7 = this.runWithRetries(key, attempts - 1, false, asking);
return var7;
} finally {
this.releaseConnection(connection);
}
return var7;
}
}
可以看到 4个参数 分别为以下4个:
runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking)
byte[] key : SafeEncoder.encode(key)
int attempts : this.maxAttempts
boolean tryRandomNode : false
boolean asking : false
我们再跟踪下
this.connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
这个方法,this.connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
从这里,我们可以看到,获取指定的key 存放的位置,是从一个暂存池中获取到指定的连接的。
this.connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)) :
ctrl 点击来,可以看到这是个 抽象类:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package redis.clients.jedis;
import java.io.Closeable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.exceptions.JedisConnectionException;
public abstract class JedisClusterConnectionHandler implements Closeable {
protected final JedisClusterInfoCache cache;
public JedisClusterConnectionHandler(Set<HostAndPort> nodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password) {
this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, password);
this.initializeSlotsCache(nodes, poolConfig, password);
}
abstract Jedis getConnection();
abstract Jedis getConnectionFromSlot(int var1);
public Jedis getConnectionFromNode(HostAndPort node) {
return this.cache.setupNodeIfNotExist(node).getResource();
}
public Map<String, JedisPool> getNodes() {
return this.cache.getNodes();
}
private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig, String password) {
Iterator var4 = startNodes.iterator();
while(var4.hasNext()) {
HostAndPort hostAndPort = (HostAndPort)var4.next();
Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort());
if(password != null) {
jedis.auth(password);
}
try {
this.cache.discoverClusterNodesAndSlots(jedis);
break;
} catch (JedisConnectionException var11) {
;
} finally {
if(jedis != null) {
jedis.close();
}
}
}
}
public void renewSlotCache() {
this.cache.renewClusterSlots((Jedis)null);
}
public void renewSlotCache(Jedis jedis) {
this.cache.renewClusterSlots(jedis);
}
public void close() {
this.cache.reset();
}
}
ctrl + alt + b 看下抽象方法的具体实现:
abstract Jedis getConnectionFromSlot(int var1);
可以看到具体的方法,由以下类进行实现:
package redis.clients.jedis;
public class JedisSlotBasedConnectionHandler extends JedisClusterConnectionHandler {
public Jedis getConnectionFromSlot(int slot) {
JedisPool connectionPool = this.cache.getSlotPool(slot);
if(connectionPool != null) {
return connectionPool.getResource();
} else {
this.renewSlotCache();
connectionPool = this.cache.getSlotPool(slot);
return connectionPool != null?connectionPool.getResource():this.getConnection();
}
}
----------------------------------------------------------------
再跟踪下
SafeEncoder.encode(key)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package redis.clients.util;
import java.io.UnsupportedEncodingException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.exceptions.JedisException;
public final class SafeEncoder {
private SafeEncoder() {
throw new InstantiationError("Must not instantiate this class");
}
public static byte[][] encodeMany(String... strs) {
byte[][] many = new byte[strs.length][];
for(int i = 0; i < strs.length; ++i) {
many[i] = encode(strs[i]);
}
return many;
}
public static byte[] encode(String str) {
try {
if(str == null) {
throw new JedisDataException("value sent to redis cannot be null");
} else {
return str.getBytes("UTF-8");
}
} catch (UnsupportedEncodingException var2) {
throw new JedisException(var2);
}
}
public static String encode(byte[] data) {
try {
return new String(data, "UTF-8");
} catch (UnsupportedEncodingException var2) {
throw new JedisException(var2);
}
}
}
可以看到,主要处理 是 将字符串转换为 UTF-8 编码格式的字符串。