Databases 10 min read

Mastering Redis with Spring Boot: Pub/Sub, Transactions, Pipelines, and Lua Locks

This guide demonstrates how to use Spring Boot 2.7 with Redis 6.2 to implement Pub/Sub messaging, transactional operations, pipelined commands, and Lua‑based distributed locks, providing code examples, configuration details, and explanations of each feature’s advantages and limitations.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Redis with Spring Boot: Pub/Sub, Transactions, Pipelines, and Lua Locks

1. Redis Pub/Sub

Spring Data Redis offers dedicated messaging integration similar to JMS in the Spring Framework. Pub/Sub consists of publishing and subscribing. The RedisTemplate class is used for message production. Drawbacks: no acknowledgment mechanism and messages are not persisted, so they can be lost if no consumer is available or if Redis restarts.

<code>@Component
public class MsgMessageListener implements MessageListener {
  @Override
  public void onMessage(Message message, byte[] pattern) {
    System.out.printf("从通道【%s】, 接收到消息: %s%n", new String(message.getChannel()), new String(message.getBody())) ;
  }
}
</code>
<code>@Configuration
public class MessageReceiverConfig {
  @Bean
  RedisMessageListenerContainer redisMessageListenerContainer(
      RedisConnectionFactory connectionFactory,
      MessageListener listener) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 这里监听了2个通道msg和chat
    container.addMessageListener(listener, Arrays.asList(ChannelTopic.of("msg"), ChannelTopic.of("chat"))) ;
    return container;
  }
}
</code>
<code>@Component
public class MessageSender {
  @Autowired
  private StringRedisTemplate stringRedisTemplate;

  public void sendMessage(String channel, String message) {
    stringRedisTemplate.convertAndSend(channel, message);
  }
}
</code>

2. Redis Transactions

Redis supports transactions via MULTI , EXEC , and DISCARD . Spring Data Redis provides SessionCallback for programmatic transaction handling, but it cannot guarantee that all operations run on the same connection.

<code>@Service
public class RedisTransactionService {
  private final StringRedisTemplate stringRedisTemplate ;
  public RedisTransactionService(StringRedisTemplate stringRedisTemplate) {
    this.stringRedisTemplate = stringRedisTemplate ;
  }

  public void multiOperator() {
    List<Object> txResults = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
      public List<Object> execute(RedisOperations operations) throws DataAccessException {
        operations.multi();
        operations.opsForHash().put("users:666", "name", "张三") ;
        operations.opsForValue().set("id", "666") ;
        return operations.exec() ;
      }
    });
    for (Object ret : txResults) {
      System.out.println(ret) ;
    }
  }
}
</code>

Declarative transaction support can be enabled by configuring the template:

<code>@Bean
StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory) ;
  // 手动开启事务功能
  template.setEnableTransactionSupport(true);
  return template;
}
</code>
<code>@Transactional
public void multiOperator2() {
  stringRedisTemplate.opsForValue().set("oo", "xx") ;
  Set<String> keys = stringRedisTemplate.keys("*") ;
  // 输出[]。读操作必须在空闲(不感知事务)连接上运行
  System.out.println(keys) ;
  String value = stringRedisTemplate.opsForValue().get("oo") ;
  // 返回null,因为在事务中设置的值不可见
  System.out.println(value) ;
}
</code>

3. Pipelines

Redis pipelines allow sending multiple commands without waiting for replies, improving performance for bulk operations. Spring Data Redis provides executePipelined to run a RedisCallback or SessionCallback in pipeline mode.

<code>@Service
public class RedisPipeliningService {
  private final StringRedisTemplate stringRedisTemplate ;
  public RedisPipeliningService(StringRedisTemplate stringRedisTemplate) {
    this.stringRedisTemplate = stringRedisTemplate ;
  }

  public List<Object> pipe() {
    List<Object> results = stringRedisTemplate.executePipelined(
      new RedisCallback<Object>() {
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
          StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
          for(int i = 0; i < 10; i++) {
            stringRedisConn.rPush("i" + i, String.valueOf(i)) ;
          }
          return null;
        }
      });
    return results ;
  }
}
</code>

Sample pipeline output is shown in the image below:

4. Redis Lua Scripts and Distributed Locks

Redis 2.6+ supports Lua scripts via EVAL and EVALSHA . Spring Data Redis offers high‑level abstractions for script execution, handling serialization and script caching.

Lock acquisition script (lock.lua):

<code>-- 判断锁key是否存在
if (redis.call('EXISTS', KEYS[1]) == 0) then
  -- 不存在
  redis.call('HINCRBY', KEYS[1], ARGV[1], 1) ;
  redis.call('PEXPIRE', KEYS[1], 300000) ;
  return 1;
end ;
-- 判断hash中是否存在ARGV[1]字段(该字段用来统计重入次数,一般使用threadId唯一标识)
if (redis.call('HEXISTS', KEYS[1], ARGV[1]) == 1) then
  redis.call('HINCRBY', KEYS[1], ARGV[1], 1) ;
  redis.call('PEXPIRE', KEYS[1], 300000) ;
  return 1;
end ;
return 0 ;
</code>

Unlock script (unlock.lua):

<code>-- 判断锁key是否存在
if (redis.call('EXISTS', KEYS[1]) == 0) then
  return nil ;
end ;
local counter = redis.call('HINCRBY', KEYS[1], ARGV[1], -1) ;
if (counter > 0) then
  redis.call('PEXPIRE', KEYS[1], 30000) ;
  return 1 ;
else 
  redis.call('DEL', KEYS[1]) ;
  return 1 ;
end ;
return 0 ;
</code>

Java wrapper for the lock:

<code>public interface PLock {
  boolean lock();
  void unlock();
}

public class ProductRedisLock implements PLock {
  private StringRedisTemplate stringRedisTemplate ;
  private String key ;
  private String id ;

  public ProductRedisLock(String key, StringRedisTemplate stringRedisTemplate) {
    this.key = key ;
    this.id = key.hashCode() + ":" ;
    this.stringRedisTemplate = stringRedisTemplate ;
  }

  @Override
  public boolean lock() {
    long threadId = Thread.currentThread().getId() ;
    return this.stringRedisTemplate.execute(RedisScript.of(new ClassPathResource("lock.lua"), Boolean.class),
        Arrays.asList(this.key), this.id + threadId) ;
  }

  @Override
  public void unlock() {
    long threadId = Thread.currentThread().getId() ;
    this.stringRedisTemplate.execute(RedisScript.of(new ClassPathResource("unlock.lua"), Boolean.class),
        Arrays.asList(this.key), this.id + threadId) ;
  }
}
</code>

Usage example:

<code>public void updateProduct(Long id) {
  String key = "lock:product:" + id ;
  PLock lock = new ProductRedisLock(key, stringRedisTemplate) ;
  if (lock.lock()) {
    try {
      System.out.println("更新商品:" + id) ;
      this.configProduct(id) ;
    } finally {
      lock.unlock() ;
    }
  }
}
</code>

These code snippets illustrate how to integrate Redis features into a Spring Boot application, covering messaging, transactions, pipelining, and distributed locking.

RedisSpring BootDistributed LockLuaTransactionsPub/SubPipelining
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.