【Redis】Lettuce源码分析-MGET原理

引用:

  1. redisTemplate对lettuce封装 
  2. Lettuce之RedisClusterClient使用以及源码分析

业务使用

业务使用,首先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命令。。。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值