引用:
业务使用
业务使用,首先StatefulRedisClusterConnection接口注入容器
@Bean(name = "redisClusterClient")
RedisClusterClient lettuceClusterClient() {
ArrayList<RedisURI> list = new ArrayList<>();
list.add(RedisURI.create("redis://123@10.10.10.10:7900"));
list.add(RedisURI.create("redis://123@10.10.10.11:7900"));
...
return RedisClusterClient.create(list);
}
@Bean(destroyMethod = "close")
StatefulRedisClusterConnection<String,String> statefulRedisClusterConnection(RedisClusterClient redisClusterClient){
StatefulRedisClusterConnection<String, String> connection = redisClusterClient.connect();
//设置读写分离 读取操作优先选择slave节点
connection.setReadFrom(ReadFrom.SLAVE_PREFERRED);
return redisClusterClient.connect();
}
使用示例:
@Resource
private StatefulRedisClusterConnection<String, String> statefulRedisClusterConnection;
public String syncGet(String key) {
RedisAdvancedClusterCommands<String, String> redisAdvancedClusterCommands = statefulRedisClusterConnection.sync();
return redisAdvancedClusterCommands.get(key);
}
即便业务端使用了spring-data-redis提供的redisTemplate,实际调用类也是如此,具体原理可参见 引用1
原理分析:
StatefulRedisClusterConnectionImpl类是StatefulRedisClusterConnection接口的实现类,代码如下
public class StatefulRedisClusterConnectionImpl<K, V> extends RedisChannelHandler<K, V> implements
StatefulRedisClusterConnection<K, V> {
private Partitions partitions;
private char[] password;
private boolean readOnly;
private String clientName;
protected final RedisCodec<K, V> codec;
protected final RedisAdvancedClusterCommands<K, V> sync;
protected final RedisAdvancedClusterAsyncCommandsImpl<K, V> async;
protected final RedisAdvancedClusterReactiveCommandsImpl<K, V> reactive;
/**
* Initialize a new connection.
*
* @param writer the channel writer
* @param codec Codec used to encode/decode keys and values.
* @param timeout Maximum time to wait for a response.
*/
public StatefulRedisClusterConnectionImpl(RedisChannelWriter writer, RedisCodec<K, V> codec, Duration timeout) {
super(writer, timeout);
this.codec = codec;
this.async = new RedisAdvancedClusterAsyncCommandsImpl<>(this, codec);
this.sync = (RedisAdvancedClusterCommands) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(),
new Class<?>[] { RedisAdvancedClusterCommands.class }, syncInvocationHandler());
this.reactive = new RedisAdvancedClusterReactiveCommandsImpl<>(this, codec);
}
成员变量包括同步命令sync,异步命令async,和响应式命令reactive,我们以async为例,它的实现类在构造函数中可以看到为RedisAdvancedClusterAsyncCommandsImpl,让我们进入这个类的代码看下mget的实现过程
@Override
public RedisFuture<List<KeyValue<K, V>>> mget(Iterable<K> keys) {
//获取分区slot和key的映射关系
Map<Integer, List<K>> partitioned = SlotHash.partition(codec, keys);
//如果分区数小于2也就是只有一个分区即所有key都落在一个分区就直接获取
if (partitioned.size() < 2) {
return super.mget(keys);
}
//每个key与slot映射关系
Map<K, Integer> slots = SlotHash.getSlots(partitioned);
Map<Integer, RedisFuture<List<KeyValue<K, V>>>> executions = new HashMap<>();
//遍历分片信息,逐个发送
for (Map.Entry<Integer, List<K>> entry : partitioned.entrySet()) {
RedisFuture<List<KeyValue<K, V>>> mget = super.mget(entry.getValue());
executions.put(entry.getKey(), mget);
}
// restore order of key 恢复key的顺序
return new PipelinedRedisFuture<>(executions, objectPipelinedRedisFuture -> {
List<KeyValue<K, V>> result = new ArrayList<>();
for (K opKey : keys) {
int slot = slots.get(opKey);
int position = partitioned.get(slot).indexOf(opKey);
RedisFuture<List<KeyValue<K, V>>> listRedisFuture = executions.get(slot);
result.add(MultiNodeExecution.execute(() -> listRedisFuture.get().get(position)));
}
return result;
});
}
可以看到主要思路是将key根据slot进行分组,将在同一个slot的命令一起发送到对应的节点,再将所有请求的返回值合并作为最终结果。(代码中的partitioned其实是slot和key的映射关系)
按slot分组效率可能不高,假如mget50个key,也会有很高的概率分属5到0个不同slot上,即便客户端实现异步并行也会循环访问集群50次,不禁疑问为什么不用集群shard分组呢,发现即便是在同一个shard上不同slot的key,redis也不支持用mget命令。。。