自己用 Redis SETNX 实现的分布式锁有各种坑(锁续期、误删、非原子)。Redisson 是官方推荐的分布式锁方案,开箱即用。
一、引入依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.25.0</version></dependency>二、配置
@ConfigurationpublicclassRedissonConfig{@BeanpublicRedissonClientredissonClient(){Configconfig=newConfig();config.useSingleServer().setAddress("redis://localhost:6379");returnRedisson.create(config);}}三、基础用法
@ServicepublicclassSeckillService{@AutowiredprivateRedissonClientredissonClient;@AutowiredprivateStringRedisTemplateredisTemplate;publicResultVO<?>seckill(LongproductId,LonguserId){StringlockKey="lock:seckill:"+productId;RLocklock=redissonClient.getLock(lockKey);// 尝试获取锁,最多等3秒,锁自动续期booleanlocked=false;try{locked=lock.tryLock(3,30,TimeUnit.SECONDS);if(!locked){returnResultVO.error(429,"操作太频繁,请稍后重试");}// 业务逻辑returndoSeckill(productId,userId);}catch(InterruptedExceptione){returnResultVO.error(500,"系统异常");}finally{// 释放锁(只有持有锁的线程能释放)if(locked&&lock.isHeldByCurrentThread()){lock.unlock();}}}}四、注解式锁
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceRedissonLock{/** 锁的key(支持SpEL) */Stringkey();/** 等待锁时间 */longwaitTime()default3;/** 锁过期时间 */longleaseTime()default30;}@Aspect@ComponentpublicclassRedissonLockAspect{@AutowiredprivateRedissonClientredissonClient;@Around("@annotation(lock)")publicObjectaround(ProceedingJoinPointpjp,RedissonLocklock)throwsThrowable{Stringkey=parseKey(lock.key(),pjp);RLockrlock=redissonClient.getLock(key);booleanlocked=rlock.tryLock(lock.waitTime(),lock.leaseTime(),TimeUnit.SECONDS);if(!locked){thrownewBusinessException(429,"操作太频繁");}try{returnpjp.proceed();}finally{if(rlock.isHeldByCurrentThread()){rlock.unlock();}}}}五、可重入锁
// Redisson 的锁是可重入的// 同一个线程可以多次获取同一个锁publicvoidmethodA(){RLocklock=redissonClient.getLock("lock:test");lock.lock();methodB();// 内部再次获取同一个锁,不会死锁lock.unlock();}publicvoidmethodB(){RLocklock=redissonClient.getLock("lock:test");lock.lock();// 业务逻辑lock.unlock();}六、公平锁
// 公平锁:按请求顺序获取锁RLockfairLock=redissonClient.getFairLock("lock:fair");七、读写锁
// 读锁:多个线程可同时读// 写锁:独占RReadWriteLockrwLock=redissonClient.getReadWriteLock("lock:rw");RLockreadLock=rwLock.readLock();// 读锁RLockwriteLock=rwLock.writeLock();// 写锁八、Redisson vs 手写 SETNX
| 特性 | 手写 SETNX | Redisson |
|---|---|---|
| 锁续期 | ❌ 自己实现 | ✅ 自动续期 |
| 释放校验 | ❌ 可能误删 | ✅ 校验线程 |
| 可重入 | ❌ | ✅ |
| 公平锁 | ❌ | ✅ |
| 读写锁 | ❌ | ✅ |
| 看门狗 | ❌ | ✅ |
九、看门狗机制
// Redisson 的锁默认使用看门狗(watchdog)// 如果业务执行时间超过锁的过期时间(默认30秒),看门狗会自动续期// 每 10 秒续期一次,直到业务完成释放锁// 注意:只有不指定 leaseTime 时看门狗才生效RLocklock=redissonClient.getLock("lock:test");lock.lock();// 不指定leaseTime → 看门狗生效十、秒杀场景最佳实践
@ServicepublicclassSeckillService{@RedissonLock(key="seckill:#productId",waitTime=3,leaseTime=30)publicResultVO<?>doSeckill(@PathVariableLongproductId,LonguserId){// 前置校验SeckillProductproduct=productService.getById(productId);if(product==null||product.getStatus()!=1){returnResultVO.error(404,"商品不存在");}// 时间校验LocalDateTimenow=LocalDateTime.now();if(now.isBefore(product.getSeckillStart())){returnResultVO.error(400,"秒杀未开始");}// 库存扣减introws=productMapper.decrementStock(productId);if(rows==0){returnResultVO.error(400,"库存不足");}// 创建订单returnResultVO.success("秒杀成功");}}💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/MySQL 实战干货,不让你白来。