文章目录
系列文章:
分布式锁-数据库mysql解决方案
分布式锁-Redis解决方案和Redisson解决方案
分布式锁-Redis红锁解决方案
Zookeeper-06-Zookeeper锁原理演进及Redis锁优缺点终极选型指南
分布式锁深度精讲:从底层理论原理到主流方案精准选型
前言
在分布式微服务架构遍地开花的当下,单机部署的业务场景早已成为过去式。集群化部署、多实例负载均衡是后端服务的标配,但随之而来的多进程、多服务并发争抢共享资源问题,成了开发绕不开的核心痛点。
就像我们日常使用的打车软件,乘客下单后,多位司机同时争抢同一笔订单,订单资源唯一,只能允许一位司机抢单成功。如果没有合理的锁机制控制,就会出现多司机同时接单、订单数据错乱、库存对账异常等严重生产问题。
本地线程锁在集群环境下完全失效,Redis分布式锁凭借高性能、高可用、轻量级的特性,成为互联网企业生产开发中解决分布式并发互斥问题的首选方案。
本文将带你循序渐进学习Redis分布式锁:先厘清核心基础概念与锁的核心区别,再从原生Redis命令手写分布式锁,一步步迭代修复各类生产致命bug,最后重点讲解生产环境标配Redisson框架的核心原理、配置与实战用法,包含编程式锁和企业常用的声明式注解锁,文末深度剖析Redis分布式锁优缺点,零基础也能轻松看懂、上手落地。
一、核心基础认知:搞懂锁与分布式锁的本质区别
1.1 什么是分布式锁?
简单来说,分布式锁就是面向分布式集群环境的互斥锁。
在单机单服务场景下,多线程并发操作共享资源,我们用普通锁就能保证线程安全;但在分布式架构中,同一个业务服务会部署在多台不同服务器上,本质是多进程跨主机并发操作共享资源,普通本地锁无法跨机器生效,此时就需要分布式锁实现多客户端互斥访问临界资源,保障并发场景下的数据一致性。
1.2 本地锁、分布式锁、事务核心三者核心区别
很多新手容易混淆普通锁、分布式锁、数据库事务的使用场景,一张明细拆解帮你彻底分清,精准适配不同业务需求:
-
本地线程锁(synchronized/Lock):核心解决单进程内多线程并发安全问题。仅作用于当前单机服务JVM内部,只能锁住本机多线程资源,跨服务器、跨进程完全失效,仅适用于单体架构简单并发场景。
-
Redis分布式锁:核心解决分布式集群多进程跨主机并发互斥问题。针对多台服务器部署的同款业务服务,保证同一时间只有一个服务实例能操作共享临界资源,是微服务集群并发控制的核心方案。
-
数据库事务:核心解决单次业务会话内多数据库操作的数据一致性问题,保证事务内所有SQL要么全部成功提交,要么全部回滚,不处理并发争抢问题,仅管控数据操作原子性。
-
分布式事务:核心解决多服务联动跨库数据一致性问题,比如下单服务、库存服务、支付服务跨服务联动,保证所有关联服务操作整体成功或整体回滚,和分布式锁的并发互斥核心诉求完全不同。
二、实战业务场景:打车司机抢单核心需求(只学习,生产不用)
2.1 业务核心需求
乘客在打车平台发起出行订单,订单生成后状态为待抢单,平台多位在线司机可同时发起抢单请求。核心规则:一笔订单仅允许一位司机抢单成功,抢单后订单状态变更为已接单,其他司机抢单直接失败。
该服务为集群多实例部署,多个司机的抢单请求会被负载均衡分发到不同服务器,本地锁完全无效,必须使用分布式锁实现并发互斥。
2.2 基础接口层代码(Controller)
编写抢单请求接收接口,接收订单ID和司机ID,调用抢单核心业务逻辑,代码简洁适配后续锁逻辑对接:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 打车抢单控制层
* @author 技术教学博客
*/
@RestController
public class GrabOrderController {
// 注入抢单核心业务服务
@Resource
private GrabOrderService grabService;
/**
* 司机抢单接口
* @param orderId 待抢订单ID
* @param driverId 抢单司机ID
* @return 抢单结果提示
*/
@GetMapping("/grab/order/{orderId}")
public String grabOrder(@PathVariable("orderId") int orderId, Integer driverId){
System.out.println("收到抢单请求:订单号"+orderId+",抢单司机ID:"+driverId);
// 调用抢单核心逻辑(含锁控制)
grabService.grabOrder(orderId,driverId);
return "抢单请求处理完成";
}
}
2.3 本地锁synchronized实测:集群环境彻底失效
很多新手第一时间会想到用JDK自带的synchronized本地锁实现互斥,但synchronized仅锁当前JVM进程,集群多服务器部署下完全无效。不同服务器的服务实例各自加锁,互不影响,依旧会出现多司机同时抢单成功的问题。
本地锁失效演示代码:
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 本地锁抢单实现(集群环境失效演示)
*/
@Service
public class GrabOrderLocalLockImpl implements GrabOrderService {
@Resource
private OrderService orderService;
@Override
public String grabOrder(int orderId, int driverId) {
// 以订单ID作为锁对象
String lockKey = orderId + "";
// 本地同步锁,仅单机生效
synchronized (lockKey.intern()) {
try {
System.out.println("司机:"+driverId+" 开始执行抢单核心逻辑");
// 调用订单数据库操作业务逻辑
boolean grabResult = orderService.grab(orderId, driverId);
if(grabResult) {
System.out.println("司机:"+driverId+" 抢单成功!");
}else {
System.out.println("司机:"+driverId+" 抢单失败,订单已被抢占");
}
} finally {
// 本地锁自动释放,无需手动操作
}
}
return null;
}
}
2.4 订单核心业务底层代码(公共基础逻辑)
该层为订单数据库查询、状态修改的基础业务代码,所有锁方案都会复用该逻辑,核心判断订单是否为待抢单状态,修改订单归属司机及订单状态,无需关注并发控制,仅做基础业务处理:
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 订单核心业务实现层
*/
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private TblOrderDao mapper; // 订单数据库DAO层
@Override
public boolean grab(int orderId, int driverid) {
// 根据订单ID查询订单详情
TblOrder order = mapper.selectByPrimaryKey(orderId);
// 模拟业务处理耗时,放大并发冲突场景
try {
Thread.sleep(2000);
} catch (InterruptedException e){
e.printStackTrace();
}
// 判断订单状态:0=待抢单,1=已接单
if (order.getOrderStatus().intValue() == 0){
// 修改订单为已接单状态,绑定抢单司机ID
order.setOrderStatus(1);
order.setDriverId(driverid);
// 更新订单数据到数据库
mapper.updateByPrimaryKeySelective(order);
return true;
}
// 订单已被抢占,返回抢单失败
return false;
}
}
三、手写Redis原生分布式锁:一步步迭代修复所有生产bug(只学习,生产不用)
明白了本地锁失效的核心问题后,我们基于Redis原生命令手写分布式锁,从最简版本开始,逐个修复宕机死锁、原子性失效、误删他人锁、锁超时业务未执行完四大核心生产问题,彻底吃透分布式锁底层原理。
3.1 手写Redis锁四大核心痛点及优化方向
-
问题1:服务宕机导致死锁:加锁成功后服务突然宕机、重启,锁无法手动释放,其他服务永久抢不到锁。优化:给锁Key设置过期时间,自动释放锁。
-
问题2:加锁和设置过期时间非原子操作:先加锁、再设过期时间,两步操作中间服务宕机,依旧会死锁。优化:使用Redis setIfAbsent原子命令,一步完成加锁+设过期时间。
-
问题3:误删除其他线程的锁:锁自动过期释放后,新线程抢到锁,旧线程业务执行完手动删锁,误删掉新线程的锁。优化:锁Value存入当前线程唯一标识,删锁前校验归属权,只删自己的锁。
-
问题4:锁提前过期,业务未执行完毕:业务执行耗时超过锁过期时间,锁自动释放,出现并发争抢。优化:新增守护线程看门狗,定时给锁续约续命,业务不结束锁不失效。
3.2 迭代版手写Redis锁代码(含过期时间+锁归属权校验)
整合前三大优化点,实现原子加锁、自动过期、防误删锁核心能力,是手写锁基础可用版本:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 手写Redis原生分布式锁(基础优化版)
* 解决:死锁、原子加锁、误删他人锁问题
*/
@Service
public class GrabRedisLockImpl implements GrabOrderService {
// Redis操作模板
@Resource
private StringRedisTemplate stringRedisTemplate;
// 订单核心业务服务
@Resource
private OrderService orderService;
@Override
public String grabOrder(int orderId , int driverId){
// 1. 定义分布式锁Key:按订单维度加锁,不同订单互不影响
String lockKey = "order_grab_lock_" + orderId;
// 2. 定义锁Value:当前抢单司机ID,作为锁唯一归属标识
String lockValue = String.valueOf(driverId);
// 3. 原子操作:加锁+设置10秒过期时间,一步完成杜绝宕机死锁
boolean lockSuccess = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey.intern(), lockValue, 10L, TimeUnit.SECONDS);
// 加锁失败,直接返回抢单失败
if(!lockSuccess) {
System.out.println("司机:"+driverId+" 抢单失败,锁已被占用");
return null;
}
try {
// 模拟业务执行耗时
TimeUnit.SECONDS.sleep(3);
// 执行核心抢单业务逻辑
System.out.println("司机:"+driverId+" 执行抢单核心业务逻辑");
boolean grabResult = orderService.grab(orderId, driverId);
if(grabResult) {
System.out.println("司机:"+driverId+" 抢单成功!");
}else {
System.out.println("司机:"+driverId+" 抢单失败,订单已被抢占");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. 释放锁:先校验锁归属权,只删除自己加的锁,防止误删
String currentLockValue = stringRedisTemplate.opsForValue().get(lockKey.intern());
if(lockValue.equals(currentLockValue)) {
stringRedisTemplate.delete(lockKey.intern());
System.out.println("司机:"+driverId+" 成功释放自己的抢单锁");
}
}
return null;
}
}
3.3 终极手写版:新增看门狗锁续约续命机制
基础优化版仍存在锁提前过期的问题,我们手动实现看门狗守护线程,后台异步线程定时给锁续约,业务没执行完就持续延长锁过期时间,彻底解决锁超时并发问题。
第一步:锁续约守护线程服务类
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* Redis锁续约看门狗服务(手动续命线程)
*/
@Service
public class RedisLockRenewService {
@Resource
private RedisTemplate<String, String> redisTemplate;
/**
* 异步定时给锁续约
* @param lockKey 锁Key
* @param lockValue 锁归属Value
* @param expireTime 锁初始过期时间
*/
@Async // 开启异步线程执行续约,不阻塞主业务流程
public void renewLock(String lockKey, String lockValue, int expireTime) {
System.out.println("看门狗启动,开始为锁"+lockKey+" 持续续约续命");
// 循环校验:锁存在且是自己的锁,就持续续约
String currentValue = redisTemplate.opsForValue().get(lockKey);
while (StringUtils.isNotBlank(currentValue) && currentValue.equals(lockValue)){
// 每n/3秒续约一次,默认10秒过期,每3秒续约一次
int sleepTime = expireTime / 3;
try {
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
// 重置锁过期时间,完成续命
redisTemplate.expire(lockKey,expireTime, TimeUnit.SECONDS);
System.out.println("锁"+lockKey+" 续约成功,过期时间已刷新");
// 重新获取锁最新状态,判断是否继续续约
currentValue = redisTemplate.opsForValue().get(lockKey);
}
System.out.println("业务执行结束,锁"+lockKey+" 停止续约");
}
}
第二步:整合续约机制的完整手写锁业务代码
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 手写Redis分布式锁(终极完整版:含续约看门狗)
*/
@Service
public class GrabRedisLockFinalImpl implements GrabOrderService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private OrderService orderService;
// 注入锁续约看门狗服务
@Resource
private RedisLockRenewService redisLockRenewService;
@Override
public String grabOrder(int orderId , int driverId){
String lockKey = "order_grab_lock_" + orderId;
String lockValue = String.valueOf(driverId);
// 锁初始过期时间10秒
int lockExpireTime = 10;
// 原子加锁:加锁同时设置过期时间,杜绝宕机死锁
boolean lockSuccess = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey.intern(), lockValue, lockExpireTime, TimeUnit.SECONDS);
// 加锁成功,启动异步看门狗续约线程,持续给锁续命
if(lockSuccess) {
redisLockRenewService.renewLock(lockKey.intern(), lockValue, lockExpireTime);
System.out.println("司机:"+driverId+" 加锁成功,看门狗续约线程已启动");
}else {
System.out.println("司机:"+driverId+" 抢单失败,锁已被其他司机占用");
return null;
}
try {
// 模拟长耗时业务操作,远超锁初始过期时间,测试续约效果
TimeUnit.SECONDS.sleep(30);
System.out.println("司机:"+driverId+" 执行抢单核心业务逻辑");
// 调用订单底层抢单业务
boolean grabResult = orderService.grab(orderId, driverId);
if(grabResult) {
System.out.println("司机:"+driverId+" 抢单成功!订单已绑定当前司机");
}else {
System.out.println("司机:"+driverId+" 抢单失败,订单已被提前抢占");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁前置校验:只能删除自己持有锁,杜绝误删其他线程锁
String currentLockValue = stringRedisTemplate.opsForValue().get(lockKey.intern());
if(lockValue.equals(currentLockValue)) {
stringRedisTemplate.delete(lockKey.intern());
System.out.println("司机:"+driverId+" 业务执行完毕,成功释放抢单锁");
}
}
return null;
}
}
四、生产级首选:Redisson分布式锁实战落地(重点)
手写Redis锁虽然能通过不断迭代修复各类并发问题,实现基础可用,但在真实生产环境中仍存在明显短板。手动编写的续约逻辑健壮性差、不支持锁可重入、无法适配Redis集群、没有锁超时重试、异常容错机制简陋,一旦面对高并发、多实例、复杂业务场景,极易出现隐藏线上bug。
Redisson是Redis官方推荐、企业生产标配的分布式锁框架,彻底封装了Redis分布式锁所有底层复杂逻辑,把原子加解锁、自动看门狗续约、锁可重入、公平锁/读写锁/红锁适配、集群容错等一系列核心能力全部封装成熟,开发者无需关注底层原理,只需简单配置即可快速落地使用。
-
Redisson 的加锁机制:线程1去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。线程2也去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
-
Redisson 的还有一个重要的续约机制,只要客户端一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间。

4.1 Redisson核心核心优势机制
-
全程Lua脚本原子保障:所有加锁、解锁、锁续约、锁释放操作底层全部基于Lua脚本执行,单脚本命令原子性生效,杜绝并发错乱、中间态宕机问题,无需开发者手动保证原子操作。
-
内置自动看门狗续生机能:业务线程加锁成功后,Redisson自动后台启动看门狗守护线程,默认每隔10秒检测锁状态,只要当前业务线程还持有锁、业务未执行完毕,就自动刷新锁过期时间,无需手动编写续约线程,彻底解决锁提前过期问题。
-
天然支持锁可重入:底层记录线程加锁次数,同一业务线程可多次重复加锁,对应多次解锁,避免业务嵌套加锁逻辑导致的死锁问题,适配复杂业务嵌套场景。
-
智能阻塞抢锁重试:加锁失败不会直接返回失败,线程自动循环自旋重试抢锁,适配多线程正常并发争抢场景,无需开发者手动编写自旋重试逻辑。
-
多类型锁按需适配:内置可重入锁、公平锁、读写锁、联锁、红锁等多种锁模式,既能满足普通抢单互斥场景,也能适配缓存读写分离、多Redis节点强一致等复杂特殊业务。
4.2 第一步:项目引入Redisson Maven依赖
SpringBoot项目直接引入核心依赖,无需额外配置,适配绝大多数SpringBoot版本,兼容性稳定:
<!-- Redisson分布式锁核心依赖,生产稳定常用版本 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
4.3 第二步:Redisson客户端配置类(SpringBoot整合)
编写配置类,初始化RedissonClient全局核心客户端Bean,统一配置Redis单机连接地址、数据库编号,后续所有业务锁全部复用该客户端,无需重复创建:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Redisson分布式锁全局配置类
* 生产环境可扩展配置Redis集群、哨兵、密码认证等信息
*/
@Configuration
public class RedissonConfig {
/**
* 初始化Redisson核心客户端Bean,全局单例使用
*/
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 配置单机Redis连接信息,生产环境替换为真实Redis地址
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setDatabase(0);
// 创建并返回Redisson客户端实例
return Redisson.create(config);
}
}
4.4 第三步:编程式Redisson锁实战代码(基础手写调用)
业务代码手动编写加锁、解锁逻辑,直观易懂,适合简单短流程业务,新手快速上手调试,底层Redisson自动封装原子操作和看门狗续约:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 生产级Redisson分布式锁抢单实战实现(编程式基础用法)
*/
@Service
public class GrabRedissonLockImpl implements GrabOrderService {
// 注入全局Redisson客户端
@Resource
private RedissonClient redissonClient;
// 注入订单核心基础业务服务
@Resource
private OrderService orderService;
@Override
public String grabOrder(int orderId , int driverId){
// 1. 按订单维度定义分布式锁Key,不同订单互不干扰,精准锁临界资源
String lockKey = "order_grab_lock_" + orderId;
// 2. 获取Redisson可重入锁实例
RLock rLock = redissonClient.getLock(lockKey.intern());
try {
// 3. 核心加锁:默认30秒过期,自动看门狗持续续约,失败自动自旋重试
rLock.lock();
System.out.println("司机:"+driverId+" 获取Redisson分布式锁成功,开始执行抢单流程");
// 执行核心抢单业务逻辑,无需关心锁过期、续约问题
boolean grabResult = orderService.grab(orderId, driverId);
if(grabResult) {
System.out.println("司机:"+driverId+" 抢单成功!订单已正式绑定当前司机");
}else {
System.out.println("司机:"+driverId+" 抢单失败,订单已被其他司机抢先接单");
}
} finally {
// 4. 安全释放锁:先判断锁是否存在、是否为当前线程持有,避免非法解锁报错
if(rLock.isLocked() && rLock.isHeldByCurrentThread()) {
rLock.unlock();
System.out.println("司机:"+driverId+" 业务执行完毕,安全释放分布式锁");
}
}
return null;
}
}
4.5 第四步:生产极简必备!Redisson声明式注解锁(AOP实现)
编程式锁需要在每个业务方法中重复编写加锁、解锁模板代码,代码冗余、维护性差。企业生产开发主流推荐自定义注解+AOP切面声明式锁,核心优势:加锁解锁零侵入业务代码、统一锁规则管控、全局锁逻辑统一维护、支持SpEL动态拼接锁Key,适配所有复杂业务场景。
核心实现三步走:自定义分布式锁注解、AOP切面统一拦截加解锁、业务方法直接注解标注使用,无需任何重复锁相关代码。
4.5.1 第一步:自定义RedisLock核心注解
定义注解参数,支持动态锁Key、抢锁等待时间、锁过期时间,leaseTime=-1默认开启Redisson看门狗自动续约机制:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Redisson声明式分布式锁自定义注解
* 支持SpEL表达式动态拼接锁Key、自定义等待时间、续约时间
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* 分布式锁Key,支持SpEL表达式动态取值
*/
String key();
/**
* 抢锁最大等待时间,默认3秒
*/
long waitTime() default 3;
/**
* 锁过期释放时间,默认-1
* -1 = 启用Redisson看门狗自动续约续命
*/
long leaseTime() default -1;
}
4.5.2 第二步:AOP切面统一锁处理核心类
通过Spring AOP环绕通知拦截标注@RedisLock注解的业务方法,自动解析SpEL动态锁Key、尝试抢锁、加锁失败抛异常、业务执行后自动释放锁,全程无需业务层干预:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.concurrent.TimeUnit;
/**
* Redis分布式锁AOP切面拦截类
* 统一实现声明式加锁、解锁、锁续约逻辑
*/
@Aspect
@Component
@Order(1)
@Slf4j
@RequiredArgsConstructor
public class RedisLockAspect {
private final RedissonClient redissonClient;
// SpEL表达式解析器,用于动态解析锁Key
private static final ExpressionParser SPEL_PARSER = new SpelExpressionParser();
/**
* 环绕通知:拦截所有加了@RedisLock注解的方法
*/
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
// 1. 解析SpEL表达式,动态生成业务锁Key
EvaluationContext context = new StandardEvaluationContext();
// 注入方法参数,供SpEL表达式取值使用
Object[] args = pjp.getArgs();
String[] parameterNames = org.springframework.core.LocalVariableTableParameterNameDiscoverer.INSTANCE.getParameterNames(pjp.getSignature().getMethod());
if (parameterNames != null && args != null) {
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
}
// 解析最终锁Key
String lockKey = SPEL_PARSER.parseExpression(redisLock.key()).getValue(context, String.class);
log.info("声明式分布式锁开始抢锁,锁Key:{}", lockKey);
// 2. 获取Redisson可重入锁实例
RLock lock = redissonClient.getLock(lockKey);
// 3. 尝试抢锁:自定义等待时间、过期时间,-1自动开启看门狗续约
boolean locked = lock.tryLock(redisLock.waitTime(), redisLock.leaseTime(), TimeUnit.SECONDS);
// 加锁失败,直接抛出业务异常,拒绝执行并发业务
if (!locked) {
throw new RuntimeException("获取分布式锁失败,订单正在被其他司机抢占,锁Key:" + lockKey);
}
// 4. 加锁成功,执行目标业务方法
try {
return pjp.proceed();
} finally {
// 5. 业务执行完毕,仅当前持有锁线程可解锁,防止误删锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("声明式分布式锁释放成功,锁Key:{}", lockKey);
}
}
}
}
4.5.3 第三步:业务层极简使用(零锁代码侵入)
业务方法仅需添加一行@RedisLock注解,通过SpEL动态绑定订单ID生成锁Key,无需手动加锁、解锁、处理续约,极简开发、统一管控:
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* Redisson声明式注解锁抢单业务实现(生产极简版)
* 零锁代码侵入,专注核心业务逻辑开发
*/
@Service
public class GrabRedissonAnnotationLockImpl implements GrabOrderService {
@Resource
private OrderService orderService;
/**
* 司机抢单核心方法,仅需添加注解自动实现分布式锁控制
* key:SpEL表达式动态按订单ID生成唯一锁
* waitTime:抢锁最大等待2秒
* leaseTime:-1 开启Redisson自动看门狗续约
*/
@Override
@RedisLock(key = "'order:grab:lock:' + #orderId", waitTime = 2, leaseTime = -1)
public String grabOrder(int orderId , int driverId){
System.out.println("司机:"+driverId+" 声明式锁加锁成功,执行抢单核心业务");
// 仅需编写纯抢单业务,无需任何锁相关操作
boolean grabResult = orderService.grab(orderId, driverId);
if(grabResult) {
System.out.println("司机:"+driverId+" 抢单成功!订单已绑定");
}else {
System.out.println("司机:"+driverId+" 抢单失败,订单已被抢占");
}
return null;
}
}
五、Redis分布式锁整体优缺点深度分析
5.1 核心优点
-
高性能高并发适配性强:Redis基于内存读写操作,加锁、解锁、续约逻辑均为毫秒级响应,性能远超数据库悲观锁、乐观锁,完美适配打车抢单、电商秒杀、库存扣减等高并发核心业务场景,吞吐量支撑能力极强。
-
轻量级低成本易落地:无需额外搭建专属中间件,依托项目现有Redis环境即可快速接入,代码开发量少、配置简单,运维成本极低,新手开发也能快速上手落地。
-
集群适配性好,稳定性高:配合Redisson框架使用,自动实现看门狗续约、锁容错、异常重试、可重入等生产级能力,完美适配微服务集群多实例部署架构,满足绝大多数互联网公司业务生产要求。
-
灵活性高,适配多场景:支持自定义锁过期时间、公平锁/非公平锁切换、锁等待策略配置,配合声明式注解可统一管控全局锁策略,适配不同业务并发优先级。
5.2 不可规避核心缺点
-
主从异步复制存在锁失效风险:Redis主从集群数据同步为异步模式,客户端在主节点加锁成功后,锁数据还未同步到从节点,主节点突发宕机,从节点自动晋升为新主节点,其他客户端可再次加锁,出现多个客户端同时持有同一把锁的问题,导致并发数据错乱。
-
非强一致性锁方案:Redis分布式锁属于最终一致性锁,无法实现分布式强数据一致性,对于银行转账、金融交易、资金对账等绝对零误差、零并发冲突的核心强一致场景,无法满足业务要求。
-
看门狗续约少量消耗资源:Redisson看门狗后台守护线程持续运行,针对超长耗时业务,续约线程会持续占用少量服务器及Redis资源,极端超高并发场景需合理优化锁过期时间及续约频率。
六、全文核心总结
-
本地synchronized线程锁仅适用于单机单体架构,微服务集群多实例部署环境下本地锁完全失效,必须使用分布式锁解决跨进程、跨机器并发争抢共享资源问题。
-
手写Redis原生分布式锁适合学习底层原理,必须依次修复死锁、原子性失效、误删锁、锁超时四大核心bug,才能基础可用,但生产环境手写逻辑容错差、隐患多,不建议线上使用。
-
Redisson锁分两种用法:编程式适合简单临时业务,生产长期项目一律首选声明式注解锁(AOP+自定义注解),业务零侵入、统一维护、极简开发。
-
Redis分布式锁高性能、轻量级,适配绝大多数高并发普通业务;若业务需要金融级强数据一致性,建议放弃Redis锁,选用Zookeeper分布式锁或Redisson红锁方案。
本文探讨了分布式锁在打车应用中的具体实现,通过Redis和Redisson两种方式解决多司机抢单问题。作者详细介绍了使用synchronized和自定义Redis锁的原理、问题及解决方案,以及Redisson工具的引入和其优势。

2547

被折叠的 条评论
为什么被折叠?



