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

Redis分布式锁:从理论到实践的全方位解析

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

在单机应用中,我们可以使用Java内置的synchronizedReentrantLock来解决并发问题。但在分布式环境下,多个服务实例运行在不同的JVM中,这些本地锁就失去了作用。

典型应用场景

  • 订单系统中的库存扣减

  • 优惠券发放防止超发

  • 定时任务的分布式调度

  • 重要操作的幂等性控制

2. 分布式锁的核心要求

一个合格的分布式锁应该满足以下基本要求:

要求说明
互斥性同一时刻只有一个客户端能持有锁
防死锁即使客户端崩溃,锁也能在一定时间后自动释放
容错性Redis节点宕机时,锁机制仍然可用
可重入性同一线程可多次获取同一把锁

3. Redis分布式锁的演进之路

3.1 第一代:简单的SETNX实现

/*** 第一代Redis分布式锁 - 基于SETNX命令* 这是最基础的实现,存在明显缺陷,仅用于理解原理*/
public class SimpleRedisLock {private Jedis jedis;          // Redis客户端private String lockKey;       // 锁的键名/*** 尝试获取分布式锁* @param value 锁的值,用于标识锁的持有者* @param expireSeconds 锁的过期时间(秒)* @return 是否成功获取锁*/public boolean lock(String value, long expireSeconds) {// 使用SET命令的NX和EX参数实现原子性的加锁操作// NX: 仅当key不存在时才设置,保证互斥性// EX: 设置过期时间,防止死锁String result = jedis.set(lockKey, value, "NX", "EX", expireSeconds);return "OK".equals(result);  // 返回"OK"表示获取锁成功}/*** 释放分布式锁(避免服务宕机,但是锁又没有释放,所以引起死锁)* @param value 锁的值,用于验证锁的持有者* 问题:直接删除,可能误删其他客户端持有的锁*/public void unlock(String value) {// 直接删除锁,存在严重问题:// 1. 可能误删其他客户端获取的锁// 2. 非原子操作,在旧版Redis中需要先GET再DELjedis.del(lockKey);}
}

问题分析:

  • 非原子操作:在旧版Redis中,SETNX和EXPIRE是两个独立命令,可能设置成功但过期时间设置失败

  • 可能误删其他客户端的锁:解锁时没有验证锁的持有者

  • 没有锁续期机制:如果业务执行时间超过锁的过期时间,锁会自动释放,导致并发问题

3.2 第二代:Lua脚本保证原子性

/*** 第二代Redis分布式锁 - 使用Lua脚本保证原子性* 解决了第一代的主要问题,但仍有改进空间*/
public class AdvancedRedisLock {// 加锁的Lua脚本:原子性地执行SETNX和EXPIREprivate static final String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then\n" +  // 如果key不存在,设置value"    return redis.call('expire', KEYS[1], ARGV[2])\n" +   // 设置过期时间,返回1表示成功"else\n" +"    return 0\n" +                                        // key已存在,返回0表示失败"end";// 解锁的Lua脚本:验证锁持有者后再删除private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then\n" +       // 验证当前值是否等于传入的value"    return redis.call('del', KEYS[1])\n" +               // 相等则删除,返回1表示成功"else\n" +"    return 0\n" +                                        // 不相等,返回0表示失败"end";/*** 获取分布式锁 - 原子操作版本*/public boolean lock(String lockKey, String value, int expireSeconds) {// 执行Lua脚本:保证SETNX和EXPIRE的原子性// KEYS[1] = lockKey, ARGV[1] = value, ARGV[2] = expireSecondsObject result = jedis.eval(LOCK_SCRIPT, Collections.singletonList(lockKey),                    // 键列表Arrays.asList(value, String.valueOf(expireSeconds)));  // 参数列表return "1".equals(result.toString());  // 返回"1"表示成功}/*** 释放分布式锁 - 安全的解锁操作*/public boolean unlock(String lockKey, String value) {// 执行Lua脚本:验证锁持有者后再删除,防止误删Object result = jedis.eval(UNLOCK_SCRIPT,Collections.singletonList(lockKey),                   // 键列表Collections.singletonList(value));                    // 参数列表return "1".equals(result.toString());  // 返回"1"表示解锁成功}
}

3.3 第三代:Redisson生产级实现

Redisson是一个在Redis基础上实现的Java驻内存数据网格框架。它提供了很多分布式Java对象和服务,让开发者能够像操作本地对象一样操作分布式环境下的数据,使得操作操作更简单。

集成spring生态

@Autowired
private RedissonClient redissonClient;即可操作对象
/*** Redis配置类 - 配置Redisson客户端* Redisson是Redis的Java客户端,提供了丰富的分布式对象和服务*/
@Configuration
public class RedisConfig {/*** 创建Redisson客户端单例* @return RedissonClient实例*/@Beanpublic RedissonClient redissonClient() {Config config = new Config();// 使用单节点Redis服务器配置config.useSingleServer().setAddress("redis://127.0.0.1:6379")  // Redis服务器地址.setDatabase(0);                        // Redis数据库编号return Redisson.create(config);               // 创建Redisson客户端}
}/*** 订单服务 - 使用Redisson实现分布式锁的业务示例* Redisson提供了生产级别的分布式锁实现*/
@Service
public class OrderService {@Autowiredprivate RedissonClient redissonClient;  // 注入Redisson客户端/*** 扣减库存 - 使用分布式锁保证并发安全* @param productId 商品ID* @param quantity 扣减数量*/public void deductStock(Long productId, Integer quantity) {// 生成锁的键:按商品ID区分,不同商品使用不同的锁String lockKey = "stock_lock:" + productId;// 获取分布式锁对象RLock lock = redissonClient.getLock(lockKey);try {// 尝试获取锁:// - waitTime=10: 最多等待10秒获取锁// - leaseTime=30: 锁的持有时间为30秒// - TimeUnit.SECONDS: 时间单位boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);if (locked) {// 成功获取锁,执行库存扣减业务逻辑processStockDeduction(productId, quantity);} else {// 获取锁失败,抛出异常或进行重试throw new RuntimeException("获取锁失败,请重试");}} catch (InterruptedException e) {// 线程在等待锁时被中断,恢复中断状态Thread.currentThread().interrupt();throw new RuntimeException("锁等待被中断", e);} finally {// 在finally块中确保锁被释放if (lock.isHeldByCurrentThread()) {// 检查当前线程是否还持有锁,避免重复释放lock.unlock();}}}/*** 具体的库存扣减逻辑*/private void processStockDeduction(Long productId, Integer quantity) {// 这里实现具体的库存扣减逻辑// 例如:查询库存、校验数量、更新库存等// ...}
}

4. Redis分布式锁的核心原理

4.1 锁的获取机制

/*** Redisson锁获取的核心逻辑* 使用Lua脚本保证所有操作的原子性*/
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,// Lua脚本开始"if (redis.call('exists', KEYS[1]) == 0) then " +           // 检查锁是否存在"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +        // 不存在:设置哈希字段,值为1(支持可重入)"redis.call('pexpire', KEYS[1], ARGV[1]); " +           // 设置过期时间(毫秒)"return nil; " +                                        // 返回nil表示成功"end; " +"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // 检查当前线程是否已持有锁"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +        // 已持有:重入计数+1"redis.call('pexpire', KEYS[1], ARGV[1]); " +           // 刷新过期时间"return nil; " +                                        // 返回nil表示成功"end; " +"return redis.call('pttl', KEYS[1]);",                      // 锁被其他线程持有:返回剩余过期时间// Lua脚本结束Collections.singletonList(getRawName()),                    // KEYS[1] = 锁的键名unit.toMillis(leaseTime), getLockName(threadId));           // ARGV[1] = 过期时间, ARGV[2] = 线程标识
}

关键特性:

  • 可重入性:同一线程可以多次获取同一把锁

  • 原子性:所有操作在Lua脚本中原子执行

  • 过期时间:自动设置锁的过期时间,防止死锁

  • 4.2 看门狗机制(锁续期)

/*** 看门狗机制 - 锁自动续期* 防止业务执行时间超过锁的过期时间*/
private void scheduleExpirationRenewal(long threadId) {// 创建一个定时任务,在锁过期时间的1/3时执行续期Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {// 异步执行锁续期操作RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {// 续期失败,记录错误日志log.error("Can't update lock " + getRawName() + " expiration", e);return;}if (res) {// 续期成功,递归调用继续安排下一次续期scheduleExpirationRenewal(threadId);}// 如果res为false,说明锁已释放,不再续期});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);  // 在锁过期时间的1/3时触发// 将续期任务保存到Map中,用于后续取消expirationRenewalMap.put(getEntryName(), task);
}

看门狗机制的作用:

  • 自动续期:在业务执行期间自动延长锁的过期时间

  • 防止过早释放:避免业务未完成时锁自动过期

  • 异常处理:客户端崩溃时,锁最终还是会过期,不会永久死锁

http://www.dtcms.com/a/590723.html

相关文章:

  • MySQL索引优化实战:原则速查与踩坑案例(实战篇)
  • 莱芜网站建设自助建站优化上海企业制作网站有哪些
  • 如何设置网站域名揭阳网站制作专业
  • 上海殷行建设网站数字营销传播
  • 九江网站建设哪家公司好电子商务网站系统的开发设计
  • 现在建设一个网站多少钱自己做的html网页怎么发布
  • 企业网站建设注意事项wordpress 固定链接 seo
  • 哈尔滨模板建站新报价7373网页游戏大全
  • 广州网站程序开发全国好的视频制作
  • python匹配人脸信息
  • 厦门市小学生计算机 C++语言竞赛(初赛)题目精讲与训练(逻辑运算符)
  • 常德网站设计微信电影网站建设教程
  • AD软件各个层的区别
  • 临沂大企业网站wordpress登录之后强制绑定邮箱
  • 上海人才网站建设企业平台网站制作
  • Java 黑马程序员学习笔记(进阶篇25)
  • 上海企业都用什么网站在线音乐网站模板
  • 网站开发可演示的版本网站建设项目分期
  • 国内做设计的网站建设徐州网站制作苏视
  • iapp网站做软件天津网站制作公司电话
  • 电子商务网站优化方案合肥瑶海区范围
  • 琼海建设网站wordpress 邮箱插件
  • 高端商品网站湖州房产网
  • JavaScript Window Location
  • 专门做品牌折扣的网站有哪些免费空间使用指南
  • 网站如何做关智联招聘手机app下载
  • 企业网站一般用什么域名珠海网站制作哪家好
  • ASP4644电源芯片 Buck变换器双PLL频率同步机制分析
  • 做网站需要了解的内容seo整站优化外包公司
  • 2025年下半年网络工程师案例分析真题及答案解析