1. Opening
Redis, as the current general-purpose cache selection, is popular because of its high performance. Redis version 2.x only supports stand-alone mode, and cluster mode has been introduced since version 3.0.
The clients of Redis's Java ecosystem include Jedis, Redisson, and Lettuce. Different clients have different capabilities and use methods. This article mainly analyzes the Jedis client.
The Jedis client also supports access modes of stand-alone mode, sharding mode, and cluster mode. Data access in stand-alone mode is realized by constructing Jedis class objects, data access in sharding mode is realized by constructing ShardedJedis class objects, and data access in sharding mode is realized by constructing JedisCluster class objects. Data access in cluster mode.
The Jedis client supports single-command and Pipeline access to Redis clusters. Through the Pipeline approach, the efficiency of cluster access can be improved.
The overall analysis of this article is based on Jedis's 3.5.0 version for analysis, and related source codes refer to this version.
2. Comparison of Jedis access mode
Jedis client operation Redis is mainly divided into three modes. The sub-tables are stand-alone mode, shard mode, and cluster mode.
- The stand-alone mode is mainly to create Jedis objects to operate single-node Redis, which is only suitable for accessing a single Redis node.
- The sharding mode (ShardedJedis) is mainly to access multiple Redis nodes in the sharding mode by creating a ShardedJedisPool object. It is a data distributed solution implemented by the client before Redis has no cluster function. In essence, the client uses consistent hashing Realize distributed storage of data.
- The cluster mode (JedisCluster) is mainly to access multiple Redis nodes in the cluster mode by creating JedisCluster objects. It is the cluster access that the client realizes after the introduction of the cluster mode in Redis3.0. It is essentially through the introduction of the slot concept and through CRC16 hash slot algorithm to realize distributed storage of data.
The stand-alone mode does not involve any idea of sharding, so we focus on analyzing the idea of sharding mode and cluster mode.
2.1 Fragmentation mode
- The sharding mode is essentially client-based sharding, and how to find the corresponding node in the Redis cluster based on a key is implemented on the client.
- Jedis's client sharding mode is implemented by consistent Hash. The advantage of consistent Hash algorithm is that when Redis nodes increase or decrease, only a small part of the data before and after adding or deleting nodes will be affected, compared to algorithms such as modulo The scope of influence on the data is relatively small.
- Redis is used as a cache in most scenarios, so there is no need to consider the impact of cache penetration caused by data loss. When Redis nodes are increased or decreased, there is no need to consider the problem of partial data failure.
The overall application of the fragmentation mode is shown in the figure below. The core is the consistent Hash strategy of the client.
(Quoted from: www.cnblogs.com)
2.2 Cluster mode
The essence of the cluster mode belongs to the server sharding technology, and the Redis cluster itself provides the sharding function, which has been officially provided since Redis 3.0.
The principle of clustering is: a Redis cluster contains 16384 hash slots, each key saved by Redis belongs to one of these 16384 hash slots, the cluster uses the formula CRC16(key)%16384 to calculate the key key Which slot it belongs to? The CRC16(key) statement is used to calculate the CRC16 checksum of the key.
Each node in the cluster is responsible for processing part of the hash slot. For example, a cluster can have three hash slots, of which:
- Node A is responsible for processing hash slots 0 to 5500.
- Node B is responsible for processing hash slots 5501 to 11000.
- Node C is responsible for processing hash slots 11001 to 16383.
Redis in the cluster mode for the key read and write process, the corresponding key value is calculated by CRC16 to obtain the corresponding hash value, and the hash value is mapped to the corresponding slot by the modulus of the total number of slots, and finally mapped to the corresponding node Read and write. Take the command set("key", "value") as an example, it will use the CRC16 algorithm to calculate the key to obtain a hash value of 28989, then take the modulus of 16384 to get 12605, and finally find the Redis node corresponding to 12605, and finally jump Go to the node and execute the set command.
The overall application of the cluster mode is shown in the figure below. The core is the design of the cluster hash slot and the redirection command.
(Quoted from: www.jianshu.com)
Three, the basic usage of Jedis
// Jedis单机模式的访问
public void main(String[] args) {
// 创建Jedis对象
jedis = new Jedis("localhost", 6379);
// 执行hmget操作
jedis.hmget("foobar", "foo");
// 关闭Jedis对象
jedis.close();
}
// Jedis分片模式的访问
public void main(String[] args) {
HostAndPort redis1 = HostAndPortUtil.getRedisServers().get(0);
HostAndPort redis2 = HostAndPortUtil.getRedisServers().get(1);
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>(2);
JedisShardInfo shard1 = new JedisShardInfo(redis1);
JedisShardInfo shard2 = new JedisShardInfo(redis2);
// 创建ShardedJedis对象
ShardedJedis shardedJedis = new ShardedJedis(shards);
// 通过ShardedJedis对象执行set操作
shardedJedis.set("a", "bar");
}
// Jedis集群模式的访问
public void main(String[] args) {
// 构建redis的集群池
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7001));
nodes.add(new HostAndPort("127.0.0.1", 7002));
nodes.add(new HostAndPort("127.0.0.1", 7003));
// 创建JedisCluster
JedisCluster cluster = new JedisCluster(nodes);
// 执行JedisCluster对象中的方法
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
}
Jedis realizes data access in stand-alone mode by creating Jedis class objects, and realizes data access in cluster mode by constructing JedisCluster class objects.
To understand the entire process of Jedis's access to Redis, you can first understand the access process in stand-alone mode, and then analyze the access process in cluster mode on this basis.
Four, Jedis stand-alone mode access
The overall flow chart of Jedis accessing stand-alone Redis is shown below. From the figure, it can be seen that the core process includes the creation of Jedis objects and the realization of Redis access through Jedis objects.
Familiar with the process of Jedis accessing stand-alone Redis, you need to understand the creation process of Jedis and the process of executing Redis commands.
- The core of Jedis creation process is to create Jedis object and Jedis internal variable Client object.
- The process of Jedis accessing Redis is to access Redis through the Client object inside Jedis.
4.1 Creation process
The class diagram of Jedis itself is shown in the following figure. From the figure, we can see that Jedis inherits from the BinaryJedis class.
In the BinaryJedis class, there is a Client class object docking with Redis, and Jedis realizes Redis reading and writing through the Client object of the parent class BinaryJedis.
The Jedis class created the Client object through the parent class BinaryJedis during the creation process, and understanding the Client object is the key to further understanding the access process.
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
protected JedisPoolAbstract dataSource = null;
public Jedis(final String host, final int port) {
// 创建父类BinaryJedis对象
super(host, port);
}
}
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
// 访问redis的Client对象
protected Client client = null;
public BinaryJedis(final String host, final int port) {
// 创建Client对象访问redis
client = new Client(host, port);
}
}
The class diagram of the Client class is shown in the figure below. The Client object inherits from the BinaryClient and Connection classes. There are related parameters such as the Redis access password in the BinaryClient class, and there are socket objects for accessing Redis and the corresponding input and output streams in the Connection class. In essence, Connection is the core class for communicating with Redis.
The Client class initializes the core parent class Connection object during the creation process, and the Connection is responsible for direct communication with Redis.
public class Client extends BinaryClient implements Commands {
public Client(final String host, final int port) {
super(host, port);
}
}
public class BinaryClient extends Connection {
// 存储和Redis连接的相关信息
private boolean isInMulti;
private String user;
private String password;
private int db;
private boolean isInWatch;
public BinaryClient(final String host, final int port) {
super(host, port);
}
}
public class Connection implements Closeable {
// 管理和Redis连接的socket信息及对应的输入输出流
private JedisSocketFactory jedisSocketFactory;
private Socket socket;
private RedisOutputStream outputStream;
private RedisInputStream inputStream;
private int infiniteSoTimeout = 0;
private boolean broken = false;
public Connection(final String host, final int port, final boolean ssl,
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier) {
// 构建DefaultJedisSocketFactory来创建和Redis连接的Socket对象
this(new DefaultJedisSocketFactory(host, port, Protocol.DEFAULT_TIMEOUT,
Protocol.DEFAULT_TIMEOUT, ssl, sslSocketFactory, sslParameters, hostnameVerifier));
}
}
4.2 Access process
Take Jedis executing the set command as an example, the whole process is as follows:
- Jedis's set operation is realized through Client's set operation.
- Client's set operation is realized through the sendCommand of the parent class Connection.
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands,
AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
@Override
public String set(final String key, final String value) {
checkIsInMultiOrPipeline();
// client执行set操作
client.set(key, value);
return client.getStatusCodeReply();
}
}
public class Client extends BinaryClient implements Commands {
@Override
public void set(final String key, final String value) {
// 执行set命令
set(SafeEncoder.encode(key), SafeEncoder.encode(value));
}
}
public class BinaryClient extends Connection {
public void set(final byte[] key, final byte[] value) {
// 发送set指令
sendCommand(SET, key, value);
}
}
public class Connection implements Closeable {
public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
try {
// socket连接redis
connect();
// 按照redis的协议发送命令
Protocol.sendCommand(outputStream, cmd, args);
} catch (JedisConnectionException ex) {
}
}
}
Five, Jedis fragmentation mode access
Based on the principle of the consistent Hash of the Redis sharding mode introduced earlier, we can understand the access of Jedis's sharding mode.
Regarding the concept of Redis sharding mode: Redis did not have the concept of cluster mode before version 3.0, which resulted in limited data that can be stored on a single node. Redis clients such as Jedis use a consistent Hash algorithm to achieve data sharding on the client side. storage.
In essence, Redis's sharding mode has nothing to do with Redis itself. It only solves the problem of limited storage of single-node data through the client.
The core of ShardedJedis's access to Redis is to initialize the consistent Hash object when constructing the object, and to build the mapping relationship between the classic Hash value of the consistent Hash and the node. After constructing the mapping relationship, performing operations such as set is the addressing process from the hash value to the node, and the single node operation is directly performed after the addressing is completed.
5.1 Creation process
The creation process of ShardedJedis lies in the initialization process related to consistent Hash in the parent class Sharded, and the core lies in the construction of consistent virtual nodes and the mapping relationship between virtual nodes and Redis nodes.
The core part of the source code is based on mapping into 160 virtual nodes according to the weights, and the specific Redis nodes are located through the virtual nodes.
public class Sharded<R, S extends ShardInfo<R>> {
public static final int DEFAULT_WEIGHT = 1;
// 保存虚拟节点和redis的node节点的映射关系
private TreeMap<Long, S> nodes;
// hash算法
private final Hashing algo;
// 保存redis节点和访问该节点的Jedis的连接信息
private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<>();
public Sharded(List<S> shards, Hashing algo) {
this.algo = algo;
initialize(shards);
}
private void initialize(List<S> shards) {
nodes = new TreeMap<>();
// 遍历每个redis的节点并设置hash值到节点的映射关系
for (int i = 0; i != shards.size(); ++i) {
final S shardInfo = shards.get(i);
// 根据权重映射成未160个虚拟节点
int N = 160 * shardInfo.getWeight();
if (shardInfo.getName() == null) for (int n = 0; n < N; n++) {
// 构建hash值和节点映射关系
nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
}
else for (int n = 0; n < N; n++) {
nodes.put(this.algo.hash(shardInfo.getName() + "*" + n), shardInfo);
}
// 保存每个节点的访问对象
resources.put(shardInfo, shardInfo.createResource());
}
}
}
5.2 Access process
The access process of ShardedJedis is the calculation process of consistent Hash. The core logic is: Hash calculation of the accessed key through the Hash algorithm to generate a Hash value, obtain the corresponding Redis node according to the Hash value, and obtain the corresponding access object Jedis according to the corresponding Redis node .
After obtaining the access object Jedis, you can directly perform command operations.
public class Sharded<R, S extends ShardInfo<R>> {
public static final int DEFAULT_WEIGHT = 1;
private TreeMap<Long, S> nodes;
private final Hashing algo;
// 保存redis节点和访问该节点的Jedis的连接信息
private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<>();
public R getShard(String key) {
// 根据redis节点找到对应的访问对象Jedis
return resources.get(getShardInfo(key));
}
public S getShardInfo(String key) {
return getShardInfo(SafeEncoder.encode(getKeyTag(key)));
}
public S getShardInfo(byte[] key) {
// 针对访问的key生成对应的hash值
// 根据hash值找到对应的redis节点
SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
if (tail.isEmpty()) {
return nodes.get(nodes.firstKey());
}
return tail.get(tail.firstKey());
}
}
Six, Jedis cluster mode access
Based on the Redis cluster principle introduced earlier to understand Jedis's cluster mode access.
The core mechanism for Jedis to locate keys and hash slots is the mapping between hash slots and Redis nodes, and this discovery process is based on Redis's cluster slot command.
Commands for Redis cluster operation: Redis will return the overall status of the Redis cluster through cluster slots. The information returned for each Redis node includes:
- Starting number of hash slot
- Hash slot end number
- The hash slot corresponds to the master node, and the node is represented by IP/Port
- The first copy of the master node
- The second copy of the master node
127.0.0.1:30001> cluster slots
1) 1) (integer) 0 // 开始槽位
2) (integer) 5460 // 结束槽位
3) 1) "127.0.0.1" // master节点的host
2) (integer) 30001 // master节点的port
3) "09dbe9720cda62f7865eabc5fd8857c5d2678366" // 节点的编码
4) 1) "127.0.0.1" // slave节点的host
2) (integer) 30004 // slave节点的port
3) "821d8ca00d7ccf931ed3ffc7e3db0599d2271abf" // 节点的编码
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 30002
3) "c9d93d9f2c0c524ff34cc11838c2003d8c29e013"
4) 1) "127.0.0.1"
2) (integer) 30005
3) "faadb3eb99009de4ab72ad6b6ed87634c7ee410f"
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 30003
3) "044ec91f325b7595e76dbcb18cc688b6a5b434a1"
4) 1) "127.0.0.1"
2) (integer) 30006
3) "58e6e48d41228013e5d9c1c37c5060693925e97e"
The overall flow chart of Jedis accessing cluster mode Redis is shown below. From the figure, it can be seen that the core process includes the creation of JedisCluster objects and the realization of Redis access through JedisCluster objects.
The core of JedisCluster object creation is to create JedisClusterInfoCache object and establish the mapping relationship between slot and cluster node through cluster discovery.
JedisCluster's access to the Redis cluster is to obtain the Redis node where the key is located and access it through the Jedis object.
6.1 Creation process
The class relationship of JedisCluster is shown in the following figure. In the figure, you can see the core variable JedisSlotBasedConnectionHandler object.
The parent class BinaryJedisCluster of JedisCluster creates the JedisSlotBasedConnectionHandler object, which is responsible for communicating with the Redis cluster.
public class JedisCluster extends BinaryJedisCluster implements JedisClusterCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {
public JedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
// 访问父类BinaryJedisCluster
super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig,
ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
}
}
public class BinaryJedisCluster implements BinaryJedisClusterCommands,
MultiKeyBinaryJedisClusterCommands, JedisClusterBinaryScriptingCommands, Closeable {
public BinaryJedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, String user, String password, String clientName, GenericObjectPoolConfig poolConfig,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
// 创建JedisSlotBasedConnectionHandler对象
this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
connectionTimeout, soTimeout, user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
this.maxAttempts = maxAttempts;
}
}
The core of JedisSlotBasedConnectionHandler is to create and initialize the JedisClusterInfoCache object, which caches the information of the Redis cluster.
The initialization process of the JedisClusterInfoCache object is completed by initializeSlotsCache, and the main purpose is to realize cluster node and slot discovery.
public class JedisSlotBasedConnectionHandler extends JedisClusterConnectionHandler {
public JedisSlotBasedConnectionHandler(Set<HostAndPort> nodes, GenericObjectPoolConfig poolConfig,
int connectionTimeout, int soTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) {
super(nodes, poolConfig, connectionTimeout, soTimeout, user, password, clientName,
ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap);
}
}
public abstract class JedisClusterConnectionHandler implements Closeable {
public JedisClusterConnectionHandler(Set<HostAndPort> nodes, final GenericObjectPoolConfig poolConfig,
int connectionTimeout, int soTimeout, int infiniteSoTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) {
// 创建JedisClusterInfoCache对象
this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout,
user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap);
// 初始化jedis的Slot信息
initializeSlotsCache(nodes, connectionTimeout, soTimeout, infiniteSoTimeout,
user, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
}
private void initializeSlotsCache(Set<HostAndPort> startNodes,
int connectionTimeout, int soTimeout, int infiniteSoTimeout, String user, String password, String clientName,
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
for (HostAndPort hostAndPort : startNodes) {
try (Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout,
soTimeout, infiniteSoTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier)) {
// 通过discoverClusterNodesAndSlots进行集群发现
cache.discoverClusterNodesAndSlots(jedis);
return;
} catch (JedisConnectionException e) {
}
}
}
}
The nodes of JedisClusterInfoCache are used to store the node information of the Redis cluster, and the slots are used to store the information of slots and cluster nodes.
The objects maintained by nodes and slots are JedisPool objects, which maintain the connection information with Redis. The cluster discovery process is implemented by discoverClusterNodesAndSlots, which is essentially implemented by executing the cluster discovery command cluster slots of Redis.
public class JedisClusterInfoCache {
// 负责保存redis集群的节点信息
private final Map<String, JedisPool> nodes = new HashMap<>();
// 负责保存redis的槽位和redis节点的映射关系
private final Map<Integer, JedisPool> slots = new HashMap<>();
// 负责集群的发现逻辑
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;
}
// 获取redis节点对应的槽位信息
List<Integer> slotNums = getAssignedSlotArray(slotInfo);
// hostInfos
int size = slotInfo.size();
for (int i = MASTER_NODE_INDEX; i < size; i++) {
List<Object> hostInfos = (List<Object>) slotInfo.get(i);
if (hostInfos.isEmpty()) {
continue;
}
HostAndPort targetNode = generateHostAndPort(hostInfos);
// 负责保存redis节点信息
setupNodeIfNotExist(targetNode);
if (i == MASTER_NODE_INDEX) {
// 负责保存槽位和redis节点的映射关系
assignSlotsToNode(slotNums, targetNode);
}
}
}
} finally {
w.unlock();
}
}
public void assignSlotsToNode(List<Integer> targetSlots, HostAndPort targetNode) {
w.lock();
try {
JedisPool targetPool = setupNodeIfNotExist(targetNode);
// 保存槽位和对应的JedisPool对象
for (Integer slot : targetSlots) {
slots.put(slot, targetPool);
}
} finally {
w.unlock();
}
}
public JedisPool setupNodeIfNotExist(HostAndPort node) {
w.lock();
try {
// 生产redis节点对应的nodeKey
String nodeKey = getNodeKey(node);
JedisPool existingPool = nodes.get(nodeKey);
if (existingPool != null) return existingPool;
// 生产redis节点对应的JedisPool
JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(),
connectionTimeout, soTimeout, infiniteSoTimeout, user, password, 0, clientName,
ssl, sslSocketFactory, sslParameters, hostnameVerifier);
// 保存redis节点的key和对应的JedisPool对象
nodes.put(nodeKey, nodePool);
return nodePool;
} finally {
w.unlock();
}
}
}
The class relationship of JedisPool is shown in the figure below. The internal internalPool is pooled through apache common pool.
The internalPool inside JedisPool creates Jedis objects through the makeObject of JedisFactory.
Each Redis node corresponds to a JedisPool object, and JedisPool is used to manage Jedis's application, release, reuse, etc.
public class JedisPool extends JedisPoolAbstract {
public JedisPool() {
this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
}
}
public class JedisPoolAbstract extends Pool<Jedis> {
public JedisPoolAbstract() {
super();
}
}
public abstract class Pool<T> implements Closeable {
protected GenericObjectPool<T> internalPool;
public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
if (this.internalPool != null) {
try {
closeInternalPool();
} catch (Exception e) {
}
}
this.internalPool = new GenericObjectPool<>(factory, poolConfig);
}
}
class JedisFactory implements PooledObjectFactory<Jedis> {
@Override
public PooledObject<Jedis> makeObject() throws Exception {
// 创建Jedis对象
final HostAndPort hp = this.hostAndPort.get();
final Jedis jedis = new Jedis(hp.getHost(), hp.getPort(), connectionTimeout, soTimeout,
infiniteSoTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
try {
// Jedis对象连接
jedis.connect();
if (user != null) {
jedis.auth(user, password);
} else if (password != null) {
jedis.auth(password);
}
if (database != 0) {
jedis.select(database);
}
if (clientName != null) {
jedis.clientSetname(clientName);
}
} catch (JedisException je) {
jedis.close();
throw je;
}
// 将Jedis对象包装成DefaultPooledObject进行返回
return new DefaultPooledObject<>(jedis);
}
}
6.2 Access process
JedisCluster accesses Redis through JedisClusterCommand to achieve the retry mechanism, and finally through Jedis object to achieve access. From an implementation point of view, JedisCluster encapsulates a layer on top of Jedis for cluster node positioning and retry mechanisms.
Taking the set command as an example, the entire access is achieved through JedisClusterCommand as follows:
- Calculate the Redis node where the key is located.
- Get the Jedis object corresponding to the Redis node.
- Set operation through Jedis object.
public class JedisCluster extends BinaryJedisCluster implements JedisClusterCommands,
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {
@Override
public String set(final String key, final String value, final SetParams params) {
return new JedisClusterCommand<String>(connectionHandler, maxAttempts) {
@Override
public String execute(Jedis connection) {
return connection.set(key, value, params);
}
}.run(key);
}
}
The core of the run method of JedisClusterCommand mainly locates the Redis node where the Redis key is located, and then obtains the Jedis object corresponding to the node for access.
After the Jedis object access is abnormal, JedisClusterCommand will retry and execute the renewSlotCache method according to a certain strategy to re-cluster node rediscovery.
public abstract class JedisClusterCommand<T> {
public T run(String key) {
// 针对key进行槽位的计算
return runWithRetries(JedisClusterCRC16.getSlot(key), this.maxAttempts, false, null);
}
private T runWithRetries(final int slot, int attempts, boolean tryRandomNode, JedisRedirectionException redirect) {
Jedis connection = null;
try {
if (redirect != null) {
connection = this.connectionHandler.getConnectionFromNode(redirect.getTargetNode());
if (redirect instanceof JedisAskDataException) {
connection.asking();
}
} else {
if (tryRandomNode) {
connection = connectionHandler.getConnection();
} else {
// 根据slot去获取Jedis对象
connection = connectionHandler.getConnectionFromSlot(slot);
}
}
// 执行真正的Redis的命令
return execute(connection);
} catch (JedisNoReachableClusterNodeException jnrcne) {
throw jnrcne;
} catch (JedisConnectionException jce) {
releaseConnection(connection);
connection = null;
if (attempts <= 1) {
// 保证最后两次机会去重新刷新槽位和节点的对应的信息
this.connectionHandler.renewSlotCache();
}
// 按照重试次数进行重试操作
return runWithRetries(slot, attempts - 1, tryRandomNode, redirect);
} catch (JedisRedirectionException jre) {
// 针对返回Move命令立即触发重新刷新槽位和节点的对应信息
if (jre instanceof JedisMovedDataException) {
// it rebuilds cluster's slot cache recommended by Redis cluster specification
this.connectionHandler.renewSlotCache(connection);
}
releaseConnection(connection);
connection = null;
return runWithRetries(slot, attempts - 1, false, jre);
} finally {
releaseConnection(connection);
}
}
}
The cache object of JedisSlotBasedConnectionHandler maintains the mapping relationship between slot and node, and the Jedis object corresponding to the slot is obtained through the getConnectionFromSlot method.
public class JedisSlotBasedConnectionHandler extends JedisClusterConnectionHandler {
protected final JedisClusterInfoCache cache;
@Override
public Jedis getConnectionFromSlot(int slot) {
// 获取槽位对应的JedisPool对象
JedisPool connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
// 从JedisPool对象中获取Jedis对象
return connectionPool.getResource();
} else {
// 获取失败就重新刷新槽位信息
renewSlotCache();
connectionPool = cache.getSlotPool(slot);
if (connectionPool != null) {
return connectionPool.getResource();
} else {
//no choice, fallback to new connection to random node
return getConnection();
}
}
}
}
Seven, Jedis's Pipeline implementation
The core technical idea of Pipeline is to send multiple commands to the server without waiting for a reply, and finally read the reply in one step. The advantage of this mode is that it saves the network overhead of request-response mode.
The core difference between Redis's common commands such as set and Pipeline batch operations is that the operation of the set command will send the request directly to Redis and wait for the result to return synchronously, while the operation of the Pipeline will send the request but does not immediately wait for the result to return synchronously. The specific implementation can be Check out the source code of Jedis.
The related key of the native Pipeline in cluster mode must be hashed to the same node to take effect. The reason is that the Client object under the Pipeline can only be connected to one of the nodes.
In the cluster mode, the keys belonging to different nodes can use the Pipeline, it is necessary to save the client object of the corresponding node for each key, and obtain it when the data is finally executed. In essence, it can be thought of as a clustered Pipeline based on a single-node Pipeline.
7.1 Pipeline usage analysis
When the Pipeline accesses the single-node Redis, the Pipeline object is returned through the Pipeline method of the Jedis object, and other command operations are accessed through the Pipeline object.
Pipeline analyzes from the perspective of use, it will send multiple commands in batches and finally use syncAndReturnAll to return the results at one time.
public void pipeline() {
jedis = new Jedis(hnp.getHost(), hnp.getPort(), 500);
Pipeline p = jedis.pipelined();
// 批量发送命令到redis
p.set("foo", "bar");
p.get("foo");
// 同步等待响应结果
List<Object> results = p.syncAndReturnAll();
assertEquals(2, results.size());
assertEquals("OK", results.get(0));
assertEquals("bar", results.get(1));
}
public abstract class PipelineBase extends Queable implements BinaryRedisPipeline, RedisPipeline {
@Override
public Response<String> set(final String key, final String value) {
// 发送命令
getClient(key).set(key, value);
// pipeline的getResponse只是把待响应的请求聚合到pipelinedResponses对象当中
return getResponse(BuilderFactory.STRING);
}
}
public class Queable {
private Queue<Response<?>> pipelinedResponses = new LinkedList<>();
protected <T> Response<T> getResponse(Builder<T> builder) {
Response<T> lr = new Response<>(builder);
// 统一保存到响应队列当中
pipelinedResponses.add(lr);
return lr;
}
}
public class Pipeline extends MultiKeyPipelineBase implements Closeable {
public List<Object> syncAndReturnAll() {
if (getPipelinedResponseLength() > 0) {
// 根据批量发送命令的个数即需要批量返回命令的个数,通过client对象进行批量读取
List<Object> unformatted = client.getMany(getPipelinedResponseLength());
List<Object> formatted = new ArrayList<>();
for (Object o : unformatted) {
try {
// 格式化每个返回的结果并最终保存在列表中进行返回
formatted.add(generateResponse(o).get());
} catch (JedisDataException e) {
formatted.add(e);
}
}
return formatted;
} else {
return java.util.Collections.<Object> emptyList();
}
}
}
The normal set command sends the request to Redis to obtain the response result immediately through getStatusCodeReply, so this is a request response mode.
When getStatusCodeReply obtains the response result, it will force a message to be sent to the Redis server through the flush() command and then read the response result.
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
@Override
public String set(final byte[] key, final byte[] value) {
checkIsInMultiOrPipeline();
// 发送命令
client.set(key, value);
// 等待请求响应
return client.getStatusCodeReply();
}
}
public class Connection implements Closeable {
public String getStatusCodeReply() {
// 通过flush立即发送请求
flush();
// 处理响应请求
final byte[] resp = (byte[]) readProtocolWithCheckingBroken();
if (null == resp) {
return null;
} else {
return SafeEncoder.encode(resp);
}
}
}
public class Connection implements Closeable {
protected void flush() {
try {
// 针对输出流进行flush操作保证报文的发出
outputStream.flush();
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException(ex);
}
}
}
8. Concluding remarks
As the official preferred Java client development kit for Redis, Jedis supports most of the Redis commands, and it is also a Redis client that is frequently used in daily life.
Understand the implementation principle of Jedis, in addition to supporting the daily operations of Redis, it can also better deal with the additional operations of Redis, such as the technical selection of capacity expansion.
By introducing Jedis's three scene access methods for stand-alone mode, distribution mode, and cluster mode, let everyone have a process of understanding from macro to micro, master the core ideas of Jedis and better apply them in practice.
Author: vivo Internet server team-Wang Zhi
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。