Spring Boot: Spring Data Redis
最后更新:2026-08-26
Redis 是缓存的瑞士军刀——String、Hash、List、Set、ZSet 五大数据结构,缓存、会话、消息一网打尽。
1. 你将学到
RedisTemplate与StringRedisTemplate操作 String / Hash / List / Set / ZSet@Cacheable集成 Redis 作为分布式缓存后端- Redis 发布/订阅(Pub/Sub)实现订单事件通知
- Redis 连接池配置 Lettuce vs Jedis 对比
- Bob 使用 Redis 缓存会话实现 OrderFlow 多实例共享登录状态
2. 一个集群架构师的真实故事
(1) 痛点:多实例缓存不共享
Alice 将 OrderFlow 从单实例扩展到 3 个实例后,Caffeine 本地缓存出了问题:实例 A 更新了商品价格并更新本地缓存,实例 B 和 C 的本地缓存还是旧价格,用户看到的价格不一致。Bob 尝试用 Redis 共享 Session,但不知道怎么集成 Spring Cache 和 Redis。
(2) Spring Data Redis 的解法
Redis 作为分布式缓存,所有实例共享:
JAVA
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id).orElseThrow();
}
// Cache stored in Redis, shared across all instances
(3) 收益
Bob 用 Redis 替换 Caffeine 作为分布式缓存后,3 个实例缓存一致,商品价格更新实时同步。同时用 Redis Pub/Sub 实现订单事件通知,用 Redis 存储 JWT 黑名单,多实例共享登录状态。
3. Redis 数据结构操作
(1) 五大数据结构
| 类型 | 用途 | 典型场景 |
|---|---|---|
| String | 简单键值对 | 缓存、计数器、Session |
| Hash | 对象属性 | 商品详情、用户信息 |
| List | 有序列表 | 消息队列、最新列表 |
| Set | 无序集合 | 标签、共同好友 |
| ZSet | 有序集合 | 排行榜、延迟队列 |
▶ 示例: RedisTemplate 基本操作
JAVA
@Service
public class RedisOperationService {
private final StringRedisTemplate redis;
public RedisOperationService(StringRedisTemplate redis) {
this.redis = redis;
}
// String operations
public void cacheProduct(Long id, String json) {
redis.opsForValue().set("product:" + id, json, Duration.ofHours(1));
}
public String getProduct(Long id) {
return redis.opsForValue().get("product:" + id);
}
// Counter
public Long incrementOrderCount() {
return redis.opsForValue().increment("stats:daily-orders");
}
// Hash operations
public void saveProductDetail(Long id, Map<String, String> details) {
redis.opsForHash().putAll("product:detail:" + id, details);
}
public Map<Object, Object> getProductDetail(Long id) {
return redis.opsForHash().entries("product:detail:" + id);
}
// List operations - Recent orders
public void addRecentOrder(Long orderId) {
redis.opsForList().leftPush("recent:orders", orderId.toString());
redis.opsForList().trim("recent:orders", 0, 99); // Keep last 100
}
public List<String> getRecentOrders() {
return redis.opsForList().range("recent:orders", 0, -1);
}
// Set operations - Product tags
public void addProductTag(Long productId, String tag) {
redis.opsForSet().add("product:tags:" + productId, tag);
}
public Set<String> getProductTags(Long productId) {
return redis.opsForSet().members("product:tags:" + productId);
}
// ZSet operations - Sales ranking
public void updateSalesRank(Long productId, double salesCount) {
redis.opsForZSet().add("sales:ranking", productId.toString(), salesCount);
}
public List<String> getTopSellingProducts(int limit) {
return redis.opsForZSet()
.reverseRange("sales:ranking", 0, limit - 1)
.stream().toList();
}
}
输出:
TEXT
📖 仅展示
// 执行成功
4. @Cacheable 集成 Redis
(1) Redis 缓存配置
▶ 示例: RedisCacheManager 配置
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
输出:
TEXT
📖 仅展示
// 执行成功
YAML
spring:
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
JAVA
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
Map<String, RedisCacheConfiguration> cacheConfigs = Map.of(
"products", config.entryTtl(Duration.ofHours(1)),
"product-list", config.entryTtl(Duration.ofMinutes(10)),
"order-stats", config.entryTtl(Duration.ofMinutes(5))
);
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.transactionAware()
.build();
}
}
| Lettuce vs Jedis | Lettuce | Jedis |
|---|---|---|
| 连接模型 | 异步非阻塞(Netty) | 同步阻塞 |
| 线程安全 | 是(单连接多线程共享) | 否(需要连接池) |
| 性能 | 高并发场景更优 | 简单场景够用 |
| Spring Boot 默认 | ✅ 默认 | 需手动切换 |
5. Redis 发布/订阅
(1) Pub/Sub 消息模型
sequenceDiagram
participant P as Publisher<br/>(OrderService)
participant R as Redis<br/>Pub/Sub
participant S1 as Subscriber 1<br/>(NotificationService)
participant S2 as Subscriber 2<br/>(AnalyticsService)
P->>R: PUBLISH order-events "ORDER_CREATED:123"
R->>S1: Message: "ORDER_CREATED:123"
R->>S2: Message: "ORDER_CREATED:123"
▶ 示例: 发布订单事件
JAVA
@Service
public class OrderEventPublisher {
private final StringRedisTemplate redis;
public OrderEventPublisher(StringRedisTemplate redis) {
this.redis = redis;
}
public void publishOrderCreated(Long orderId) {
redis.convertAndSend("order-events",
"ORDER_CREATED:" + orderId);
}
public void publishOrderCancelled(Long orderId) {
redis.convertAndSend("order-events",
"ORDER_CANCELLED:" + orderId);
}
}
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例: 订阅订单事件
JAVA
@Component
public class OrderEventSubscriber {
private static final Logger log = LoggerFactory.getLogger(OrderEventSubscriber.class);
@RedisListener(topic = "order-events")
public void handleOrderEvent(String message) {
String[] parts = message.split(":");
String eventType = parts[0];
Long orderId = Long.parseLong(parts[1]);
switch (eventType) {
case "ORDER_CREATED" -> log.info("Processing order created: {}", orderId);
case "ORDER_CANCELLED" -> log.info("Processing order cancelled: {}", orderId);
}
}
}
@Configuration
public class RedisPubSubConfig {
@Bean
public RedisMessageListenerContainer container(
RedisConnectionFactory factory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener(listenerAdapter,
new ChannelTopic("order-events"));
return container;
}
@Bean
public MessageListenerAdapter listenerAdapter(OrderEventSubscriber subscriber) {
return new MessageListenerAdapter(subscriber, "handleOrderEvent");
}
}
输出:
TEXT
📖 仅展示
// 执行成功
6. Redis 共享 Session 与 JWT 黑名单
▶ 示例: JWT 黑名单实现
JAVA
@Service
public class TokenBlacklistService {
private final StringRedisTemplate redis;
public TokenBlacklistService(StringRedisTemplate redis) {
this.redis = redis;
}
public void blacklistToken(String tokenId, Duration remainingTtl) {
redis.opsForValue().set(
"token:blacklist:" + tokenId, "1", remainingTtl);
}
public boolean isBlacklisted(String tokenId) {
return Boolean.TRUE.equals(
redis.hasKey("token:blacklist:" + tokenId));
}
}
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例: 分布式限流
JAVA
@Service
public class RateLimitService {
private final StringRedisTemplate redis;
public RateLimitService(StringRedisTemplate redis) {
this.redis = redis;
}
public boolean isAllowed(String clientId, int maxRequests, Duration window) {
String key = "rate-limit:" + clientId;
Long count = redis.opsForValue().increment(key);
if (count != null && count == 1) {
redis.expire(key, window);
}
return count != null && count <= maxRequests;
}
}
输出:
TEXT
📖 仅展示
// 执行成功
| 应用场景 | Redis 数据结构 | Key 设计 |
|---|---|---|
| 分布式缓存 | String / Hash | cache:products:123 |
| JWT 黑名单 | String + TTL | token:blacklist:tokenId |
| 分布式限流 | String + INCR + TTL | rate-limit:clientId |
| 最近订单 | List + LPUSH + TRIM | recent:orders |
| 销售排行 | ZSet | sales:ranking |
| 事件通知 | Pub/Sub | order-events |
7. 综合示例:OrderFlow Redis 完整集成
JAVA
// RedisCacheConfig.java
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(defaults)
.withInitialCacheConfigurations(Map.of(
"products", defaults.entryTtl(Duration.ofHours(1)),
"product-list", defaults.entryTtl(Duration.ofMinutes(10))
))
.build();
}
}
// ProductService with Redis cache
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product", id));
}
@CachePut(value = "products", key = "#result.id")
@CacheEvict(value = "product-list", allEntries = true)
public Product updateProduct(Long id, String name, BigDecimal price) {
Product p = getProduct(id);
p.setName(name);
p.setPrice(price);
return productRepository.save(p);
}
}
// OrderEventPublisher.java
@Service
public class OrderEventPublisher {
private final StringRedisTemplate redis;
public OrderEventPublisher(StringRedisTemplate redis) { this.redis = redis; }
public void publishOrderCreated(Long orderId) {
redis.convertAndSend("order-events", "ORDER_CREATED:" + orderId);
}
}
YAML
# application.yml
spring:
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 20
max-idle: 10
❓ 常见问题
Q RedisTemplate 和 StringRedisTemplate 有什么区别?
A StringRedisTemplate 继承
RedisTemplate<String, String>,key 和 value 都是 String,使用 StringRedisSerializer。RedisTemplate 默认使用 JDK 序列化,可读性差。推荐使用 StringRedisTemplate 或自定义序列化器。Q Redis 缓存和 Caffeine 缓存如何选择?
A 单实例用 Caffeine(最快),多实例用 Redis(共享)。最佳方案:L1 Caffeine + L2 Redis 两级缓存,本地缓存短期(1-5 分钟),Redis 缓存长期(30-60 分钟)。
Q Redis Pub/Sub 消息会丢失吗?
A 会。Pub/Sub 是"发后即忘",订阅者不在线时消息丢失。如果需要可靠消息,使用 Redis Streams 或专业消息队列(RabbitMQ/Kafka)。
Q Lettuce 连接池需要配置吗?
A Lettuce 基于单个连接多路复用,通常不需要连接池。但在高并发阻塞操作场景下,配置连接池可以提升吞吐量。Jedis 必须配置连接池。
Q Redis 缓存的序列化方式怎么选?
A 推荐 JSON 序列化(GenericJackson2JsonRedisSerializer),可读性好,跨语言兼容。JDK 序列化不可读,不推荐。也可以用 StringRedisSerializer + 手动 JSON 转换。
Q 如何处理 Redis 连接失败?
A 1)Spring Cache 失败时默认不抛异常(回退到数据库查询);2)配置
CacheErrorHandler 自定义失败处理;3)Redis Sentinel/Cluster 保证高可用。📖 小节
- Redis 五大数据结构:String、Hash、List、Set、ZSet
@Cacheable+ RedisCacheManager 实现分布式缓存,所有实例共享- Lettuce 是 Spring Boot 默认 Redis 客户端,异步非阻塞,线程安全
- Redis Pub/Sub 实现事件通知,但消息不可靠(订阅者不在线会丢失)
- JWT 黑名单用 Redis String + TTL,分布式限流用 INCR + TTL
- 生产环境用 Redis Sentinel 或 Cluster 保证高可用
📝 作业
-
基础题(难度⭐):为 OrderFlow 配置 Spring Data Redis,使用
@Cacheable将商品缓存存储到 Redis,验证多实例缓存共享。 -
进阶题(难度⭐⭐):实现 Redis Pub/Sub 订单事件通知——下单时发布 ORDER_CREATED 事件,通知服务订阅并发送邮件。实现 JWT 黑名单 Token 注销功能。
-
挑战题(难度⭐⭐⭐):实现 L1 Caffeine + L2 Redis 两级缓存,当 Redis 缓存更新时通过 Pub/Sub 通知所有实例清除本地 Caffeine 缓存,保证两级缓存一致性。