JedisCluster类的定义
public class JedisCluster extends BinaryJedisCluster implements JedisCommands, MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {.....}BinaryJedisCluster 用于进行字节数组的key,value
JedisCommands 用于操作字符串类型的key,value
MultiKeyJedisClusterCommands 用于操作多个key类型的
JedisClusterScriptingCommands 用于lua脚本操作
JedisClusterCommand
在JedisCluster客户端中,JedisClusterCommand是一个非常重要的类,采用模板方法设计,高度封装了操作Redis集群的操作。对于集群中各种存储操作,提供了一个抽象execute方法。
JedisCluster各种具体操作Redis集群方法,只需要通过匿名内部类的方式,灵活扩展Execute方法。
内部通过JedisClusterConnectionHandler封装了Jedis的实例。
该类主要由两个重点:
- 采用模板方法设计,具体存取操作有子类实现
- 在集群操作中,为了保证高可用方式,采用递归算法进行尝试发生的MOVED,ASK,数据迁移操作等。
BinaryJedisCluster和JedisCommands通过实现创建JedisClusterCommand实例实现execute抽象方法之后调用run方法进行redis的操作。核心类在JedisClusterCommand
public abstract class JedisClusterCommand<T> {
// JedisCluster的连接真正持有类
private JedisClusterConnectionHandler connectionHandler;
// 重试次数默认为5
private int maxAttempts;
// 线程本地变量
private ThreadLocal<Jedis> askConnection = new ThreadLocal();
public JedisClusterCommand(JedisClusterConnectionHandler connectionHandler, int maxAttempts) {
this.connectionHandler = connectionHandler;
this.maxAttempts = maxAttempts;
}
public abstract T execute(Jedis var1);
public T run(String key) {
if (key == null) {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
} else {
return this.runWithRetries(SafeEncoder.encode(key), this.maxAttempts, false, false);
}
}
public T run(int keyCount, String... keys) {
if (keys != null && keys.length != 0) {
if (keys.length > 1) {
int slot = JedisClusterCRC16.getSlot(keys[0]);
for(int i = 1; i < keyCount; ++i) {
int nextSlot = JedisClusterCRC16.getSlot(keys[i]);
if (slot != nextSlot) {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster because keys have different slots.");
}
}
}
return this.runWithRetries(SafeEncoder.encode(keys[0]), this.maxAttempts, false, false);
} else {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
}
}
public T runBinary(byte[] key) {
if (key == null) {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
} else {
return this.runWithRetries(key, this.maxAttempts, false, false);
}
}
public T runBinary(int keyCount, byte[]... keys) {
if (keys != null && keys.length != 0) {
if (keys.length > 1) {
int slot = JedisClusterCRC16.getSlot(keys[0]);
for(int i = 1; i < keyCount; ++i) {
int nextSlot = JedisClusterCRC16.getSlot(keys[i]);
if (slot != nextSlot) {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster because keys have different slots.");
}
}
}
return this.runWithRetries(keys[0], this.maxAttempts, false, false);
} else {
throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
}
}
public T runWithAnyNode() {
Jedis connection = null;
Object var2;
try {
connection = this.connectionHandler.getConnection();
var2 = this.execute(connection);
} catch (JedisConnectionException var6) {
throw var6;
} finally {
this.releaseConnection(connection);
}
return var2;
}
/**
* 这个方法是核心操作,整个类都是在调用这个方法
* 该方法采用递归方式,保证在往集群中存取数据时,发生MOVED,ASKing,数据迁移过程中遇到问题,也是一种实现高可用的方式。
* 该方法中调用execute方法,该方法由子类具体实现
*/
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 {
// 如果是在slot的迁移过程中访问了slot中已经被迁移的key,那么先发送asking命令,然后再去目标节点操作
if (asking) {
connection = (Jedis)this.askConnection.get();
connection.asking();
asking = false;
} else if (tryRandomNode) {
//随机获取一个节点,第一次执行时,tryRandomNode为false。
connection = this.connectionHandler.getConnection();
} else {
//根据key的slot去JedisClusterInfoCache 中获取Jedis的实例
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) {
//重新更新redis slot缓存
this.connectionHandler.renewSlotCache();
throw var14;
}
//递归调用一次
var7 = this.runWithRetries(key, attempts - 1, tryRandomNode, asking);
return var7;
} catch (JedisRedirectionException var15) {
// 重定向异常,key不再当前连接的节点上,需要定位到其他节点,并且更新slot缓存,方便下次直接定位过去
if (var15 instanceof JedisMovedDataException) {
this.connectionHandler.renewSlotCache(connection);
}
this.releaseConnection(connection);
connection = null;
//slot迁移的时候得ask响应,需要把当前请求重定向到目标机器,不用缓存slot
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);
} finally {
this.releaseConnection(connection);
}
return var7;
}
}
private void releaseConnection(Jedis connection) {
if (connection != null) {
connection.close();
}
}
}JedisClusterInfoCache
用于封装连接信息的类,这个类中可以获取每一个slot对应的Master节点的连接。这里面有两个缓存Map<String, JedisPool> nodes,Map<Integer, JedisPool> slots
public class JedisClusterInfoCache {
//节点缓存,key=ip:port,value=JedisPool
private final Map<String, JedisPool> nodes;
//slot对应的Jedis,key是0-16383个slot
private final Map<Integer, JedisPool> slots;
public JedisClusterInfoCache(GenericObjectPoolConfig poolConfig, int timeout) {
this(poolConfig, timeout, timeout, (String)null);
}
/**
* 这个方法在JedisClusterConnectionHandler(连接持有者)的初始化方法中调用
* 作用是使用当前的jedis中获取到的集群信息去初始化缓存
*/
public void discoverClusterNodesAndSlots(Jedis jedis) {
//加写锁
this.w.lock();
try {
this.reset();
//获取集群信息,slot,master,slave,有多少个master,list就有多少个元素
List<Object> slots = jedis.clusterSlots();
Iterator var3 = slots.iterator();
//循环生成每个Master的jedis对象pool
while(true) {
List slotInfo;
do {
if (!var3.hasNext()) {
return;
}
Object slotInfoObj = var3.next();
slotInfo = (List)slotInfoObj;
} while(slotInfo.size() <= 2);
List<Integer> slotNums = this.getAssignedSlotArray(slotInfo);
int size = slotInfo.size();
for(int i = 2; i < size; ++i) {
List<Object> hostInfos = (List)slotInfo.get(i);
if (hostInfos.size() > 0) {
HostAndPort targetNode = this.generateHostAndPort(hostInfos);
//设置nodes缓存,使用ip:port作为key,然后创建JedisPool作为value缓存起来。
this.setupNodeIfNotExist(targetNode);
if (i == 2) {
//设置slot缓存,这里会将每一个slot对应的JedisPool缓存起来
this.assignSlotsToNode(slotNums, targetNode);
}
}
}
}
} finally {
this.w.unlock();
}
}总结:集群操作主要是在客户端缓存了每个slot对应的master的连接池,由客户端进行key的分发,如果客户端分发的key和集群不一致,那么redis server会返回重定向异常,客户端根据重定向异常重新去缓存即可。