Databases 29 min read

Understanding and Optimizing Redis Pipeline in Spring Boot with Lettuce and Redisson

This article explains the principles of Redis Pipeline, compares Lettuce and Redisson client implementations in Spring Boot, presents performance tests showing dramatic speed gains over single commands, discusses practical usage scenarios, common pitfalls, and provides detailed source code examples for both clients.

High Availability Architecture
High Availability Architecture
High Availability Architecture
Understanding and Optimizing Redis Pipeline in Spring Boot with Lettuce and Redisson

Redis is a client‑server TCP service that supports pipelining to improve batch command performance. This article analyzes how the Lettuce and Redisson clients in a Spring Boot environment implement the Pipeline feature, examines underlying mechanisms, and shares practical lessons learned.

1. Pipeline fundamentals

Redis Pipeline sends multiple commands in one request and receives all responses together, reducing round‑trip time (RTT) and the number of user‑to‑kernel context switches caused by repeated read/write system calls.

2. Basic command execution steps

When a client issues a request, the server processes the command and returns a response. The typical flow includes client request, server processing, and client receiving the result.

3. Performance comparison

In a test inserting 100,000 set entries, three approaches were compared: Jedis single commands, Jedis with Pipeline, and Redisson with Pipeline. Results:

Jedis逐一给每个set新增一个value耗时:162655ms
Jedis Pipeline模式耗时:504ms
Redisson Pipeline模式耗时:1399ms

The Pipeline modes dramatically outperformed the single‑command approach.

4. Real‑world application

During holiday icon updates, batch writes to Redis are required. Using Pipeline reduces latency and CPU load compared to individual commands.

4.1 Redis client comparison

Common Java Redis clients include Jedis, Lettuce, and Redisson. Spring Boot defaults to Lettuce, so the article focuses on Lettuce’s Pipeline support.

4.2 Lettuce Pipeline implementation in Spring

Spring Data Redis provides StringRedisTemplate.executePipelined with two callback types: RedisCallback and SessionCallback . Example using RedisCallback :

@Slf4j
public class RedisPipelineTestDemo {
public static void main(String[] args) {
//连接redis
Jedis jedis = new Jedis("10.101.17.180", 6379);
//jedis逐一给每个set新增一个value
String zSetKey = "Pipeline-test-set";
int size = 100000;
long begin = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
jedis.sadd(zSetKey + i, "aaa");
}
log.info("Jedis逐一给每个set新增一个value耗时:{}ms", (System.currentTimeMillis() - begin));
//Jedis使用Pipeline模式
Pipeline pipeline = jedis.pipelined();
begin = System.currentTimeMillis();
for (int i = 0; i < size; i++) { pipeline.sadd(zSetKey + i, "bbb"); }
pipeline.sync();
log.info("Jedis Pipeline模式耗时:{}ms", (System.currentTimeMillis() - begin));
//Redisson使用Pipeline模式
Config config = new Config();
config.useSingleServer().setAddress("redis://10.101.17.180:6379");
RedissonClient redisson = Redisson.create(config);
RBatch redisBatch = redisson.createBatch();
begin = System.currentTimeMillis();
for (int i = 0; i < size; i++) { redisBatch.getSet(zSetKey + i).addAsync("ccc"); }
redisBatch.execute();
log.info("Redisson Pipeline模式耗时:{}ms", (System.currentTimeMillis() - begin));
//关闭资源
pipeline.close();
jedis.close();
redisson.shutdown();
}
}

Using RedisCallback :

public void testRedisCallback() {
    List
ids = Arrays.asList(1,2,3,4,5,6,7,8,9);
    Integer contentId = 1;
    redisTemplate.executePipelined(new InsertPipelineExecutionA(ids, contentId));
}

@AllArgsConstructor
private static class InsertPipelineExecutionA implements RedisCallback
{
    private final List
ids;
    private final Integer contentId;
    @Override
    public Void doInRedis(RedisConnection connection) {
        RedisSetCommands redisSetCommands = connection.setCommands();
        ids.forEach(id -> {
            String redisKey = "aaa:" + id;
            String value = String.valueOf(contentId);
            redisSetCommands.sAdd(redisKey.getBytes(), value.getBytes());
        });
        return null;
    }
}

Using SessionCallback :

public void testSessionCallback() {
    List
ids = Arrays.asList(1,2,3,4,5,6,7,8,9);
    Integer contentId = 1;
    redisTemplate.executePipelined(new InsertPipelineExecutionB(ids, contentId));
}

@AllArgsConstructor
private static class InsertPipelineExecutionB implements SessionCallback
{
    private final List
ids;
    private final Integer contentId;
    @Override
    public
Void execute(RedisOperations
operations) {
        SetOperations
setOps = (SetOperations
) operations.opsForSet();
        ids.forEach(id -> {
            String redisKey = "aaa:" + id;
            String value = String.valueOf(contentId);
            setOps.add(redisKey, value);
        });
        return null;
    }
}

Both callbacks open a pipeline via connection.openPipeline() , execute commands, then close the pipeline with connection.closePipeline() . Lettuce’s sAdd method detects pipeline mode and uses the asynchronous connection, avoiding blocking I/O.

4.3 Comparison of callbacks

RedisCallback works directly with low‑level RedisConnection , requiring manual byte conversion.

SessionCallback provides higher‑level abstractions via RedisOperations , making code more readable.

5. Pitfalls

Using Pipeline for list lrem operations can still cause high CPU because each removal is O(N+M). Switching to ltrim , which is O(M), resolves the issue without needing Pipeline.

public void deleteSet(String updateKey, Set
deviceIds) {
    if (CollectionUtils.isEmpty(deviceIds)) return;
    int maxSize = 10000;
    redisTemplate.opsForList().trim(updateKey, maxSize + 1, -1);
}

6. Redisson support for Redis Pipeline

Redisson implements Pipeline via the RBatch interface. Commands are queued and executed together with execute() or executeAsync() . Example:

@Slf4j
public class RedisPipelineTest {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://xx.xx.xx.xx:6379");
        RedissonClient redisson = Redisson.create(config);
        RBatch redisBatch = redisson.createBatch();
        int size = 100000;
        String zSetKey = "Pipeline-test-set";
        long begin = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            redisBatch.getSet(zSetKey + i).addAsync("ccc");
        }
        redisBatch.execute();
        log.info("Redisson Pipeline模式耗时:{}ms", (System.currentTimeMillis() - begin));
        redisson.shutdown();
    }
}

Internally, Redisson creates a CommandBatchService that aggregates BatchCommandData objects per Redis node, sends them in a single Netty writeAndFlush call, and collects results into a BatchResult .

7. Conclusion

Redis Pipeline significantly reduces latency for batch operations, but developers must consider the cost of individual commands (e.g., O(N) list removals). Lettuce and Redisson both provide efficient pipeline implementations—Lettuce via asynchronous connections and Redisson via batched commands—allowing Spring Boot applications to achieve high throughput when used correctly.

JavaperformanceRedisSpring BootpipelineRedissonLettuce
High Availability Architecture
Written by

High Availability Architecture

Official account for High Availability Architecture.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.