从零理解 Redisson:Java 分布式工具箱的入门与实战
从零理解 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 高级客户端 | 封装了分布式锁、集合、队列等高级功能 |
// 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:面向对象的方式,屏蔽底层命令RLocklock=redissonClient.getLock("lock-key");lock.lock();// 内部自动处理 SETNX、过期时间、看门狗等二、为什么需要 Redisson
2.1 自己实现分布式锁的痛点
如果用 Jedis/Lettuce 自己实现分布式锁,需要处理:
// 自己实现:代码量大,容易出错publicbooleantryLock(Stringkey,StringholderId,longtimeout){// 1. 加锁(SETNX + 过期时间,必须原子操作)Booleansuccess=redisTemplate.opsForValue().setIfAbsent(key,holderId,timeout,TimeUnit.SECONDS);returnBoolean.TRUE.equals(success);}publicvoidunlock(Stringkey,StringholderId){// 2. 释放锁(必须验证持有者,必须用 Lua 脚本保证原子性)Stringscript="if redis.call('get',KEYS[1]) == ARGV[1] then "+"return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(newDefaultRedisScript<>(script,Long.class),Collections.singletonList(key),holderId);}// 3. 还需要自己实现:// - 看门狗续期// - 可重入支持// - 公平锁// - 红锁(RedLock)// - 异步加锁// - 锁的监听和通知2.2 Redisson 帮你做了什么
// Redisson:一行代码搞定,内部自动处理所有细节RLocklock=redissonClient.getLock("my-lock");lock.lock();try{// 业务逻辑}finally{lock.unlock();}Redisson 内部自动处理了:
- 加锁的原子性(Lua 脚本)
- 释放锁的持有者验证
- 看门狗自动续期
- 可重入计数
- 等待队列和公平性
- 集群/哨兵模式的兼容
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
三、快速上手
3.1 引入依赖
<!-- Maven --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.27.0</version></dependency>3.2 配置连接
# application.ymlspring:redis:host:127.0.0.1port:6379password:your-password或者使用 Redisson 自己的配置:
# redisson.ymlsingleServerConfig:address:"redis://127.0.0.1:6379"password:"your-password"connectionMinimumIdleSize:5connectionPoolSize:103.3 注入使用
@ServicepublicclassOrderServiceImplimplementsOrderService{@ResourceprivateRedissonClientredissonClient;publicvoidprocessOrder(IntegerorderId){// 获取锁对象(此时还没有加锁)RLocklock=redissonClient.getLock("order-"+orderId);try{// 加锁:等待10秒,锁自动30秒过期booleanacquired=lock.tryLock(10,30,TimeUnit.SECONDS);if(!acquired){thrownewRuntimeException("获取锁失败");}// 执行业务doProcess(orderId);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{// 释放锁if(lock.isHeldByCurrentThread()){lock.unlock();}}}}四、Redisson 核心功能详解
4.1 分布式锁(RLock)
这是 Redisson 最常用的功能。
RLocklock=redissonClient.getLock("my-lock");// 方式一:阻塞加锁(一直等到获取成功)lock.lock();// 方式二:尝试加锁(等待指定时间)booleansuccess=lock.tryLock(10,TimeUnit.SECONDS);// 方式三:尝试加锁 + 指定锁过期时间booleansuccess=lock.tryLock(10,30,TimeUnit.SECONDS);// 参数1:最多等10秒// 参数2:锁30秒后自动释放(不启用看门狗)// 方式四:尝试加锁 + 看门狗自动续期booleansuccess=lock.tryLock(10,TimeUnit.SECONDS);// 不指定第二个参数 → 启用看门狗,锁不会自动过期// 释放锁lock.unlock();4.2 公平锁(RFairLock)
普通锁是"谁抢到算谁的",公平锁是"先来先得"。
// 公平锁:按请求顺序获取锁RLockfairLock=redissonClient.getFairLock("fair-lock");fairLock.lock();try{// 业务逻辑}finally{fairLock.unlock();}4.3 读写锁(RReadWriteLock)
允许多个线程同时读,但写的时候独占。
RReadWriteLockrwLock=redissonClient.getReadWriteLock("rw-lock");// 读锁:多个线程可以同时持有RLockreadLock=rwLock.readLock();readLock.lock();try{// 读取数据(多个线程可以同时读)returndataRepository.findById(id);}finally{readLock.unlock();}// 写锁:独占,其他读和写都要等待RLockwriteLock=rwLock.writeLock();writeLock.lock();try{// 修改数据(独占)dataRepository.save(data);}finally{writeLock.unlock();}4.4 分布式Map(RMap)
// 像使用 HashMap 一样,但数据存在 RedisRMap<String,UserInfo>userCache=redissonClient.getMap("user-cache");// 存入userCache.put("user-123",userInfo);// 读取UserInfoinfo=userCache.get("user-123");// 设置过期时间userCache.put("user-123",userInfo,30,TimeUnit.MINUTES);4.5 分布式队列(RBlockingQueue)
// 生产者RBlockingQueue<String>queue=redissonClient.getBlockingQueue("task-queue");queue.offer("task-001");// 消费者(阻塞等待)Stringtask=queue.take();// 如果队列为空,会阻塞等待processTask(task);4.6 限流器(RRateLimiter)
// 创建限流器:每秒最多10个请求RRateLimiterlimiter=redissonClient.getRateLimiter("api-limiter");limiter.trySetRate(RateType.OVERALL,10,1,RateIntervalUnit.SECONDS);// 使用if(limiter.tryAcquire()){// 允许通过processRequest();}else{// 被限流thrownewRuntimeException("请求过于频繁");}五、RLock 的内部原理
5.1 加锁过程
Redisson 加锁时执行的 Lua 脚本(简化版):
-- 如果锁不存在ifredis.call('exists',KEYS[1])==0then-- 创建锁,设置持有者和重入计数redis.call('hset',KEYS[1],ARGV[2],1)-- 设置过期时间redis.call('pexpire',KEYS[1],ARGV[1])returnnilend-- 如果锁存在且是自己持有的(可重入)ifredis.call('hexists',KEYS[1],ARGV[2])==1then-- 重入计数+1redis.call('hincrby',KEYS[1],ARGV[2],1)-- 重置过期时间redis.call('pexpire',KEYS[1],ARGV[1])returnnilend-- 锁被别人持有,返回剩余过期时间returnredis.call('pttl',KEYS[1])5.2 Redis 中锁的数据结构
Key: "my-lock" Type: Hash Value: "holder-id-thread-1" → 2 (重入计数=2,表示加了2次锁) TTL: 30000ms5.3 释放锁过程
-- 检查锁是否是自己持有的ifredis.call('hexists',KEYS[1],ARGV[1])==0thenreturnnil-- 不是自己的锁,不操作end-- 重入计数-1localcounter=redis.call('hincrby',KEYS[1],ARGV[1],-1)ifcounter>0then-- 还有重入,只重置过期时间redis.call('pexpire',KEYS[1],ARGV[2])return0else-- 计数归零,真正释放锁redis.call('del',KEYS[1])-- 通知等待的线程redis.call('publish',KEYS[2],ARGV[3])return1end六、Redisson 与 Spring Boot 集成
6.1 自动配置(最简方式)
引入redisson-spring-boot-starter后,Redisson 会自动读取spring.redis配置创建RedissonClient。
@ServicepublicclassMyService{@ResourceprivateRedissonClientredissonClient;// 自动注入publicvoiddoSomething(){RLocklock=redissonClient.getLock("key");// ...}}6.2 手动配置(需要自定义参数时)
@ConfigurationpublicclassRedissonConfig{@BeanpublicRedissonClientredissonClient(){Configconfig=newConfig();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("password").setConnectionMinimumIdleSize(5).setConnectionPoolSize(20).setTimeout(3000).setRetryAttempts(3);returnRedisson.create(config);}}6.3 集群模式配置
@BeanpublicRedissonClientredissonClient(){Configconfig=newConfig();config.useClusterServers().addNodeAddress("redis://node1:6379","redis://node2:6379","redis://node3:6379").setPassword("password");returnRedisson.create(config);}七、实战示例:商品库存扣减
7.1 完整代码
/** * 库存服务实现. */@ServicepublicclassStockServiceImplimplementsStockService{@ResourceprivateRedissonClientredissonClient;@ResourceprivateStockRepositorystockRepository;/** * 扣减库存(使用分布式锁防止超卖). * * @param skuId 商品SKU ID * @param quantity 扣减数量 * @return true=扣减成功,false=库存不足 */@Transactional(rollbackFor=Exception.class)publicbooleandeductStock(IntegerskuId,Integerquantity){// 按SKU维度加锁StringlockKey="stock-deduct-"+skuId;RLocklock=redissonClient.getLock(lockKey);try{// 等待5秒,不指定leaseTime(启用看门狗)booleanacquired=lock.tryLock(5,TimeUnit.SECONDS);if(!acquired){log.warn("获取库存锁超时, skuId:{}",skuId);returnfalse;}// 查询当前库存Stockstock=stockRepository.findBySkuId(skuId);if(stock==null||stock.getQuantity()<quantity){log.info("库存不足, skuId:{}, 当前:{}, 需要:{}",skuId,stock==null?0:stock.getQuantity(),quantity);returnfalse;}// 扣减库存stock.setQuantity(stock.getQuantity()-quantity);stockRepository.save(stock);log.info("库存扣减成功, skuId:{}, 扣减:{}, 剩余:{}",skuId,quantity,stock.getQuantity());returntrue;}catch(InterruptedExceptione){Thread.currentThread().interrupt();log.warn("获取库存锁被中断, skuId:{}",skuId);returnfalse;}finally{// 释放锁if(lock.isHeldByCurrentThread()){lock.unlock();}}}}7.2 关键代码解读
// 1. getLock("key") — 获取锁对象(还没加锁)RLocklock=redissonClient.getLock(lockKey);// 2. tryLock(waitTime) — 尝试加锁// - 如果锁空闲:立即获取,返回 true// - 如果锁被占用:等待最多5秒// - 5秒内锁释放了:获取成功,返回 true// - 5秒后仍被占用:放弃,返回 falsebooleanacquired=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 的实现:
@Cacheable(cacheNames="userCache",key="#userId")publicUserInfogetUserById(IntegeruserId){returnuserRepository.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,对大多数业务可忽略 |
