Redis的Pipeline特性
一、什么是Pipeline
我们知道,Redis执行命令的一般流程可以分为如下四步:
- 发送命令
- 命令排队
- 执行命令
- 返回结果
其中发送命令和返回结果这两步耗费的时间我们称之为往返时间(Round Trip Time,RTT),假设客户端和服务端之间距离800公里,那么一次RTT时间就是(800*2)/(300000*2/3)=8ms。
注意:光在真空中传输速度为每秒30万公里,这里假设光纤为光速的2/3!
如果我们现在有1万条命令需要执行,总的RTT时间就是80s,相对来说,Redis实际执行命令的时间就非常短了,大部分命令都可以在微秒级别完成,所以才会有Redis的性能瓶颈是网络的这种说法!
那么我们这里要讲的Pipeline是干什么的呢?简单来说,它是一个“打包工具”,它可以将一大堆命令打包在一起,然后一次性发送给服务端,这样就可以把原来1万次的RTT缩短为1次,提升整体的一个执行效率!当然了,如果可以直接使用批量执行的命令解决的话,还是优先选择使用批量命令。
二、如何使用Pipeline
Pipeline的使用很简单,RedisTemplate给我们提供了一个executePipelined()方法,我们这里进行一个对比试验,即使用Pipeline和不使用Pipeline来执行1万条命令的耗时差异比对。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class RedisPipeline {
@Autowired
private RedisTemplate redisTemplate;
public List<Object> plGet(List<String> keys){
return redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (String key : keys) {
operations.opsForValue().get(key);
}
return null;
}
});
}
public void plSet(List<String> keys,List<String> values){
if (keys == null || values == null){
return;
}
if (keys.size() != values.size()){
return;
}
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (int i=0;i<keys.size();i++){
operations.opsForValue().set(keys.get(i), values.get(i));
}
return null;
}
});
}
}
@Test
public void testPlAndNoPl(){
//准备好数据
List<String> plKeys = new ArrayList<>();
List<String> plValues = new ArrayList<>();
List<String> noPlKeys = new ArrayList<>();
List<String> noPlValues = new ArrayList<>();
for (int i=0;i<TEST_COUNT;i++){
plKeys.add("pl:" + i);
plValues.add(UUID.randomUUID().toString());
noPlKeys.add("noPl:"+i);
noPlValues.add(UUID.randomUUID().toString());
}
//非Pipeline操作10000次
long noPlStart = System.currentTimeMillis();
for (int i=0;i<TEST_COUNT;i++){
redisTemplate.opsForValue().set(noPlKeys.get(i),noPlValues.get(i));
}
long noPlEnd = System.currentTimeMillis();
System.out.println("非Pipeline操作10000次的执行时间为:" + (noPlEnd-noPlStart) + "毫秒");
//Pipeline操作10000次
long plStart = System.currentTimeMillis();
redisPipeline.plSet(plKeys,plValues);
long plEnd = System.currentTimeMillis();
System.out.println("Pipeline操作10000次的执行时间为:" + (plEnd-plStart) + "毫秒");
}
执行结果如下:

可以看到:同样是执行1万条命令,使用Pipeline要快了十几倍,不过我这里使用的是本地虚拟机上跑的Redis,如果Redis是部署在远距离的服务器上,差异甚至可以达到上百倍!
三、注意事项
需要注意的是,我们在使用Pipeline打包命令的时候不能一点限制都没有,一股脑全放进去,最好将Pipeline的总发送大小控制在内核输入输出缓冲区大小之内(一般为4k~8k)或者控制在单个TCP 报文最大值1460字节之内,过大的话一方面会导致客户端的等待时间较长,另一方面是会造成一定的网络阻塞!!

浙公网安备 33010602011771号