Redis 集成

Spring Boot 通过 spring-boot-starter-data-redis 提供 Redis 集成,底层可选 Lettuce(默认,支持异步/响应式)或 Jedis(同步)连接池。

Redis 命令与 redis-cli 速查:Redis 基础命令


依赖与配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(Lettuce 需要 commons-pool2) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

单机

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: secret          # 无密码则省略
      database: 0
      timeout: 2s               # 命令超时
      lettuce:
        pool:
          max-active: 16
          max-idle: 8
          min-idle: 2
          max-wait: 1s

哨兵(Sentinel)

spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes:
          - sentinel1:26379
          - sentinel2:26379
          - sentinel3:26379
      password: secret

集群(Cluster)

spring:
  data:
    redis:
      cluster:
        nodes:
          - node1:7001
          - node2:7002
          - node3:7003
        max-redirects: 3

RedisTemplate

Spring Boot 自动配置 RedisTemplate<Object, Object>StringRedisTemplate

推荐配置:JSON 序列化

默认使用 JDK 序列化(二进制,可读性差)。通常改为 JSON:

@Configuration
public class RedisConfig {
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
 
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(
            new ObjectMapper().activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
            ), Object.class
        );
 
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

常用操作

String

@Service
public class UserCacheService {
 
    private final StringRedisTemplate redisTemplate;
 
    // 写入,带过期时间
    public void set(String key, String value, Duration ttl) {
        redisTemplate.opsForValue().set(key, value, ttl);
    }
 
    // 读取
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
 
    // 原子递增(计数器、限流)
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }
 
    // SETNX:不存在才写入(分布式锁原语)
    public Boolean setIfAbsent(String key, String value, Duration ttl) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, ttl);
    }
}

Hash

Redis Hash 详解

// 存储对象字段
redisTemplate.opsForHash().put("user:1001", "name", "张三");
redisTemplate.opsForHash().put("user:1001", "age", "30");
 
// 读取单个字段
String name = (String) redisTemplate.opsForHash().get("user:1001", "name");
 
// 读取所有字段
Map<Object, Object> fields = redisTemplate.opsForHash().entries("user:1001");
 
// 批量写入(使用 Map)
Map<String, Object> map = Map.of("name", "李四", "age", 25);
redisTemplate.opsForHash().putAll("user:1002", map);

List

// 左推(队列头部)
redisTemplate.opsForList().leftPush("queue:task", "task1");
 
// 右推(队列尾部,FIFO 队列)
redisTemplate.opsForList().rightPush("queue:task", "task2");
 
// 左弹(消费)
String task = (String) redisTemplate.opsForList().leftPop("queue:task");
 
// 阻塞弹出(最多等待 5 秒)
String item = (String) redisTemplate.opsForList().leftPop("queue:task", Duration.ofSeconds(5));
 
// 范围查询
List<Object> items = redisTemplate.opsForList().range("queue:task", 0, -1);

Set

// 写入
redisTemplate.opsForSet().add("tags:post:1", "java", "spring", "redis");
 
// 判断是否存在
Boolean exists = redisTemplate.opsForSet().isMember("tags:post:1", "java");
 
// 集合运算(求并集)
Set<Object> union = redisTemplate.opsForSet().union("tags:post:1", "tags:post:2");

Sorted Set(ZSet)

Redis ZSet 详解

// 写入(score 用于排序)
redisTemplate.opsForZSet().add("rank:score", "player1", 9500);
redisTemplate.opsForZSet().add("rank:score", "player2", 8800);
 
// 递增 score(积分累加)
redisTemplate.opsForZSet().incrementScore("rank:score", "player1", 200);
 
// 按 score 降序取 Top 10
Set<ZSetOperations.TypedTuple<Object>> top10 =
    redisTemplate.opsForZSet().reverseRangeWithScores("rank:score", 0, 9);
 
// 查询排名(0-based,降序)
Long rank = redisTemplate.opsForZSet().reverseRank("rank:score", "player1");

过期时间

// 设置 key 过期
redisTemplate.expire("user:1001", Duration.ofMinutes(30));
 
// 查询剩余时间
Duration ttl = redisTemplate.getExpire("user:1001");
 
// 持久化(移除过期时间)
redisTemplate.persist("user:1001");
 
// 写入时同时设置过期(原子操作)
redisTemplate.opsForValue().set("token:" + userId, token, Duration.ofHours(2));

Pipeline(批量命令)

减少网络往返,适合批量写入:

List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
    for (int i = 0; i < 1000; i++) {
        connection.stringCommands().set(
            ("key:" + i).getBytes(),
            ("value:" + i).getBytes()
        );
    }
    return null;
});

Lua 脚本(原子操作)

多命令需要原子执行时使用 Lua,Redis 保证脚本执行不被中断:

// 原子地「读取 → 判断 → 删除」(用于锁释放)
String script = """
    if redis.call('get', KEYS[1]) == ARGV[1] then
        return redis.call('del', KEYS[1])
    else
        return 0
    end
    """;
 
Long result = redisTemplate.execute(
    new DefaultRedisScript<>(script, Long.class),
    List.of("lock:order:123"),
    "unique-lock-value"
);

Pub/Sub(发布订阅)

// 发布消息
redisTemplate.convertAndSend("channel:notify", "用户登录事件");
 
// 订阅(需在配置类中注册)
@Configuration
public class RedisSubscribeConfig {
 
    @Bean
    public RedisMessageListenerContainer listenerContainer(
            RedisConnectionFactory factory,
            MessageListenerAdapter adapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(adapter, new ChannelTopic("channel:notify"));
        return container;
    }
 
    @Bean
    public MessageListenerAdapter messageListenerAdapter(NotifySubscriber subscriber) {
        return new MessageListenerAdapter(subscriber, "onMessage");
    }
}
 
@Component
public class NotifySubscriber {
    public void onMessage(String message) {
        System.out.println("收到消息: " + message);
    }
}

Pub/Sub 消息不持久化,消费者离线期间的消息会丢失。需要可靠消息传递时,考虑使用 Stream 或 消息队列


Redisson(分布式高级特性)

Redisson 在 Redis 基础上封装了分布式锁、信号量、限流器等:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.2</version>
</dependency>

详见 分布式锁,此处仅示例限流器(RRateLimiter):

RRateLimiter limiter = redissonClient.getRateLimiter("limiter:api");
// 初始化:每秒允许 10 次
limiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
 
if (!limiter.tryAcquire()) {
    throw new TooManyRequestsException("请求过于频繁");
}

相关链接