1、底层结构分析
(1)JedisCluster
我们从JedisCluster类入手
public class JedisCluster extends BinaryJedisCluster implements JedisCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands
这个类继承了BinaryJedisCluster,实现了JedisCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands,而这三个接口,主要是redis的命令。可以不用关注。
JedisCluster的构造器最终调用了父类BinaryJedisCluster的构造器方法
public JedisCluster(Set<HostAndPort> jedisClusterNode, int timeout, int maxAttempts,
final GenericObjectPoolConfig poolConfig) {
super(jedisClusterNode, timeout, maxAttempts, poolConfig);
}
public JedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, final GenericObjectPoolConfig poolConfig) {
super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, poolConfig);
}
(2)BinaryJedisCluster
BinaryJedisCluster类有两个属性,分别是
protected int maxAttempts;
protected JedisClusterConnectionHandler connectionHandler;
构造器方法主要是初始化这俩属性。所以一步一步看进去,就要看这个方法
this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
timeout);
(3)JedisClusterConnectionHandler
JedisSlotBasedConnectionHandler类是JedisClusterConnectionHandler的子类
而关于槽点、redis请求时根据key计算槽点然后到哪个节点请求数据都是在这两个类里做的。
先看基类JedisClusterConnectionHandler
public abstract class JedisClusterConnectionHandler implements Closeable {
protected final JedisClusterInfoCache cache;
public JedisClusterConnectionHandler(Set<HostAndPort> nodes,
final GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout) {
this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout);
initializeSlotsCache(nodes, poolConfig);
}
abstract Jedis getConnection();
abstract Jedis getConnectionFromSlot(int slot);
public Jedis getConnectionFromNode(HostAndPort node) {
return cache.setupNodeIfNotExist(node).getResource();
}
public Map<String, JedisPool> getNodes() {
return cache.getNodes();
}
//初始化槽点缓存
private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig) {
for (HostAndPort hostAndPort : startNodes) {
Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort());
try {
cache.discoverClusterNodesAndSlots(jedis);
break;
} catch (JedisConnectionException e) {
// try next nodes
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
public void renewSlotCache() {
cache.renewClusterSlots(null);
}
public void renewSlotCache(Jedis jedis) {
cache.renewClusterSlots(jedis);
}
@Override
public void close() {
cache.reset();
}
}
(4)JedisClusterInfoCache
这个类里主要做了两件事,初始化JedisClusterInfoCache类,然后调用initializeSlotsCache(nodes, poolConfig);
再来看下JedisClusterInfoCache类是做什么的。
public class JedisClusterInfoCache {
private final Map<String, JedisPool> nodes = new HashMap<String, JedisPool>();
private final Map<Integer, JedisPool> slots = new HashMap<Integer, JedisPool>();
nodes变量存放的是节点信息,key是hnp.getHost() + “:” + hnp.getPort(),
public JedisPool setupNodeIfNotExist(HostAndPort node) {
w.lock();
try {
String nodeKey = getNodeKey(node);
JedisPool existingPool = nodes.get(nodeKey);
if (existingPool != null) return existingPool;
JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(),
connectionTimeout, soTimeout, null, 0, null);
nodes.put(nodeKey, nodePool);
return nodePool;
} finally {
w.unlock();
}
}
上述代码是用来存储nodes信息的
再来说slots变量,是用来存放槽点对应的节点信息。
public void discoverClusterNodesAndSlots(Jedis jedis) {
w.lock();
try {
reset();
//获取节点的槽点信息
List<Object> slots = jedis.clusterSlots();
for (Object slotInfoObj : slots) {
List<Object> slotInfo = (List<Object>) slotInfoObj;
if (slotInfo.size() <= MASTER_NODE_INDEX) {
continue;
}
//获取槽点值list
List<Integer> slotNums = getAssignedSlotArray(slotInfo);
// hostInfos
int size = slotInfo.size();
//list的0,1索引存放的是槽点的起始值和结束值,2存放的是主节点信息,后面存放的是从节点信息
for (int i = MASTER_NODE_INDEX; i < size; i++) {
List<Object> hostInfos = (List<Object>) slotInfo.get(i);
if (hostInfos.size() <= 0) {
continue;
}
HostAndPort targetNode = generateHostAndPort(hostInfos);
//节点存入nodes map缓存
setupNodeIfNotExist(targetNode);
if (i == MASTER_NODE_INDEX) {
//把主节点对应的槽点信息缓存起来,从节点没必要缓存,因为从节点是用来做备用的,当主节点挂掉,redis cluster会选出从节点做主节点
assignSlotsToNode(slotNums, targetNode);
}
}
}
} finally {
w.unlock();
}
}
//所有槽点转成list结构,list里每个item是槽点值
private List<Integer> getAssignedSlotArray(List<Object> slotInfo) {
List<Integer> slotNums = new ArrayList<Integer>();
for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1))
.intValue(); slot++) {
slotNums.add(slot);
}
return slotNums;
}
//槽点对应的节点信息缓存起来,每个槽点值对应一个节点
public void assignSlotsToNode(List<Integer> targetSlots, HostAndPort targetNode) {
w.lock();
try {
JedisPool targetPool = setupNodeIfNotExist(targetNode);
for (Integer slot : targetSlots) {
slots.put(slot, targetPool);
}
} finally {
w.unlock();
}
}
到这里JedisClusterInfoCache类的主要方法已经讲完。还有一个方法是renewClusterSlots(Jedis jedis),用来重置缓存,也就是当从A节点拉取数据失败,返回槽点不在此节点时,需要把本地槽点缓存信息更新一下。
public void renewClusterSlots(Jedis jedis) {
//If rediscovering is already in process - no need to start one more same rediscovering, just return
if (!rediscovering) {
try {
w.lock();
rediscovering = true;
if (jedis != null) {
try {
discoverClusterSlots(jedis);
return;
} catch (JedisException e) {
//try nodes from all pools
}
}
for (JedisPool jp : getShuffledNodesPool()) {
try {
jedis = jp.getResource();
discoverClusterSlots(jedis);
return;
} catch (JedisConnectionException e) {
// try next nodes
} finally {
if (jedis != null) {
jedis.close();
}
}
}
} finally {
rediscovering = false;
w.unlock();
}
}
}
(5)JedisClusterConnectionHandler->initializeSlotsCache
底层已经讲完,再往上一层。看JedisClusterConnectionHandler
类执行initializeSlotsCache方法。
//对所有节点执行上述方法,去初始化槽点节点缓存
private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig) {
for (HostAndPort hostAndPort : startNodes) {
Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort());
try {
cache.discoverClusterNodesAndSlots(jedis);
break;
} catch (JedisConnectionException e) {
// try next nodes
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
底层结构已经分析结束。
2、执行命令分析
JedisClusterCommand类是执行redis命令的方法封装。
核心方法就是这个
private T runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking) {
if (attempts <= 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 {
connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
}
}
return execute(connection);
} catch (JedisNoReachableClusterNodeException jnrcne) {
throw jnrcne;
} catch (JedisConnectionException jce) {
// release current connection before recursion
releaseConnection(connection);
connection = null;
if (attempts <= 1) {
//We need this because if node is not reachable anymore - we need to finally initiate slots renewing,
//or we can stuck with cluster state without one node in opposite case.
//But now if maxAttempts = 1 or 2 we will do it too often. For each time-outed request.
//TODO make tracking of successful/unsuccessful operations for node - do renewing only
//if there were no successful responses from this node last few seconds
this.connectionHandler.renewSlotCache();
//no more redirections left, throw original exception, not JedisClusterMaxRedirectionsException, because it's not MOVED situation
throw jce;
}
return runWithRetries(key, attempts - 1, tryRandomNode, asking);
} catch (JedisRedirectionException jre) {
// if MOVED redirection occurred,
if (jre instanceof JedisMovedDataException) {
// it rebuilds cluster's slot cache
// recommended by Redis cluster specification
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);
}
return runWithRetries(key, attempts - 1, false, asking);
} finally {
releaseConnection(connection);
}
}