W
e
l
c
o
m
e
: )

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) + "毫秒");
}

执行结果如下:

image

可以看到:同样是执行1万条命令,使用Pipeline要快了十几倍,不过我这里使用的是本地虚拟机上跑的Redis,如果Redis是部署在远距离的服务器上,差异甚至可以达到上百倍!

三、注意事项

需要注意的是,我们在使用Pipeline打包命令的时候不能一点限制都没有,一股脑全放进去,最好将Pipeline的总发送大小控制在内核输入输出缓冲区大小之内(一般为4k~8k)或者控制在单个TCP 报文最大值1460字节之内,过大的话一方面会导致客户端的等待时间较长,另一方面是会造成一定的网络阻塞!!

posted @ 2026-09-18 15:40  寒月静无光  阅读(6)  评论(0)    收藏  举报