Spring Boot Starter实现基于 Redis + Lua 的分布式锁封装与实践 Spring Boot Starter实现基于 Redis Lua 的分布式锁封装与实践一、涉及的技术知识点1.1 Redis 分布式锁核心原理知识点说明Redis 单线程模型保证命令按序执行天然支持原子操作SETNXSET if Not eXists只有 key 不存在时才设置成功是分布式锁的基础PEXPIRE设置 key 毫秒级过期时间防止死锁HSET/HGET用 Hash 结构存储锁持有者标识requestId支持安全释放Lua 脚本多个 Redis 命令在服务端原子执行保证加锁/解锁操作的原子性锁超时自动释放持有者崩溃时锁不会永久占用可重入防护通过 requestId 标识持有者只有持有者能释放锁1.2 Spring Boot 自动配置机制知识点说明spring.factoriesSpring Boot SPI 加载机制声明自动配置类ConfigurationBean声明式注册 Spring BeanAutowired/Resource自动注入StringRedisTemplate条件装配可配合ConditionalOnClass实现按需加载1.3 Java 语言特性知识点说明Closeable接口实现自动资源管理支持 try-with-resourcesLambda 表达式RedisCallback用 Lambda 简化回调写法自旋等待 超时退出tryLock内循环重试直到超时包级访问控制构造器和 setter 使用包级权限只允许通过 Provider 创建1.4 设计模式模式应用工厂模式DistributedLockProvider作为工厂创建DistributedLock实例模板方法StringRedisTemplate.execute(RedisCallback)统一连接管理策略模式不同TimeUnitleaseTime组合适应不同场景注博客https://blog.csdn.net/badao_liumang_qizhi二、封装 Spring Boot Starter JAR 包的流程2.1 项目结构xxx-xxx-lib-lock/ ├── pom.xml └── src/main/ ├── java/xx/xx/lib/lock/ │ ├── LockConfig.java // 自动配置类 │ ├── DistributedLockProvider.java // 锁工厂对外暴露 │ └── DistributedLock.java // 锁实现核心逻辑 └── resources/META-INF/ └── spring.factories // 自动配置声明2.2 封装步骤定义核心接口/类— 确定对外暴露的 APIProvider Lock实现核心逻辑— Redis Lua 脚本加锁解锁编写自动配置—Configuration类注册 Bean声明 spring.factories— 让 Spring Boot 自动发现配置类Maven 打包发布—mvn deploy到私有仓库2.3 引入方使用流程pom.xml添加依赖确保项目已配置spring.redisStringRedisTemplate可用注入DistributedLockProvider直接使用无需任何额外配置或注解开启三、通用示例代码3.1 Starter 封装侧pom.xmlprojectgroupIdcom.example/groupIdartifactIdexample-distributed-lock-starter/artifactIdversion1.0.0/versionpackagingjar/packagingdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactIdscopeprovided/scope/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdscopeprovided/scope/dependency/dependencies/projectDistributedLock.javapackagecom.example.lock;importjava.io.Closeable;importjava.util.concurrent.TimeUnit;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.data.redis.core.StringRedisTemplate;/** * 基于Redis的分布式锁实现. * 支持自旋等待、超时释放、安全解锁. */publicclassDistributedLockimplementsCloseable{privatestaticfinalLoggerlogLoggerFactory.getLogger(DistributedLock.class);privatestaticfinalStringLOCK_PREFIXapp-locks:{%s};privatestaticfinalStringREQUEST_ID_KEYrid-{%s};/** 加锁Lua脚本key不存在时设置hash并设过期时间. */privatestaticfinalStringLOCK_SCRIPTif (redis.call(exists, KEYS[1]) 0) then redis.call(hset, KEYS[1], KEYS[2], ARGV[1]); redis.call(pexpire, KEYS[1], ARGV[2]); return 1; else return 0; end;/** 解锁Lua脚本校验requestId匹配后删除key. */privatestaticfinalStringUNLOCK_SCRIPTif redis.call(hget, KEYS[1], KEYS[2]) ARGV[1] then redis.call(del, KEYS[1]); return 1; else return 0; end;;privatefinalStringRedisTemplateredisTemplate;privatefinalStringkey;privatefinalStringlockKey;privatefinalStringrequestIdKey;privatefinalStringrequestId;privateTimeUnitleaseTimeUnitTimeUnit.MINUTES;privatelongleaseTime10;privatebooleanlockedfalse;DistributedLock(StringRedisTemplateredisTemplate,Stringkey,StringrequestId){this.redisTemplateredisTemplate;this.keykey;this.requestId(requestIdnull||requestId.isEmpty())?java.util.UUID.randomUUID().toString().replace(-,):requestId;this.lockKeyString.format(LOCK_PREFIX,key);this.requestIdKeyString.format(REQUEST_ID_KEY,key);}voidsetLeaseTimeUnit(TimeUnitleaseTimeUnit){if(leaseTimeUnit!null){this.leaseTimeUnitleaseTimeUnit;}}voidsetLeaseTime(longleaseTime){if(leaseTime0){this.leaseTimeleaseTime;}}/** * 尝试获取锁支持超时等待. * * param waitTimeUnit 等待时间单位 * param waitTime 最大等待时间 * return true-获取成功false-超时未获取到 */publicbooleantryLock(TimeUnitwaitTimeUnit,longwaitTime){BooleanresultredisTemplate.execute(connection-{longwaitMilliswaitTimeUnit.toMillis(waitTime);longstartTimeSystem.currentTimeMillis();longdeadlinestartTimewaitMillis;// 第一次尝试if(doLock(connection)){returnBoolean.TRUE;}// 自旋重试直到超时while(System.currentTimeMillis()deadline){if(doLock(connection)){returnBoolean.TRUE;}try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){log.error(获取锁等待时被中断,e);Thread.currentThread().interrupt();returnBoolean.FALSE;}}returnBoolean.FALSE;});this.lockedBoolean.TRUE.equals(result);log.debug(获取锁结果 - {} - {},key,locked);returnthis.locked;}/** * 释放锁. * * return true-释放成功false-释放失败非持有者或已过期 */publicbooleanunlock(){if(!locked){log.info(未锁无需操作 - {},key);returntrue;}log.info(准备释放锁 - {},key);BooleanresultredisTemplate.execute(connection-{longexpireMillisleaseTimeUnit.toMillis(leaseTime);LongevalResult(Long)connection.eval(UNLOCK_SCRIPT.getBytes(),org.springframework.data.redis.connection.ReturnType.INTEGER,2,lockKey.getBytes(),requestIdKey.getBytes(),requestId.getBytes());log.info(释放锁数量 - {} - {} - {},requestId,key,evalResult);returnevalResult!nullevalResult!0?Boolean.TRUE:Boolean.FALSE;});this.locked!Boolean.TRUE.equals(result);returnBoolean.TRUE.equals(result);}Overridepublicvoidclose(){unlock();}privatebooleandoLock(org.springframework.data.redis.connection.RedisConnectionconnection){longexpireMillisleaseTimeUnit.toMillis(leaseTime);LongevalResult(Long)connection.eval(LOCK_SCRIPT.getBytes(),org.springframework.data.redis.connection.ReturnType.INTEGER,2,lockKey.getBytes(),requestIdKey.getBytes(),requestId.getBytes(),String.valueOf(expireMillis).getBytes());returnevalResult!nullevalResult!0;}}DistributedLockProvider.javapackagecom.example.lock;importjava.util.concurrent.TimeUnit;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;/** * 分布式锁工厂. * 通过注入此类获取锁实例. */publicclassDistributedLockProvider{AutowiredprivateStringRedisTemplateredisTemplate;/** * 获取分布式锁默认过期10分钟. * * param key 业务锁标识 * return 锁实例 */publicDistributedLockgetLock(Stringkey){returnnewDistributedLock(redisTemplate,key,getCurrentRequestId());}/** * 获取分布式锁自定义过期时间. * * param key 业务锁标识 * param leaseTimeUnit 过期时间单位 * param leaseTime 过期时间值 * return 锁实例 */publicDistributedLockgetLock(Stringkey,TimeUnitleaseTimeUnit,longleaseTime){DistributedLocklocknewDistributedLock(redisTemplate,key,getCurrentRequestId());lock.setLeaseTime(leaseTime);lock.setLeaseTimeUnit(leaseTimeUnit);returnlock;}privateStringgetCurrentRequestId(){// 从链路追踪上下文获取requestId保证同一请求内的锁标识一致// 实际项目中可替换为 MDC.get(requestId) 或自定义实现returnjava.util.UUID.randomUUID().toString().replace(-,);}}LockConfig.javapackagecom.example.lock;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * 分布式锁自动配置类. */ConfigurationpublicclassLockConfig{BeanpublicDistributedLockProviderdistributedLockProvider(){returnnewDistributedLockProvider();}}META-INF/spring.factories# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.lock.LockConfig3.2 引入方使用侧pom.xml 添加依赖dependencygroupIdcom.example/groupIdartifactIdexample-distributed-lock-starter/artifactIdversion1.0.0/version/dependencyapplication.yml确保 Redis 已配置spring:redis:host:localhostport:6379password:your_passworddatabase:0使用示例1try-with-resources 自动释放最佳实践ServicepublicclassOrderService{ResourceprivateDistributedLockProviderdistributedLockProvider;publicvoidprocessOrder(StringorderId){StringlockKeyorder_process_orderId;// 锁过期5分钟等待获取锁最多3分钟try(DistributedLocklockdistributedLockProvider.getLock(lockKey,TimeUnit.MINUTES,5)){if(lock.tryLock(TimeUnit.MINUTES,3)){// 获取锁成功安全执行业务逻辑doProcessOrder(orderId);}else{// 超时未获取到锁thrownewRuntimeException(订单正在处理中请勿重复操作);}}// try 块结束自动调用 close() - unlock()}}使用示例2非阻塞立即返回publicvoidsubmitTask(StringtaskId){StringlockKeytask_submit_taskId;try(DistributedLocklockdistributedLockProvider.getLock(lockKey)){// 等待时间为0立即返回结果if(lock.tryLock(TimeUnit.MILLISECONDS,0)){executeTask(taskId);}else{thrownewRuntimeException(任务已在执行中);}}}使用示例3事务提交后释放锁TransactionalpublicvoidupdateStock(IntegeritemId){Stringkeystock_update_itemId;DistributedLocklockdistributedLockProvider.getLock(key,TimeUnit.MINUTES,3);if(!lock.tryLock(TimeUnit.MINUTES,1)){thrownewRuntimeException(库存操作冲突);}// 在事务提交后再释放锁保证其他线程读到的是已提交数据TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){OverridepublicvoidafterCommit(){lock.unlock();}});// 执行库存扣减doUpdateStock(itemId);}使用示例4MQ 消费防重复处理ComponentpublicclassOrderMqConsumer{ResourceprivateDistributedLockProviderdistributedLockProvider;publicvoidonMessage(MapString,Objectmessage){StringorderId(String)message.get(orderId);StringlockKeymq_consume_order_orderId;// 锁10分钟等待8分钟try(DistributedLocklockdistributedLockProvider.getLock(lockKey,TimeUnit.MINUTES,10)){if(lock.tryLock(TimeUnit.MINUTES,8)){processMessage(message);}else{log.warn(消息正在被其他消费者处理: {},orderId);}}}}四、设计要点总结要点实现方式收益零配置使用spring.factories 自动装配引入依赖即可用无需Enable注解防死锁锁自带过期时间进程崩溃不会永久阻塞防误释放requestId 校验A 加的锁不会被 B 释放原子操作Lua 脚本避免 check-then-act 竞态条件资源安全Closeable try-with-resources确保锁一定被释放灵活度工厂模式 可配参数不同场景可设不同超时可观测性关键操作打日志方便排查锁竞争问题最小依赖仅依赖 spring-data-redis slf4j轻量无侵入