从零理解 Redisson:Java 分布式工具箱的入门与实战
一、Redisson 是什么
1.1 一句话定义
Redisson 是一个基于 Redis 的 Java 客户端框架,它把 Redis 的底层命令封装成了 Java 开发者熟悉的数据结构和工具(锁、队列、Map、信号量等),让你像使用本地 Java 对象一样使用分布式功能。
1.2 类比理解
| 你熟悉的 | Redisson 提供的 | 区别 |
|---|---|---|
java.util.HashMap |
RMap(分布式Map) |
数据存在 Redis,多个服务实例共享 |
java.util.concurrent.locks.ReentrantLock |
RLock(分布式锁) |
锁存在 Redis,跨JVM生效 |
java.util.concurrent.BlockingQueue |
RBlockingQueue(分布式队列) |
队列存在 Redis,多实例消费 |
java.util.concurrent.Semaphore |
RSemaphore(分布式信号量) |
信号量存在 Redis,跨实例限流 |
简单说:Redisson = Redis + Java 并发工具包的分布式版本。
1.3 Redisson vs Jedis vs Lettuce
| 框架 | 定位 | 特点 |
|---|---|---|
| Jedis | Redis 底层客户端 | 直接发送 Redis 命令,简单轻量 |
| Lettuce | Redis 底层客户端 | 基于 Netty,支持异步,Spring Boot 默认 |
| Redisson | Redis 高级客户端 | 封装了分布式锁、集合、队列等高级功能 |
java
// Jedis:直接操作 Redis 命令
jedis.set("key", "value");
jedis.setnx("lock-key", "holder");
// Lettuce(通过 Spring RedisTemplate):也是操作命令
redisTemplate.opsForValue().set("key", "value");
redisTemplate.opsForValue().setIfAbsent("lock-key", "holder");
// Redisson:面向对象的方式,屏蔽底层命令
RLock lock = redissonClient.getLock("lock-key");
lock.lock(); // 内部自动处理 SETNX、过期时间、看门狗等
二、为什么需要 Redisson
2.1 自己实现分布式锁的痛点
如果用 Jedis/Lettuce 自己实现分布式锁,需要处理:
java
// 自己实现:代码量大,容易出错
public boolean tryLock(String key, String holderId, long timeout) {
// 1. 加锁(SETNX + 过期时间,必须原子操作)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, holderId, timeout, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock(String key, String holderId) {
// 2. 释放锁(必须验证持有者,必须用 Lua 脚本保证原子性)
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then "
+ "return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key), holderId);
}
// 3. 还需要自己实现:
// - 看门狗续期
// - 可重入支持
// - 公平锁
// - 红锁(RedLock)
// - 异步加锁
// - 锁的监听和通知
2.2 Redisson 帮你做了什么
java
// Redisson:一行代码搞定,内部自动处理所有细节
RLock lock = redissonClient.getLock("my-lock");
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
Redisson 内部自动处理了:
- 加锁的原子性(Lua 脚本)
- 释放锁的持有者验证
- 看门狗自动续期
- 可重入计数
- 等待队列和公平性
- 集群/哨兵模式的兼容
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
三、快速上手
3.1 引入依赖
xml
<!-- Maven -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
3.2 配置连接
yaml
# application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password: your-password
或者使用 Redisson 自己的配置:
yaml
# redisson.yml
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: "your-password"
connectionMinimumIdleSize: 5
connectionPoolSize: 10
3.3 注入使用
java
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private RedissonClient redissonClient;
public void processOrder(Integer orderId) {
// 获取锁对象(此时还没有加锁)
RLock lock = redissonClient.getLock("order-" + orderId);
try {
// 加锁:等待10秒,锁自动30秒过期
boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (!acquired) {
throw new RuntimeException("获取锁失败");
}
// 执行业务
doProcess(orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
四、Redisson 核心功能详解
4.1 分布式锁(RLock)
这是 Redisson 最常用的功能。
java
RLock lock = redissonClient.getLock("my-lock");
// 方式一:阻塞加锁(一直等到获取成功)
lock.lock();
// 方式二:尝试加锁(等待指定时间)
boolean success = lock.tryLock(10, TimeUnit.SECONDS);
// 方式三:尝试加锁 + 指定锁过期时间
boolean success = lock.tryLock(10, 30, TimeUnit.SECONDS);
// 参数1:最多等10秒
// 参数2:锁30秒后自动释放(不启用看门狗)
// 方式四:尝试加锁 + 看门狗自动续期
boolean success = lock.tryLock(10, TimeUnit.SECONDS);
// 不指定第二个参数 → 启用看门狗,锁不会自动过期
// 释放锁
lock.unlock();
4.2 公平锁(RFairLock)
普通锁是"谁抢到算谁的",公平锁是"先来先得"。
java
// 公平锁:按请求顺序获取锁
RLock fairLock = redissonClient.getFairLock("fair-lock");
fairLock.lock();
try {
// 业务逻辑
} finally {
fairLock.unlock();
}
4.3 读写锁(RReadWriteLock)
允许多个线程同时读,但写的时候独占。
java
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw-lock");
// 读锁:多个线程可以同时持有
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读取数据(多个线程可以同时读)
return dataRepository.findById(id);
} finally {
readLock.unlock();
}
// 写锁:独占,其他读和写都要等待
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 修改数据(独占)
dataRepository.save(data);
} finally {
writeLock.unlock();
}
4.4 分布式Map(RMap)
java
// 像使用 HashMap 一样,但数据存在 Redis
RMap<String, UserInfo> userCache = redissonClient.getMap("user-cache");
// 存入
userCache.put("user-123", userInfo);
// 读取
UserInfo info = userCache.get("user-123");
// 设置过期时间
userCache.put("user-123", userInfo, 30, TimeUnit.MINUTES);
4.5 分布式队列(RBlockingQueue)
java
// 生产者
RBlockingQueue<String> queue =
redissonClient.getBlockingQueue("task-queue");
queue.offer("task-001");
// 消费者(阻塞等待)
String task = queue.take(); // 如果队列为空,会阻塞等待
processTask(task);
4.6 限流器(RRateLimiter)
java
// 创建限流器:每秒最多10个请求
RRateLimiter limiter = redissonClient.getRateLimiter("api-limiter");
limiter.trySetRate(
RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
// 使用
if (limiter.tryAcquire()) {
// 允许通过
processRequest();
} else {
// 被限流
throw new RuntimeException("请求过于频繁");
}
五、RLock 的内部原理
5.1 加锁过程
Redisson 加锁时执行的 Lua 脚本(简化版):
lua
-- 如果锁不存在
if redis.call('exists', KEYS[1]) == 0 then
-- 创建锁,设置持有者和重入计数
redis.call('hset', KEYS[1], ARGV[2], 1)
-- 设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- 如果锁存在且是自己持有的(可重入)
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
-- 重入计数+1
redis.call('hincrby', KEYS[1], ARGV[2], 1)
-- 重置过期时间
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- 锁被别人持有,返回剩余过期时间
return redis.call('pttl', KEYS[1])
5.2 Redis 中锁的数据结构
Key: "my-lock"
Type: Hash
Value:
"holder-id-thread-1" → 2 (重入计数=2,表示加了2次锁)
TTL: 30000ms
5.3 释放锁过程
lua
-- 检查锁是否是自己持有的
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then
return nil -- 不是自己的锁,不操作
end
-- 重入计数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)
if counter > 0 then
-- 还有重入,只重置过期时间
redis.call('pexpire', KEYS[1], ARGV[2])
return 0
else
-- 计数归零,真正释放锁
redis.call('del', KEYS[1])
-- 通知等待的线程
redis.call('publish', KEYS[2], ARGV[3])
return 1
end
六、Redisson 与 Spring Boot 集成
6.1 自动配置(最简方式)
引入 redisson-spring-boot-starter 后,Redisson 会自动读取 spring.redis 配置创建 RedissonClient。
java
@Service
public class MyService {
@Resource
private RedissonClient redissonClient; // 自动注入
public void doSomething() {
RLock lock = redissonClient.getLock("key");
// ...
}
}
6.2 手动配置(需要自定义参数时)
java
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("password")
.setConnectionMinimumIdleSize(5)
.setConnectionPoolSize(20)
.setTimeout(3000)
.setRetryAttempts(3);
return Redisson.create(config);
}
}
6.3 集群模式配置
java
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useClusterServers()
.addNodeAddress(
"redis://node1:6379",
"redis://node2:6379",
"redis://node3:6379")
.setPassword("password");
return Redisson.create(config);
}
七、实战示例:商品库存扣减
7.1 完整代码
java
/**
* 库存服务实现.
*/
@Service
public class StockServiceImpl implements StockService {
@Resource
private RedissonClient redissonClient;
@Resource
private StockRepository stockRepository;
/**
* 扣减库存(使用分布式锁防止超卖).
*
* @param skuId 商品SKU ID
* @param quantity 扣减数量
* @return true=扣减成功,false=库存不足
*/
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Integer skuId, Integer quantity) {
// 按SKU维度加锁
String lockKey = "stock-deduct-" + skuId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 等待5秒,不指定leaseTime(启用看门狗)
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
if (!acquired) {
log.warn("获取库存锁超时, skuId:{}", skuId);
return false;
}
// 查询当前库存
Stock stock = stockRepository.findBySkuId(skuId);
if (stock == null || stock.getQuantity() < quantity) {
log.info("库存不足, skuId:{}, 当前:{}, 需要:{}",
skuId,
stock == null ? 0 : stock.getQuantity(),
quantity);
return false;
}
// 扣减库存
stock.setQuantity(stock.getQuantity() - quantity);
stockRepository.save(stock);
log.info("库存扣减成功, skuId:{}, 扣减:{}, 剩余:{}",
skuId, quantity, stock.getQuantity());
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("获取库存锁被中断, skuId:{}", skuId);
return false;
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
7.2 关键代码解读
java
// 1. getLock("key") --- 获取锁对象(还没加锁)
RLock lock = redissonClient.getLock(lockKey);
// 2. tryLock(waitTime) --- 尝试加锁
// - 如果锁空闲:立即获取,返回 true
// - 如果锁被占用:等待最多5秒
// - 5秒内锁释放了:获取成功,返回 true
// - 5秒后仍被占用:放弃,返回 false
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
// 3. isHeldByCurrentThread() --- 检查当前线程是否持有锁
// 防止释放别人的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
八、Redisson 功能全景图
RedissonClient
│
├── 分布式锁
│ ├── RLock(可重入锁)
│ ├── RFairLock(公平锁)
│ ├── RReadWriteLock(读写锁)
│ ├── RSemaphore(信号量)
│ └── RCountDownLatch(倒计时门闩)
│
├── 分布式集合
│ ├── RMap(分布式Map)
│ ├── RSet(分布式Set)
│ ├── RList(分布式List)
│ ├── RSortedSet(有序集合)
│ └── RQueue / RBlockingQueue(队列)
│
├── 分布式服务
│ ├── RRemoteService(远程调用)
│ ├── RScheduledExecutorService(定时任务)
│ └── RExecutorService(分布式执行器)
│
└── 其他工具
├── RRateLimiter(限流器)
├── RAtomicLong(原子计数器)
├── RTopic(发布订阅)
└── RBloomFilter(布隆过滤器)
九、常见问题
Q1:Redisson 和 Spring Cache 能一起用吗?
可以。Redisson 提供了 Spring Cache 的实现:
java
@Cacheable(cacheNames = "userCache", key = "#userId")
public UserInfo getUserById(Integer userId) {
return userRepository.findById(userId).orElse(null);
}
Q2:Redisson 支持 Redis 集群吗?
支持。Redisson 支持单机、哨兵、集群、主从等所有 Redis 部署模式。
Q3:Redisson 的性能如何?
- 加锁/释放锁:通常 1-3 毫秒(取决于网络延迟)
- 比自己用 RedisTemplate 实现稍慢(多了一些内部逻辑)
- 但比 ZooKeeper 实现快很多
Q4:项目中已经有 RedisTemplate 了,还需要 Redisson 吗?
两者可以共存。RedisTemplate 用于简单的缓存读写,Redisson 用于分布式锁、限流等高级功能。
十、总结
| 问题 | 答案 |
|---|---|
| Redisson 是什么? | 基于 Redis 的 Java 分布式工具框架 |
| 和 Jedis/Lettuce 的区别? | Jedis/Lettuce 是底层客户端,Redisson 是高级封装 |
| 最常用的功能? | 分布式锁(RLock) |
| 为什么不自己实现分布式锁? | 自己实现需要处理原子性、续期、可重入、持有者验证等细节 |
| 如何引入? | 加 redisson-spring-boot-starter 依赖即可 |
| 性能影响? | 加锁/释放锁约 1-3ms,对大多数业务可忽略 |