起因

在优化线上系统代码时, 通过抓包发现当 Spring 中的 RedisTemplate 底层使用 Lettuce 库时, 并不是真正的 pipeline .

代码如下

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("10.0.0.40", 6479);
        config.setPassword("pwd");
        LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
        return factory;
    }

		// 调用代码:
    redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {
        redisConnection.hSet("hell1".getBytes(), "hash1".getBytes(), "hellovalue1".getBytes());
        redisConnection.hSet("hell2".getBytes(), "hash2".getBytes(), "hellovalue2".getBytes());
        redisConnection.hSet("hell3".getBytes(), "hash3".getBytes(), "hellovalue3".getBytes());
        redisConnection.hSet("hell4".getBytes(), "hash4".getBytes(), "hellovalue4".getBytes());
        return null;
    });

使用 tcpdump 抓包发现如下:

image-20190730163405844

真正的 pipeline 请求是像下面这样子的:

image-20190730163220679

注意, 其实 Lettuce 的行为很奇怪. 在 Spring RedisTemplate 中的 executePipelined 方法中的情况:

  • 有时完全是一条一条命令地发送
  • 有时全合并几条命令发送
  • 但跟完全 pipeline 的方式不同, 测试多次, 但没发现有一次是完整 pipeline 的

解法方法

目前使用 Spring RedisTemplate 中的 executePipelined 中对 Lettuce 未真正支持 pipeline. 所以这点要特别注意.

  • 切换为 Jedis 库. Spring 对 Jedis 库的 pipeline 支持正常
  • Lettuce 中的话, 只好手工写代码来使用 pipeline 了. 参考官方文档 Lettuce.io
    • 代码中, 记得要复用 ClientResources 对象. 它类似 Netty 中的EventLoopGroup 配置.

Jedis 与 Lettuce 对比

参考