SpringBoot中RedisTemplate使用Pipeline

2,737 阅读1分钟

一、为什么要使用Pipeline

Redis是采用基于C/S模式的请求/响应协议的TCP服务器。因为redis具有很高的吞吐量,所以redis的性能主要体现在网络状况上。如果网络状况不好,每次请求都会出现轻微的延迟和阻塞,在大量请求中,这种延迟的影响会很大。

Pipeline可以将一组redis命令打包无阻塞并按顺序发送到redis服务器上,这样可以节省大量请求的往返时间。 还有一点要注意的是,Pipeline并不能保证原子性,即Pipeline执行的内容可能会被其他客户端或是线程的指令"插队",若想要原子性操作,需要使用事务。

二、如何使用Pipeline

<!--依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring中通过RedisTemplate.executePipelined使用Pipeline执行命令

函数提供了两种回调方式: SessionCalback/RedisCallback

主要的区别是:SessionCalback封装的更好,使用会更方便一点,通常优先选择。

性能测试:

@Component
public class RedisUtil {

    // 循环往redis插入数据
    @Autowired
    private RedisTemplate redisTemplate;

    public void executePipelined(Map<String, String> param, long seconds) {
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                for (Map.Entry<String, String> entry : param.entrySet()) {
                    operations.opsForValue().set(entry.getKey(), entry.getValue(), seconds);
                }
                //参数回调必须返回null,否则将抛出异常,可参考源码。
                return null;
            }
        });
    }
}
@SpringBootTest
@Slf4j
public class RedisPipelineTest {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisUtil redisUtil;

    @Test
    public void test() {
        long seconds = 10;
        Map<String, String> mapA = new HashMap<>(100);
        for (int i=0; i< 100; ++i) {
            mapA.put("a" + i, "aa" + i);
        }
        Map<String, String> mapB = new HashMap<>(100);
        for (int i=0; i< 100; ++i) {
            mapB.put("b" + i, "bb" + i);
        }

        StopWatch watchA = new StopWatch("noPipeline");
        watchA.start();
        for (Map.Entry<String, String> entry : mapA.entrySet()) {
            redisTemplate.opsForValue().set(entry.getKey(), entry.getValue(), seconds);
        }
        watchA.stop();
        StopWatch watchB = new StopWatch("pipeline");
        watchB.start();
        redisUtil.executePipelined(mapB, seconds);
        watchB.stop();
        log.info("【noPipeline】 | 耗时: ({})", watchA.getLastTaskTimeMillis());
        log.info("【pipeline】 | 耗时: ({})", watchB.getLastTaskTimeMillis());

    }
}

最后的执行结果:

image.png

可以看出使用Pipeline的性能提升还是挺大的。但是需要注意的是,如果Pipeline里的命令过多的话,也会造成网络阻塞的,实际中,可以根据业务情况拆分成多个小的Pipeline来执行。