RedisTemplate使用PipeLine的总结

                  一、为什么要使用Pipeline?

二、如何在使用Pipeline?

三、集群下优化RedisPipeline操作


一、为什么要使用Pipeline?

Redis是采用基于C/S模式的请求/响应协议的TCP服务器。
           性能问题一:redis客户端发送多条请求,后面的请求需要等待前面的请求处理完后,才能进行处理,而且每个请求都存在往返时间RRT(Round Trip Time),即使redis性能极高,当数据量足够大,也会极大影响性能,还可能会引起其他意外情况。
           性能问题二:性能问题一,我们可以通过scan命令来解决,如何来设置count又是一个问题,设置不好,同样会有大量请求存在,即使设置到1w(推荐最大值),如果扫描的数据量太大,这个问题同样不能避免。每个请求都会经历三次握手、四次挥手,在处理大量连接时,处理完后,挥手会产生大量time-wait,如果该服务器提供其他服务,可能对其他服务造成影响。

使用Pipeline可以解决以上问题。

二、如何在使用Pipeline?

本文使用的是RedisTemplate调用execute,connection使用的redis原生的操作,这里简单使用了zCount来计算keys中集合数据的长度。获取结果为result,这里要使用Pipeline需要调用Connection.openPipeline()。Connection.closePipeline()返回值为List是执行后的结果,相当简单,默认使用。



   org.springframework.boot
   spring-boot-starter-data-redis
 redisTemplate.execute(new RedisCallback() {
            @Nullable
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (int i = 0; i < 1000000; i++) {
                    String key = "123" + i;
                    connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
                }
                List result=connection.closePipeline();
                return null;
            }
        });

这个list是放在匿名类内部,对于数据处理不太友好,代码会看起来相当难受,想取出来使用还是不可变的。如果要获取返回值,我们可以调用如下代码executePipelined,这样就可以返回我们需要的结果,下面我们可以对得到list进行操作。

 List List = redisTemplate.executePipelined(new RedisCallback() {
            @Nullable
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
               for (int i = 0; i < 1000000; i++) {
                    String key = "123" + i;
                    connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
                }
                return null;
            }
        });

在这里需要注意4点内容:

1.这里的connect是redis原生链接,所以connection的返回结果是基本上是byte数组,如果需要存储的数据,需要对byte[]数组反序列化。
2.在doInRedis中返回值必须返回为null,为什么返回为空?可以定位到内部代码去查看详情,这里不再赘述。3.connection.openPipeline()可以调用,也可以不调用,但是connection.closePipeline()不能调用,调用了拿不到返回值。因为调用的时候会直接将结果返回,同时也不会对代码进行反序列化。
4.反序列化需要传入反序列化对象,这些对象都可以进行相应的实例化,如下图所示。

RedisTemplate使用PipeLine的总结_第1张图片

根据你的项目需求选择合适的反序列化对象。比如我在项目中key使用的是StringRedisSerializer,而值通常使用的是GenerJackson2JsonRedisSerializer。所以在初始化redisTemplate的时候会这样做,代码如下,将序列化的实例化对象放入redisTemplate中,当使用的时候就可以直接redis.getKeySerializer()或者redis.getValueSerializer(),这样就不用在实例化一个对象,造成浪费和冗余。

public class MyRedisUtil {

    private RedisTemplate redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer keySerializer = new StringRedisSerializer();
        RedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        this.redisTemplate = redisTemplate;

    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

}

通过这样的掉用方式,我们就可以不用进行强制转换,直接获得我们想要的对象了。

 List List = redisTemplate.executePipelined(new RedisCallback() {
            @Nullable
            @Override
            public User doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
               for (int i = 0; i < 1000000; i++) {
                    String key = "123" + i;
                    connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
                }
                return null;
            }
        }, myRedisComponent.getRedisTemplate().getValueSerializer());

三、集群下优化RedisPipeline操作

在redis集群上使用上面的方法操作,会导致执行时间变成长,也会带来额外的消耗。那么集群下如何使用Pipeline操作呢?Redis 集群中内置了 16384 个哈希槽,redisCluster把所有的物理节点映射到[0-16383]slot上,由cluster 负责维护。比如A,B,C三个节点,那么对应槽点的范围值可能是分别是0-5400,5401-10800,10801-16383。我们需要根据key用CRC16算法计算出该key的槽点值,获取到相应的节点,然后把同一节点的数据通过pipeline获取。则可以极大提升效率。

public List clusterPiplineGet(List keys) {
        RedisSerializer keySerializer = redisClusterComponent.getRedisTemplate().getKeySerializer();
        RedisSerializer valueSerializer = redisClusterComponent.getRedisTemplate().getValueSerializer();
        HashMap> nodeKeyMap = new HashMap<>();
        List result = new ArrayList<>();
        RedisClusterConnection redisClusterConnection = redisClusterComponent.getRedisTemplate().getConnectionFactory().getClusterConnection();
        try {
            //通过计算每个key的槽点,并获取相应节点
            Iterable redisClusterNodes = redisClusterConnection.clusterGetNodes();
            for (RedisClusterNode redisClusterNode : redisClusterNodes) {
                RedisClusterNode.SlotRange slotRange = redisClusterNode.getSlotRange();
                for (String key : keys) {
                    int slot = JedisClusterCRC16.getSlot(key);
                    if (slotRange.contains(slot)) {
                        List list = nodeKeyMap.get(redisClusterNode);
                        if (null == list) {
                            list = new ArrayList<>();
                            nodeKeyMap.putIfAbsent(redisClusterNode, list);
                        }
                        list.add(key);
                    }
                }
            }
            for (Map.Entry> clusterNodeListEntry : nodeKeyMap.entrySet()) {
                RedisClusterNode redisClusterNode = clusterNodeListEntry.getKey();
                //连接节点
                JedisPool jedisPool = ((JedisCluster) redisClusterConnection.getNativeConnection()).getClusterNodes().get(new HostAndPort(redisClusterNode.getHost(), redisClusterNode.getPort()).toString());
                List nodeListEntryValue = clusterNodeListEntry.getValue();

                byte[][] arr = new byte[nodeListEntryValue.size()][];
                int count = 0;
                //获取key数据
                for (String nodeKey : nodeListEntryValue) {
                    arr[count++] = keySerializer.serialize(nodeKey);
                }
                Jedis jedis = jedisPool.getResource();
                List> responses = new ArrayList<>();
                try {
                    Pipeline pipeline = jedis.pipelined();
                    for (String nodeKey : nodeListEntryValue) {
                        responses.add(pipeline.get(keySerializer.serialize(nodeKey)));
                    }
                    pipeline.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    jedis.close();
                }
                for (Response response : responses) {
                    byte[] data = response.get();
                    result.add(valueSerializer.deserialize(data));
                }
            }
        } finally {
            RedisConnectionUtils.releaseConnection(redisClusterConnection, redisClusterComponent.getRedisTemplate().getConnectionFactory());
        }
        return result;
    }

好了,关于redisTemplate有不懂的地方,欢迎留言,大家一起讨论。

你可能感兴趣的:(redis,redis,RedisTemplate,java,pipeline)