当前位置: 首页 > news >正文

SpringBoot整合Lock4j:基于Redisson的分布式锁实战与深度定制

1. 为什么需要分布式锁?

想象一下双十一秒杀场景:10000人同时抢购100台手机,如果不做并发控制,系统可能会超卖。单机环境下用synchronized或ReentrantLock就能解决,但在分布式系统中,多个服务实例同时运行,本地锁就失效了。这就是分布式锁的用武之地——它能在跨进程、跨服务器的环境中实现互斥访问。

我去年参与过一个电商项目就遇到过这个问题。当时库存扣减用了本地锁,结果上线后出现了超卖。后来用Redisson改造,配合Lock4j的注解式开发,三行代码就解决了问题。下面我会手把手带你实现这套方案。

2. 环境搭建与基础配置

2.1 依赖引入

首先创建SpringBoot项目,在pom.xml中添加关键依赖:

<dependencies> <!-- Lock4j核心 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>lock4j-core</artifactId> <version>2.2.2</version> </dependency> <!-- Redisson实现 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- SpringBoot Redis Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>

注意:如果项目已存在RedisTemplate相关依赖,建议排除lettuce-core以避免冲突

2.2 Redisson配置

在application.yml中添加配置:

spring: redis: host: your-redis-host port: 6379 password: your-password database: 0 lock4j: acquire-timeout: 3000 # 获取锁超时时间(ms) expire: 30000 # 锁自动释放时间(ms) primary-executor: com.baomidou.lock.executor.RedissonLockExecutor

这里有个坑我踩过:expire时间不能设置过短。曾经设成5秒,结果业务逻辑还没执行完锁就自动释放了,导致数据错乱。建议根据业务执行时间合理设置,一般要比平均执行时间长30%。

3. 基础锁功能实战

3.1 注解式锁的使用

Lock4j最方便的就是@Lock4j注解,直接在方法上添加就能实现分布式锁:

@GetMapping("/seckill") @Lock4j(keys = {"#productId"}, expire = 60000) public String seckill(Long productId) { // 1. 查询库存 int stock = productService.getStock(productId); if (stock <= 0) { return "已售罄"; } // 2. 扣减库存 productService.reduceStock(productId); // 3. 创建订单 return orderService.create(productId); }

这个注解有几个关键参数:

  • keys:锁的Key,支持SpEL表达式。上例中用商品ID作为锁Key,确保不同商品不会互相阻塞
  • expire:锁过期时间,覆盖全局配置
  • acquireTimeout:获取锁等待时间

3.2 锁的隔离测试

我们模拟并发请求来验证锁的效果:

@SpringBootTest class SeckillTest { @Test void testConcurrentSeckill() throws InterruptedException { int threadCount = 50; ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { executor.execute(() -> { restTemplate.getForObject("/seckill?productId=1", String.class); latch.countDown(); }); } latch.await(); System.out.println("库存剩余:" + productService.getStock(1)); } }

运行后会看到库存只扣减一次,证明锁生效了。如果不加锁,库存很可能会被扣成负数。

4. 高级定制方案

4.1 自定义锁Key生成

默认的Key生成规则可能不满足复杂场景。比如我们需要加上用户ID做二级隔离:

@Component public class UserProductKeyBuilder implements LockKeyBuilder { @Override public String buildKey(MethodInvocation invocation, String[] definitionKeys) { // 获取方法参数值 Object[] args = invocation.getArguments(); Long productId = (Long) args[0]; Long userId = (Long) args[1]; // 格式:lock4j:product:{productId}:user:{userId} return String.format("product:%d:user:%d", productId, userId); } }

然后在注解中指定:

@Lock4j(keys = {"#productId", "#userId"}, keyBuilder = UserProductKeyBuilder.class) public String seckill(Long productId, Long userId) { // ... }

4.2 锁失败个性化处理

默认获取锁失败会抛出LockFailureException,我们可以自定义处理逻辑:

@Component public class SeckillLockFailureStrategy implements LockFailureStrategy { @Override public void onLockFailure(String key, Method method, Object[] args) { Long productId = (Long) args[0]; throw new BusinessException("商品[" + productId + "]正在被抢购,请稍后再试"); } }

在application.yml中配置使用这个策略:

lock4j: failure-strategy: com.yourpackage.SeckillLockFailureStrategy

4.3 锁执行器扩展

如果需要对接其他锁实现(比如Zookeeper),可以继承AbstractLockExecutor:

public class ZookeeperLockExecutor extends AbstractLockExecutor<InterProcessMutex> { @Autowired private CuratorFramework curatorFramework; @Override public InterProcessMutex acquire(String lockKey, String lockValue, long expire, long acquireTimeout) { InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/locks/" + lockKey); try { if (mutex.acquire(acquireTimeout, TimeUnit.MILLISECONDS)) { return mutex; } } catch (Exception e) { throw new LockException("获取Zookeeper锁失败", e); } return null; } @Override public boolean releaseLock(String key, String value, InterProcessMutex lockInstance) { try { lockInstance.release(); return true; } catch (Exception e) { return false; } } }

5. 生产环境注意事项

5.1 锁的粒度控制

锁的粒度既不能太粗也不能太细:

  • 太粗:比如整个秒杀方法加锁,会导致并发能力骤降
  • 太细:比如每个SKU单独加锁,可能引发死锁

建议方案:

  1. 按商品ID加锁
  2. 热点商品做分片(如商品ID后两位mod 10)
  3. 读多写少场景用读写锁

5.2 锁续期机制

Redisson默认提供看门狗机制,但要注意:

  • 业务执行时间超过expire时,需要手动续期
  • 可以使用Redisson的RLock的expireAsync方法
@Lock4j(keys = {"#id"}) public void longTimeTask(String id) { RLock lock = redissonClient.getLock("lock4j:" + id); try { // 每30秒续期一次 lock.expire(30, TimeUnit.SECONDS); // 业务逻辑... } finally { lock.unlock(); } }

5.3 监控与报警

建议通过Redis的INFO命令监控锁状态:

# 查看锁Key数量 redis-cli --stat # 查看特定锁信息 redis-cli HGETALL "lock4j:your_lock_key"

在SpringBoot中可以通过定时任务上报监控数据:

@Scheduled(fixedRate = 60000) public void reportLockMetrics() { Map<String, String> lockStats = redisTemplate.opsForHash() .entries("lock4j:stats"); metricsClient.report("distributed_lock", lockStats); }
http://www.jsqmd.com/news/529799/

相关文章:

  • 卫星遥感海岸线分析:从太空视角重新定义海岸监测的三大技术突破
  • 从微软与LinkedIn的Career Essentials in Generative AI课程看生成式AI核心技术栈
  • OpenClaw技能市场:nanobot镜像十大实用插件推荐
  • 频谱仪关键参数解析与测试应用指南
  • 英语_阅读_public transportation systems_待读
  • 从零开始理解GPU高速互联:NVLink和InfiniBand的保姆级科普
  • 开源API资源利用:零成本AI开发的技术实践指南
  • Linux 调度器中的等待队列:wait.c/swait.c 的同步原语实现
  • 如何在VMware ESXi 6.7中突破性实现Realtek RTL8125 2.5G网卡驱动支持
  • 免费m4s转mp4工具终极指南:永久保存你的B站缓存视频
  • Python连接高斯数据库SASL认证失败?3种安全解决方案实测对比
  • 零基础海岸线监测指南:如何用卫星遥感守护海滩的未来
  • TSL2561光照传感器驱动开发与照度计算实战
  • 喜马拉雅新版xm-sign生成原理详解:从dws.1.6.8.js到browserid/sessionid的完整流程
  • HJ143 小红的好排列
  • m4s格式转换工具终极指南:如何将B站缓存视频永久保存为MP4?
  • Linux 调度器中的完成量:completion.c 的线程同步逻辑
  • 功能上下文划分与测试替身选择策略
  • BilibiliDown高效下载指南:3个核心技巧实现B站视频批量下载
  • Java基础部分面试题(2026最新)
  • CLion+Qt6实战:从零搭建学生信息管理系统与团队Git协作
  • Django REST Framework全面解析与实战指南:构建企业级API的架构与实践
  • BilibiliDown:如何轻松获取B站高清视频与音频的完整解决方案
  • 测试工序:让架构设计真正落地的关键机制
  • Spark vs Hadoop终极对决:内存计算如何帮你省下50%集群成本?
  • Escape From Tarkov训练器终极指南:离线模式下的智能游戏辅助深度解析
  • Xinference-v1.17.1在嵌入式Linux中的轻量化部署
  • 数据结构:哈希表的原理与 C++ 数组模拟实现
  • 遥感小白也能懂:Git-RSCLIP提示词从入门到精通
  • Adafruit GFX图形库深度实战指南:从原理到优化的嵌入式显示解决方案