一、为什么要使用Pipeline?
二、如何在使用Pipeline?
三、集群下优化RedisPipeline操作
Redis是采用基于C/S模式的请求/响应协议的TCP服务器。
性能问题一:redis客户端发送多条请求,后面的请求需要等待前面的请求处理完后,才能进行处理,而且每个请求都存在往返时间RRT(Round Trip Time),即使redis性能极高,当数据量足够大,也会极大影响性能,还可能会引起其他意外情况。
性能问题二:性能问题一,我们可以通过scan命令来解决,如何来设置count又是一个问题,设置不好,同样会有大量请求存在,即使设置到1w(推荐最大值),如果扫描的数据量太大,这个问题同样不能避免。每个请求都会经历三次握手、四次挥手,在处理大量连接时,处理完后,挥手会产生大量time-wait,如果该服务器提供其他服务,可能对其他服务造成影响。
使用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
这个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.反序列化需要传入反序列化对象,这些对象都可以进行相应的实例化,如下图所示。
根据你的项目需求选择合适的反序列化对象。比如我在项目中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());
在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有不懂的地方,欢迎留言,大家一起讨论。