Spring Data Redis + Redisson 学习笔记
目录
- 背景与动机
- 核心概念与技术栈
- [Spring Data Redis 详解](#Spring Data Redis 详解)
- [Redisson 详解](#Redisson 详解)
- 两者对比与选择
- 实战案例
- 常见问题与最佳实践
- 进阶主题
- 扩展阅读
1. 背景与动机
1.1 为什么需要 Redis?
Redis (Remote Dictionary Server)是一个开源的内存数据结构存储系统,可用作:
- 缓存(Cache): 减轻数据库压力,提升响应速度
- 消息队列(Message Queue): 异步处理、削峰填谷
- 分布式锁(Distributed Lock): 解决分布式系统并发问题
- 会话存储(Session Store): 分布式 Session 共享
- 计数器/排行榜: 高性能计数与排序
1.2 为什么需要 Spring Data Redis?
原生 Jedis/Lettuce 客户端虽然功能强大,但:
- API 较底层,需要手动管理连接
- 缺少序列化/反序列化支持
- 与 Spring 生态集成不便
Spring Data Redis 提供:
- 统一的
RedisTemplate抽象 - 自动连接池管理
- 内置序列化方案(JSON、JDK、Protobuf 等)
- 声明式缓存(
@Cacheable)支持 - 与 Spring Boot 无缝集成
1.3 为什么需要 Redisson?
Spring Data Redis 主要面向基础数据操作,但分布式场景需要:
- 分布式锁(避免超卖、重复执行)
- 分布式集合(跨 JVM 共享的 Map、Set)
- 限流器(RateLimiter)
- 消息队列/发布订阅
Redisson 是一个基于 Redis 的 Java 分布式工具库,提供:
- 丰富的分布式对象(RMap、RSet、RLock 等)
- 开箱即用的分布式锁(支持看门狗、公平锁)
- 消息队列、Topic、Stream 支持
- 限流、布隆过滤器等高级功能
2. 核心概念与技术栈
2.1 技术栈概览
┌─────────────────────────────────────────────┐
│ Spring Boot Application │
├─────────────────────────────────────────────┤
│ Spring Data Redis │ Redisson │
│ (基础操作 + 缓存) │ (分布式工具) │
├─────────────────────────────────────────────┤
│ Lettuce / Jedis 客户端 │
├─────────────────────────────────────────────┤
│ Redis Server │
│ (单机/哨兵/集群) │
└─────────────────────────────────────────────┘
2.2 核心依赖
xml
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson(可选,按需引入) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.3</version>
</dependency>
<!-- 连接池(推荐) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
3. Spring Data Redis 详解
3.1 核心组件
| 组件 | 说明 |
|---|---|
| RedisTemplate | 通用 Redis 操作模板,支持各种数据类型 |
| StringRedisTemplate | 针对 String 类型优化(Key/Value 都是 String) |
| ValueOperations | String 类型操作(get/set) |
| HashOperations | Hash 类型操作(hget/hset) |
| ListOperations | List 类型操作(lpush/rpop) |
| SetOperations | Set 类型操作(sadd/smembers) |
| ZSetOperations | Sorted Set 操作(zadd/zrange) |
3.2 配置示例
3.2.1 application.yml 配置
yaml
spring:
data:
redis:
host: localhost
port: 6379
password: your_password # 如果有密码
database: 0 # 默认数据库编号
timeout: 3000 # 连接超时(毫秒)
# 连接池配置(Lettuce)
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: -1ms # 连接池最大阻塞等待时间
# 如果使用 Jedis
# jedis:
# pool:
# max-active: 8
# max-idle: 8
# min-idle: 0
# max-wait: -1ms
3.2.2 Redis 配置类(推荐)
java
@Configuration
public class RedisConfig {
/**
* 自定义 RedisTemplate(解决序列化问题)
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson2JsonRedisSerializer 序列化 Value
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
serializer.setObjectMapper(objectMapper);
// String 类型的 Key 使用 StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 使用 JSON 序列化
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
/**
* StringRedisTemplate(Key/Value 都是 String)
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
return new StringRedisTemplate(factory);
}
}
3.3 基础操作示例
3.3.1 String 操作
java
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
// ========== String 操作 ==========
/**
* 设置键值对
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置键值对(带过期时间)
*/
public void setWithExpire(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取值
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除键
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 判断键是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取剩余过期时间
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 自增操作
*/
public Long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
}
3.3.2 Hash 操作
java
/**
* Hash 操作(适合存储对象)
*/
public void hashOperations() {
String key = "user:1001";
// 单个字段设置
redisTemplate.opsForHash().put(key, "name", "张三");
redisTemplate.opsForHash().put(key, "age", 25);
// 批量设置
Map<String, Object> userMap = new HashMap<>();
userMap.put("name", "李四");
userMap.put("age", 30);
userMap.put("email", "lisi@example.com");
redisTemplate.opsForHash().putAll(key, userMap);
// 获取单个字段
Object name = redisTemplate.opsForHash().get(key, "name");
// 获取所有字段
Map<Object, Object> allFields = redisTemplate.opsForHash().entries(key);
// 删除字段
redisTemplate.opsForHash().delete(key, "email");
// 判断字段是否存在
Boolean exists = redisTemplate.opsForHash().hasKey(key, "name");
}
3.3.3 List 操作
java
/**
* List 操作(适合消息队列、排行榜)
*/
public void listOperations() {
String key = "message:queue";
// 左侧插入(队头)
redisTemplate.opsForList().leftPush(key, "消息1");
redisTemplate.opsForList().leftPushAll(key, "消息2", "消息3");
// 右侧插入(队尾)
redisTemplate.opsForList().rightPush(key, "消息4");
// 左侧弹出(消费消息)
Object message = redisTemplate.opsForList().leftPop(key);
// 阻塞式弹出(等待消息)
Object blockMessage = redisTemplate.opsForList().leftPop(key, 10, TimeUnit.SECONDS);
// 获取列表范围
List<Object> messages = redisTemplate.opsForList().range(key, 0, -1);
// 获取列表长度
Long size = redisTemplate.opsForList().size(key);
}
3.3.4 Set 操作
java
/**
* Set 操作(适合去重、交集/并集/差集)
*/
public void setOperations() {
String key1 = "user:tags:1001";
String key2 = "user:tags:1002";
// 添加元素
redisTemplate.opsForSet().add(key1, "Java", "Redis", "MySQL");
redisTemplate.opsForSet().add(key2, "Python", "Redis", "MongoDB");
// 获取所有元素
Set<Object> members = redisTemplate.opsForSet().members(key1);
// 判断元素是否存在
Boolean isMember = redisTemplate.opsForSet().isMember(key1, "Java");
// 交集
Set<Object> intersect = redisTemplate.opsForSet().intersect(key1, key2); // [Redis]
// 并集
Set<Object> union = redisTemplate.opsForSet().union(key1, key2);
// 差集
Set<Object> diff = redisTemplate.opsForSet().difference(key1, key2); // [Java, MySQL]
// 移除元素
redisTemplate.opsForSet().remove(key1, "MySQL");
}
3.3.5 ZSet(Sorted Set)操作
java
/**
* ZSet 操作(适合排行榜、延时队列)
*/
public void zSetOperations() {
String key = "game:rank";
// 添加元素(score 用于排序)
redisTemplate.opsForZSet().add(key, "玩家A", 100);
redisTemplate.opsForZSet().add(key, "玩家B", 200);
redisTemplate.opsForZSet().add(key, "玩家C", 150);
// 增加分数
redisTemplate.opsForZSet().incrementScore(key, "玩家A", 50); // 100 + 50 = 150
// 获取排名(从小到大,0-based)
Long rank = redisTemplate.opsForZSet().rank(key, "玩家A");
// 获取排名(从大到小)
Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, "玩家A");
// 获取分数
Double score = redisTemplate.opsForZSet().score(key, "玩家A");
// 获取排名范围(Top 10)
Set<Object> top10 = redisTemplate.opsForZSet().reverseRange(key, 0, 9);
// 获取分数范围内的元素
Set<Object> range = redisTemplate.opsForZSet().rangeByScore(key, 100, 200);
// 删除元素
redisTemplate.opsForZSet().remove(key, "玩家C");
}
3.4 声明式缓存(@Cacheable)
3.4.1 启用缓存
java
@SpringBootApplication
@EnableCaching // 启用缓存支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.4.2 配置 CacheManager
java
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 配置序列化
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
// 配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 缓存过期时间 1 小时
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues(); // 不缓存 null 值
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
3.4.3 使用缓存注解
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
/**
* @Cacheable: 查询时先检查缓存,不存在则执行方法并缓存结果
* value: 缓存名称
* key: 缓存键(支持 SpEL 表达式)
* unless: 条件不缓存
*/
@Cacheable(value = "user", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id).orElse(null);
}
/**
* @CachePut: 更新缓存(总是执行方法,并更新缓存)
*/
@CachePut(value = "user", key = "#user.id")
public User updateUser(User user) {
System.out.println("更新用户: " + user.getId());
return userRepository.save(user);
}
/**
* @CacheEvict: 删除缓存
* allEntries = true: 清空整个缓存空间
*/
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
userRepository.deleteById(id);
}
/**
* 清空所有用户缓存
*/
@CacheEvict(value = "user", allEntries = true)
public void clearAllCache() {
System.out.println("清空所有用户缓存");
}
}
4. Redisson 详解
4.1 核心概念
Redisson 是一个基于 Redis 的 Java 分布式工具库,提供:
- 分布式对象: RMap、RSet、RList、RQueue 等
- 分布式锁: RLock、ReadWriteLock、Semaphore 等
- 分布式集合: RMultimap、RSetCache 等
- 消息队列: RTopic、RStream、RDelayedQueue 等
- 限流器: RRateLimiter
- 布隆过滤器: RBloomFilter
4.2 配置示例
4.2.1 application.yml 配置(单机模式)
yaml
spring:
redis:
redisson:
# 配置文件路径(可选)
config: classpath:redisson.yaml
# 或直接配置
redisson:
single-server-config:
address: redis://localhost:6379
password: your_password
database: 0
connection-pool-size: 64
connection-minimum-idle-size: 10
idle-connection-timeout: 10000
timeout: 3000
4.2.2 Java 配置(推荐)
java
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 单机模式
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("your_password")
.setDatabase(0)
.setConnectionPoolSize(64)
.setConnectionMinimumIdleSize(10)
.setIdleConnectionTimeout(10000)
.setTimeout(3000);
// 集群模式
// config.useClusterServers()
// .addNodeAddress("redis://127.0.0.1:7000")
// .addNodeAddress("redis://127.0.0.1:7001")
// .addNodeAddress("redis://127.0.0.1:7002");
// 哨兵模式
// config.useSentinelServers()
// .setMasterName("mymaster")
// .addSentinelAddress("redis://127.0.0.1:26379", "redis://127.0.0.1:26380");
return Redisson.create(config);
}
}
4.3 分布式对象
4.3.1 RMap(分布式 Map)
java
@Service
public class RedissonMapService {
@Autowired
private RedissonClient redissonClient;
public void rMapDemo() {
// 获取 RMap
RMap<String, User> userMap = redissonClient.getMap("user:map");
// 基本操作(与 Java Map 一致)
userMap.put("1001", new User("张三", 25));
userMap.put("1002", new User("李四", 30));
User user = userMap.get("1001");
userMap.remove("1002");
// 批量操作
Map<String, User> batch = new HashMap<>();
batch.put("1003", new User("王五", 28));
batch.put("1004", new User("赵六", 32));
userMap.putAll(batch);
// 异步操作
RFuture<User> future = userMap.getAsync("1001");
future.whenComplete((u, ex) -> {
if (ex == null) {
System.out.println("异步获取用户: " + u.getName());
}
});
// 过期时间
userMap.expire(Duration.ofHours(1));
}
}
4.3.2 RSet(分布式 Set)
java
public void rSetDemo() {
RSet<String> tags = redissonClient.getSet("user:tags");
tags.add("Java");
tags.add("Redis");
tags.add("MySQL");
boolean contains = tags.contains("Java");
tags.remove("MySQL");
// 集合操作
RSet<String> tags2 = redissonClient.getSet("user:tags:2");
tags2.add("Python");
tags2.add("Redis");
Set<String> intersection = tags.readIntersection("user:tags:2");
}
4.3.3 RList(分布式 List)
java
public void rListDemo() {
RList<String> messageList = redissonClient.getList("message:list");
messageList.add("消息1");
messageList.add("消息2");
messageList.add(0, "消息0"); // 指定位置插入
String first = messageList.get(0);
messageList.remove(1);
// 排序
messageList.sort(Comparator.naturalOrder());
}
4.4 分布式锁(重点)
4.4.1 基础分布式锁
java
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 基础分布式锁
*/
public void basicLock() {
RLock lock = redissonClient.getLock("my:lock");
try {
// 加锁(阻塞等待)
lock.lock();
// 业务逻辑
System.out.println("执行业务逻辑...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 带超时的分布式锁
*/
public void lockWithTimeout() {
RLock lock = redissonClient.getLock("my:lock");
try {
// 尝试加锁(等待 10 秒,锁定 30 秒后自动释放)
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (acquired) {
try {
// 业务逻辑
System.out.println("获取锁成功,执行业务...");
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 看门狗机制(自动续期)
* 默认锁定 30 秒,每 10 秒自动续期
*/
public void lockWithWatchdog() {
RLock lock = redissonClient.getLock("my:lock");
try {
// 不指定 leaseTime,使用看门狗机制
lock.lock();
// 长时间业务逻辑(超过 30 秒也不会释放)
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
4.4.2 公平锁
java
/**
* 公平锁(按请求顺序获取锁)
*/
public void fairLock() {
RLock fairLock = redissonClient.getFairLock("fair:lock");
try {
fairLock.lock();
// 业务逻辑
} finally {
fairLock.unlock();
}
}
4.4.3 读写锁
java
/**
* 读写锁(读读不互斥,读写互斥,写写互斥)
*/
public void readWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw:lock");
// 读锁
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读操作
System.out.println("读取数据...");
} finally {
readLock.unlock();
}
// 写锁
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 写操作
System.out.println("写入数据...");
} finally {
writeLock.unlock();
}
}
4.4.4 联锁(MultiLock)
java
/**
* 联锁(同时锁定多个资源)
*/
public void multiLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
// 同时锁定多个锁
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
try {
multiLock.lock();
// 业务逻辑
} finally {
multiLock.unlock();
}
}
4.4.5 红锁(RedLock)
java
/**
* 红锁(适用于 Redis 集群,更高的可靠性)
*/
public void redLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
// 红锁(需要多数节点成功才算获取锁)
RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3);
try {
redLock.lock();
// 业务逻辑
} finally {
redLock.unlock();
}
}
4.5 信号量(Semaphore)
java
/**
* 信号量(限制并发数)
*/
public void semaphoreDemo() {
RSemaphore semaphore = redissonClient.getSemaphore("my:semaphore");
try {
// 设置许可数量
semaphore.trySetPermits(3);
// 获取许可
semaphore.acquire();
// 业务逻辑(最多 3 个线程同时执行)
System.out.println("执行任务...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可
semaphore.release();
}
}
4.6 限流器(RateLimiter)
java
/**
* 限流器(控制访问频率)
*/
@Service
public class RateLimiterService {
@Autowired
private RedissonClient redissonClient;
public void rateLimiterDemo() {
RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:limit");
// 设置速率: 每 5 秒最多 10 次请求
rateLimiter.trySetRate(RateType.OVERALL, 10, 5, RateIntervalUnit.SECONDS);
// 尝试获取令牌
boolean acquired = rateLimiter.tryAcquire();
if (acquired) {
System.out.println("请求通过");
// 处理业务
} else {
System.out.println("请求被限流");
}
}
/**
* API 限流注解(自定义)
*/
public void apiWithRateLimit() {
RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:user:query");
rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES);
if (!rateLimiter.tryAcquire()) {
throw new RuntimeException("请求过于频繁,请稍后再试");
}
// 执行业务逻辑
}
}
4.7 布隆过滤器(BloomFilter)
java
/**
* 布隆过滤器(快速判断元素是否存在,允许一定误判率)
*/
public void bloomFilterDemo() {
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user:exists");
// 初始化(预计元素数量 100000,误判率 0.01)
bloomFilter.tryInit(100000, 0.01);
// 添加元素
bloomFilter.add("user:1001");
bloomFilter.add("user:1002");
// 判断元素是否存在
boolean exists1 = bloomFilter.contains("user:1001"); // true
boolean exists2 = bloomFilter.contains("user:9999"); // false(或小概率误判为 true)
System.out.println("user:1001 存在: " + exists1);
System.out.println("user:9999 存在: " + exists2);
}
4.8 消息队列
4.8.1 普通队列(RQueue)
java
/**
* 普通队列(FIFO)
*/
public void queueDemo() {
RQueue<String> queue = redissonClient.getQueue("message:queue");
// 生产者
queue.offer("消息1");
queue.offer("消息2");
// 消费者
String message = queue.poll();
System.out.println("消费消息: " + message);
// 阻塞式消费
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("message:blocking");
try {
String msg = blockingQueue.poll(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4.8.2 延时队列(RDelayedQueue)
java
/**
* 延时队列(订单超时取消、定时任务等)
*/
public void delayedQueueDemo() {
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:queue");
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
// 生产者: 30 分钟后执行
delayedQueue.offer("订单:123456", 30, TimeUnit.MINUTES);
// 消费者(监听延时队列)
new Thread(() -> {
while (true) {
try {
String orderId = blockingQueue.take();
System.out.println("处理超时订单: " + orderId);
// 取消订单逻辑
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
4.8.3 优先级队列(RPriorityQueue)
java
/**
* 优先级队列(按优先级消费)
*/
public void priorityQueueDemo() {
RPriorityQueue<Task> queue = redissonClient.getPriorityQueue("task:priority");
// 添加任务(按优先级排序)
queue.add(new Task("任务1", 3));
queue.add(new Task("任务2", 1)); // 优先级最高
queue.add(new Task("任务3", 2));
// 按优先级消费
Task task = queue.poll(); // 先取出优先级 1 的任务
}
4.8.4 发布订阅(RTopic)
java
/**
* 发布订阅模式
*/
@Service
public class PubSubService {
@Autowired
private RedissonClient redissonClient;
/**
* 订阅消息
*/
public void subscribe() {
RTopic topic = redissonClient.getTopic("news:channel");
// 添加监听器
topic.addListener(String.class, (channel, msg) -> {
System.out.println("收到消息 [" + channel + "]: " + msg);
});
}
/**
* 发布消息
*/
public void publish(String message) {
RTopic topic = redissonClient.getTopic("news:channel");
// 发布消息(返回接收到消息的订阅者数量)
long count = topic.publish(message);
System.out.println("消息发送给 " + count + " 个订阅者");
}
}
5. 两者对比与选择
5.1 功能对比表
| 功能 | Spring Data Redis | Redisson | 推荐使用 |
|---|---|---|---|
| 基础数据操作 | ✅ 完善(String/Hash/List/Set/ZSet) | ✅ 支持 | Spring Data Redis |
| 声明式缓存 | ✅ @Cacheable 注解 | ❌ 不支持 | Spring Data Redis |
| 分布式锁 | ❌ 需手动实现 | ✅ 开箱即用(支持看门狗) | Redisson |
| 分布式对象 | ❌ 不支持 | ✅ RMap、RSet、RList | Redisson |
| 限流器 | ❌ 需手动实现 | ✅ RRateLimiter | Redisson |
| 布隆过滤器 | ❌ 不支持 | ✅ RBloomFilter | Redisson |
| 消息队列 | ⚠️ 仅支持 List | ✅ RQueue、RDelayedQueue、RTopic | Redisson |
| 学习成本 | 低(Spring 生态友好) | 中(需了解分布式概念) | - |
| 性能 | 高(轻量级) | 中(功能丰富,开销略大) | - |
| 适用场景 | 缓存、基础数据操作 | 分布式锁、分布式系统 | - |
5.2 使用场景建议
优先使用 Spring Data Redis:
- 简单的缓存场景(
@Cacheable) - 基础的 Redis 数据操作(CRUD)
- 对性能要求极高的场景
- 团队对 Spring 生态熟悉
优先使用 Redisson:
- 分布式锁(防止超卖、重复执行)
- 分布式限流(API 限流)
- 延时任务(订单超时取消)
- 分布式对象(跨 JVM 共享数据)
- 布隆过滤器(缓存穿透防护)
结合使用(推荐):
java
@Service
public class HybridService {
@Autowired
private RedisTemplate<String, Object> redisTemplate; // 基础操作
@Autowired
private RedissonClient redissonClient; // 分布式锁
/**
* 秒杀场景: 结合两者优势
*/
public boolean seckill(Long productId, Long userId) {
String lockKey = "seckill:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 使用 Redisson 分布式锁
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 使用 Spring Data Redis 操作库存
String stockKey = "product:stock:" + productId;
Long stock = redisTemplate.opsForValue().increment(stockKey, -1);
if (stock >= 0) {
// 库存充足,创建订单
createOrder(productId, userId);
return true;
} else {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey, 1);
return false;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return false;
}
}
6. 实战案例
6.1 案例1: 秒杀系统
java
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 初始化秒杀库存
*/
public void initStock(Long productId, Integer stock) {
String stockKey = "seckill:stock:" + productId;
redisTemplate.opsForValue().set(stockKey, stock);
}
/**
* 秒杀抢购
*/
public boolean seckill(Long productId, Long userId) {
String lockKey = "seckill:lock:" + productId;
String stockKey = "seckill:stock:" + productId;
String userKey = "seckill:user:" + productId + ":" + userId;
// 防止重复购买
if (Boolean.TRUE.equals(redisTemplate.hasKey(userKey))) {
throw new RuntimeException("您已参与过此商品的秒杀");
}
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁(等待 0.5 秒)
if (lock.tryLock(500, 3000, TimeUnit.MILLISECONDS)) {
try {
// 扣减库存
Long stock = redisTemplate.opsForValue().increment(stockKey, -1);
if (stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey, 1);
throw new RuntimeException("库存不足");
}
// 记录用户购买记录
redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);
// 异步创建订单(实际应通过 MQ)
createOrderAsync(productId, userId);
return true;
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("系统繁忙,请稍后再试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁失败");
}
}
private void createOrderAsync(Long productId, Long userId) {
// 发送到 MQ 异步创建订单
System.out.println("创建订单: productId=" + productId + ", userId=" + userId);
}
}
6.2 案例2: API 限流
java
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedissonClient redissonClient;
/**
* 自定义限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
String key() default "";
int rate() default 10; // 每分钟请求次数
int interval() default 60; // 时间窗口(秒)
}
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
String key = rateLimit.key();
if (key.isEmpty()) {
key = point.getSignature().toShortString();
}
String rateLimiterKey = "rate:limit:" + key;
RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterKey);
// 设置速率
rateLimiter.trySetRate(RateType.OVERALL, rateLimit.rate(),
rateLimit.interval(), RateIntervalUnit.SECONDS);
// 尝试获取令牌
if (rateLimiter.tryAcquire()) {
return point.proceed();
} else {
throw new RuntimeException("请求过于频繁,请稍后再试");
}
}
}
// 使用示例
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/user/{id}")
@RateLimit(key = "api:user:query", rate = 100, interval = 60) // 每分钟 100 次
public User getUser(@PathVariable Long id) {
// 查询用户
return userService.getUser(id);
}
}
6.3 案例3: 订单超时自动取消
java
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
/**
* 创建订单
*/
public String createOrder(Order order) {
// 1. 保存订单到数据库
String orderId = saveOrder(order);
// 2. 加入延时队列(30 分钟后自动取消)
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:timeout");
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
delayedQueue.offer(orderId, 30, TimeUnit.MINUTES);
return orderId;
}
/**
* 启动订单超时监听器
*/
@PostConstruct
public void startOrderTimeoutListener() {
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:timeout");
new Thread(() -> {
while (true) {
try {
// 阻塞等待超时订单
String orderId = blockingQueue.take();
// 检查订单状态
Order order = getOrderById(orderId);
if (order != null && "UNPAID".equals(order.getStatus())) {
// 取消订单
cancelOrder(orderId);
System.out.println("订单超时自动取消: " + orderId);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "order-timeout-listener").start();
}
private String saveOrder(Order order) {
// 保存订单逻辑
return "ORDER_" + System.currentTimeMillis();
}
private Order getOrderById(String orderId) {
// 查询订单
return new Order();
}
private void cancelOrder(String orderId) {
// 取消订单,释放库存
}
}
6.4 案例4: 布隆过滤器防止缓存穿透
java
@Service
public class UserCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private UserRepository userRepository;
private RBloomFilter<String> bloomFilter;
/**
* 初始化布隆过滤器
*/
@PostConstruct
public void initBloomFilter() {
bloomFilter = redissonClient.getBloomFilter("user:exists");
bloomFilter.tryInit(1000000, 0.01); // 预计 100 万用户,误判率 1%
// 加载所有用户 ID 到布隆过滤器
List<Long> userIds = userRepository.findAllUserIds();
for (Long userId : userIds) {
bloomFilter.add("user:" + userId);
}
}
/**
* 查询用户(三级缓存)
*/
public User getUser(Long userId) {
String key = "user:" + userId;
// 1. 布隆过滤器判断(防止恶意请求)
if (!bloomFilter.contains(key)) {
System.out.println("布隆过滤器拦截: " + key);
return null;
}
// 2. 查询 Redis 缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
System.out.println("命中缓存: " + key);
return user;
}
// 3. 查询数据库
user = userRepository.findById(userId).orElse(null);
if (user != null) {
// 写入缓存
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}
return user;
}
/**
* 新增用户时更新布隆过滤器
*/
public void addUser(User user) {
userRepository.save(user);
bloomFilter.add("user:" + user.getId());
}
}
7. 常见问题与最佳实践
7.1 序列化问题
问题描述
默认的 JDK 序列化会导致:
- 存储空间浪费(字节数多)
- 可读性差(二进制格式)
- 跨语言不兼容
解决方案
推荐使用 JSON 序列化:
java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// JSON 序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
serializer.setObjectMapper(mapper);
// Key 使用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 使用 JSON 序列化
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
7.2 缓存穿透、击穿、雪崩
7.2.1 缓存穿透(查询不存在的数据)
问题: 恶意请求不存在的数据,绕过缓存直击数据库
解决方案:
- 布隆过滤器(推荐)
- 缓存空值(设置较短过期时间)
java
public User getUser(Long userId) {
String key = "user:" + userId;
// 查询缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 查询数据库
user = userRepository.findById(userId).orElse(null);
if (user == null) {
// 缓存空值,防止穿透(5 分钟)
redisTemplate.opsForValue().set(key, new User(), 5, TimeUnit.MINUTES);
return null;
}
// 正常缓存(1 小时)
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
return user;
}
7.2.2 缓存击穿(热点 Key 过期)
问题: 热点数据过期瞬间,大量请求打到数据库
解决方案:
- 设置热点数据永不过期
- 互斥锁(Redisson 分布式锁)
java
public User getHotUser(Long userId) {
String key = "user:hot:" + userId;
String lockKey = "lock:" + key;
// 查询缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 获取分布式锁
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
// 双重检查
user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 查询数据库
user = userRepository.findById(userId).orElse(null);
// 写入缓存(永不过期或超长时间)
redisTemplate.opsForValue().set(key, user, 24, TimeUnit.HOURS);
} finally {
lock.unlock();
}
return user;
}
7.2.3 缓存雪崩(大量 Key 同时过期)
问题: 大量缓存同时失效,数据库压力激增
解决方案:
- 随机过期时间(避免集中过期)
- 多级缓存(本地缓存 + Redis)
- 限流降级(保护数据库)
java
public void setWithRandomExpire(String key, Object value) {
// 基础过期时间 1 小时 + 随机 0-300 秒
long expire = 3600 + new Random().nextInt(300);
redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}
7.3 分布式锁最佳实践
7.3.1 必须设置超时时间
java
// ❌ 错误: 未设置超时时间,服务宕机导致死锁
lock.lock();
// ✅ 正确: 设置超时时间
lock.tryLock(10, 30, TimeUnit.SECONDS);
7.3.2 确保锁的释放
java
RLock lock = redissonClient.getLock("my:lock");
try {
lock.lock(30, TimeUnit.SECONDS);
// 业务逻辑
} finally {
// 判断是否是当前线程持有的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
7.3.3 使用看门狗机制
java
// 不指定 leaseTime,Redisson 自动续期
lock.lock(); // 默认 30 秒,每 10 秒续期一次
// 长时间业务逻辑也不会释放锁
doLongTimeTask();
7.4 性能优化
7.4.1 Pipeline(管道)
批量操作减少网络往返:
java
// ❌ 低效: 每次操作一个网络往返
for (int i = 0; i < 1000; i++) {
redisTemplate.opsForValue().set("key" + i, "value" + i);
}
// ✅ 高效: 使用 Pipeline
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
for (int i = 0; i < 1000; i++) {
connection.set(("key" + i).getBytes(), ("value" + i).getBytes());
}
return null;
}
});
7.4.2 Lua 脚本(原子性操作)
java
/**
* 使用 Lua 脚本保证原子性
* 场景: 扣减库存并检查是否充足
*/
public boolean decrementStock(String key, int count) {
String script =
"local stock = redis.call('get', KEYS[1]) " +
"if not stock or tonumber(stock) < tonumber(ARGV[1]) then " +
" return 0 " +
"else " +
" redis.call('decrby', KEYS[1], ARGV[1]) " +
" return 1 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(key),
String.valueOf(count));
return result != null && result == 1;
}
7.5 监控与运维
7.5.1 开启 Redis 监控
yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics
metrics:
export:
prometheus:
enabled: true
7.5.2 关键指标监控
- 连接数: 监控连接池使用情况
- 命中率 :
(hits / (hits + misses)) * 100% - 内存使用: 避免内存溢出
- 慢查询: 定位性能瓶颈
- 网络延迟: 监控 Redis 响应时间
8. 进阶主题
8.1 Redis 集群配置
8.1.1 Spring Data Redis 集群配置
yaml
spring:
data:
redis:
cluster:
nodes:
- 127.0.0.1:7000
- 127.0.0.1:7001
- 127.0.0.1:7002
- 127.0.0.1:7003
- 127.0.0.1:7004
- 127.0.0.1:7005
max-redirects: 3
8.1.2 Redisson 集群配置
java
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://127.0.0.1:7000")
.addNodeAddress("redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002")
.addNodeAddress("redis://127.0.0.1:7003")
.addNodeAddress("redis://127.0.0.1:7004")
.addNodeAddress("redis://127.0.0.1:7005");
return Redisson.create(config);
}
8.2 Redis 哨兵模式
yaml
spring:
data:
redis:
sentinel:
master: mymaster
nodes:
- 127.0.0.1:26379
- 127.0.0.1:26380
- 127.0.0.1:26381
8.3 事务支持
java
/**
* Redis 事务(不支持回滚)
*/
public void transaction() {
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // 开启事务
operations.opsForValue().set("key1", "value1");
operations.opsForValue().set("key2", "value2");
return operations.exec(); // 提交事务
}
});
}
9. 扩展阅读
9.1 官方文档
- Spring Data Redis: https://spring.io/projects/spring-data-redis
- Redisson: https://redisson.org/
- Redis 官方文档: https://redis.io/documentation
9.2 推荐书籍
- 《Redis 设计与实现》(黄健宏)
- 《Redis 实战》(Josiah L. Carlson)
- 《Redis 深度历险》(钱文品)
9.3 学习路径
- 基础阶段: 掌握 Redis 基本数据结构和命令
- 框架阶段: 熟悉 Spring Data Redis 配置和使用
- 进阶阶段: 学习 Redisson 分布式特性
- 实战阶段: 缓存设计、分布式锁、秒杀系统
- 优化阶段: 性能调优、集群部署、监控运维
9.4 常用命令速查
bash
# String
SET key value
GET key
INCR key
EXPIRE key seconds
# Hash
HSET key field value
HGET key field
HGETALL key
# List
LPUSH key value
RPUSH key value
LPOP key
LRANGE key start stop
# Set
SADD key member
SMEMBERS key
SINTER key1 key2
# ZSet
ZADD key score member
ZRANGE key start stop
ZREVRANGE key start stop
# 通用
DEL key
EXISTS key
TTL key
KEYS pattern
总结
核心要点
- Spring Data Redis 适合基础操作和缓存
- Redisson 适合分布式锁和高级功能
- 结合使用可发挥各自优势
- 缓存穿透/击穿/雪崩是必须防范的问题
- 分布式锁必须设置超时时间并正确释放
- 性能优化关注 Pipeline、Lua 脚本、连接池
学习建议
- 先掌握 Spring Data Redis 基础操作
- 理解 Redis 五种数据结构的应用场景
- 熟练使用 Redisson 分布式锁
- 实战秒杀、限流、延时队列等场景
- 关注缓存设计模式和最佳实践