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: 3RedisTemplate
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
// 存储对象字段
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)
// 写入(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("请求过于频繁");
}