redis 实现 分布式锁

上节 我们讲了 线程锁 在单体项目中的作用,和 放在 分布式 项目里产生的问题,那接下来我们就来解决 分布式 架构上怎么 保证 数据的一直性

使用 redisTemplate 实现

// 设置锁
setIfAbsent("lock", "1213")---> SETNX lock "1213"
// 释放锁
redisTemplate.delete("lock");
@GetMapping("/cut")public Object kc() {redisTemplate.setKeySerializer(new StringRedisSerializer());/*** 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true*          如果存在的话:就不操作 直接返回 false*/Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213");if(!lock){// 锁存在return "服务繁忙,请稍后再试";}int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());if (num > 0) {int lastNum = num - 1;redisTemplate.opsForValue().set("num", lastNum + "");System.out.println("扣减库存成功,剩余库存为:" + lastNum);} else {System.out.println("扣减库存失败,库存不足");}// 释放锁redisTemplate.delete("lock");return "ok";}

简述一下逻辑:
第一个请求 进来,设置一把锁 进行锁住,然后 往下执行减库存的操作,此时 第二个线程进来获取锁,但是第一请求没有释放锁,所以第二个请求获取锁就会失败 得到返回值为 false,进入if 方法体 直接返回了。其他线程也是如此,直到 第一个线程释放锁后 其他线程才有获取锁的机会,每次只有一个线程能够成功获取锁,其他线程获取不到直接返回。

大家 觉得 这把锁怎么样,是不是解决问题了呢?还有什么问题吗?有没有那个小可爱想到了呢?

肯定有小伙伴想到了。

情况1:一个请求进来,获取锁,执行扣减库存的操作,在它 释放锁之前 服务突然抛异常了呢?

  • 那就 使用 try{}finally{}嘛,这下不怕抛异常 无法释放锁了吧
try{int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());if (num > 0) {int lastNum = num - 1;redisTemplate.opsForValue().set("num", lastNum + "");System.out.println("扣减库存成功,剩余库存为:" + lastNum);} else {System.out.println("扣减库存失败,库存不足");}}finally {// 释放锁redisTemplate.delete("lock");}

情况2:要是不抛异常,在释放锁之前服务重启了呢?那就来不及执行finally了吧,这下 锁也没有释放吧。

那该怎么解决呢?

有小伙伴 就会想:给锁加一个 定时器嘛,它要挂就挂,到了时间 锁自动释放,其他人又可以获取到 锁 服务又可用了

感觉是可行哦,那我们来实际操作一下。源码如下:

OK 定时有了,要是 线程挂在里面,时间一到 锁就会自动释放

事实真的会 是这个样子的吗?

情况3:锁设置成功,接下来对 锁进行定时,此时准备定时呢,还没定时成功突然程序挂了,又会导致死锁,像前那情况一样。

那这时 我们就要保存设置锁 和 定时 两个指令的原子性,要么全部成功 要么全部失败,那该怎么实现呢?

这个问题 大牛已经都想过了,往下看

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213",10, TimeUnit.SECONDS);

一条更比两条强。这个底层使用 Lua 实现,保证其原子性。

再看一下 我们优化后的代码

@GetMapping("/cut")public Object kc2() {redisTemplate.setKeySerializer(new StringRedisSerializer());/*** 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true*          如果存在的话:就不操作 直接返回 false*/String lockKey = "lock";String redisClientId = UUID.randomUUID().toString();Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, redisClientId,10, TimeUnit.SECONDS);if(!lock){// 锁存在return "服务繁忙,请稍后再试";}try{int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());if (num > 0) {int lastNum = num - 1;redisTemplate.opsForValue().set("num", lastNum + "");System.out.println("扣减库存成功,剩余库存为:" + lastNum);} else {System.out.println("扣减库存失败,库存不足");}}finally {// 释放锁if (redisClientId.equals(redisTemplate.opsForValue().get(lockKey))){redisTemplate.delete(lockKey);}}return "ok";}

小伙伴 会说,这下没问题啦 哈哈。。。。

你确定 没问题?

那就我来给你分析分析

正常逻辑:一个请求进来 说去锁,设置有效时间为 10 秒,然后 执行下面 业务逻辑,最后释放锁,然后第二个线程进来。。。。

lock 有效时间为 10 秒,保不定 那个线程执行任务的时候 执行完要 15 秒,此时,lock 10秒就失效,那下一个线程就会进来,假如第二个线程要执行8 秒,第一个线程5秒后就执行完了 然后释放lock 锁,线程1的锁早就失效了,它释放的锁确实线程2的锁,而第二个线程还有3秒才执行完,此时线程3获取到锁,又进来了,3秒后线程2又释放线程3 的锁,这样下去线程3释放线程4.。。。 就会导致 锁永久失效。

所以 这个超时时间该设置多少呢? 就需要根据项目来考量了。

那有什么 好的解决方案吗,解决这个锁失效的问题呢?

有的,那肯定是有的,听我徐徐道来。。。

假设 设置锁有效时间为 30 秒,那当线程获取锁后,开一个子线程 做一个定时器,每隔一段时间去检查该对象的锁是否存在,存在的话 就重新给 锁续命。
那如何保证 自家的锁不会被别人释放呢?
这下那个 锁的 value 就派上用场了,给每个线程的锁配置上唯一标识(这个唯一标识就使用UUID)
每次释放锁的时候就判断是否是自己的锁,保证只释放自己的锁。

理论有了,那该怎么实现呢?

使用Redisson 实现分布式锁

1、引入 Redisson 依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.0</version>
</dependency>
2、配置Redisson
@Bean
public Redisson redisson(){// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0).setPassword("root");return (Redisson) Redisson.create(config);
}
3、代码实现
@Autowired
private Redisson redisson;@GetMapping("/cut")
public Object kc() {String lockKey = "lock";RLock rLock = redisson.getLock(lockKey);try {// 加锁rLock.lock();int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());if (num > 0) {int lastNum = num - 1;redisTemplate.opsForValue().set("num", lastNum + "");System.out.println("扣减库存成功,剩余库存为:" + lastNum);} else {System.out.println("扣减库存失败,库存不足");}} finally {// 释放锁rLock.unlock();}return "ok";
}
但是 主从架构 也是可能出现问题,如 redis 挂了
后期优化 可以 用 redis 集群,增加高可用。
OK redis 锁分布式就讲完了,到这里 锁就差不多了
要是还想更加完善 可以选择使用ZK来实现,但是性能是没有 redis 好。

深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 2)相关推荐

  1. 深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 1)

    在 使用 redis 实现分布式锁 之前 我们需要先了解以下几点 什么是分布式锁 要介绍 什么是分布式锁,那首先要提到 与之对应的 的两个锁:线程锁 和 进程锁 1.线程锁 主要 用来 给方法.代码块 ...

  2. Redis 作者 Antirez 讲如何实现分布式锁?Redis 实现分布式锁天然的缺陷分析Redis分布式锁的正确使用姿势!...

    Redis分布式锁基本原理 采用 redis 实现分布式锁,主要是利用其单线程命令执行的特性,一般是 setnx, 只会有一个线程会执行成功,也就是只有一个线程能成功获取锁:看着很完美. 然而-- 看 ...

  3. 关于Zookeeper和Redis实现分布式锁的异同

    本文来说下Zookeeper和Redis实现分布式锁的异同 文章目录 概述 Redis单机实现分布式锁 Redis加锁 Redis解锁 Redis加锁过期时间设置问题 Zookeeper单机实现分布式 ...

  4. 基于 Redis 实现分布式锁思考

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/xuan_lu/article/details/111600302 分布式锁 基于redis实 ...

  5. Zookeeper和Redis实现分布式锁,附我的可靠性分析

    作者:今天你敲代码了吗 链接:https://www.jianshu.com/p/b6953745e341 在分布式系统中,为保证同一时间只有一个客户端可以对共享资源进行操作,需要对共享资源加锁来实现 ...

  6. 基于redis实现分布式锁思考

    分布式锁 基于redis实现分布式锁思考几个问题??? synchronized锁为什么不能应用于分布式锁? synchronized虽然能够解决同步问题,但是每次只有一个线程访问,并且synchro ...

  7. 使用Redis作为分布式锁的错误用法

    前言 这段时间看到挺多人使用redis作为分布式锁来进行资源的控制,但是这种写法有挺多问题的,所以才特意写一篇文章让大家讨论一下. 锁的特性 安全性:当一个资源被占用后,其他线程不能占用 容错性:当一 ...

  8. 如何用Redis实现分布式锁?

    简介   我相信很多人学分布式锁最大的动力并不是他自己的系统需要,而是面试官需要...当然,这也侧面说明分布锁很重要,经常作为考题,在学习之前,我们要先明确几个问题. 一.锁重要吗?   当然重要,只 ...

  9. 《Redis官方文档》用Redis构建分布式锁

    <Redis官方文档>用Redis构建分布式锁 用Redis构建分布式锁 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现 ...

最新文章

  1. druid sql黑名单 报异常 sql injection violation, part alway true condition not allow
  2. IOS的OC项目下回调函数的定义以及传参
  3. scala akka_使用Scala,Play和Akka连接到RabbitMQ(AMQP)
  4. binlog日志_mysql的binlog日志的自动定时清理
  5. php默认语法,PHP基本语法总结
  6. 边缘计算崛起!施耐德联手华胜天成打造胶囊数据中心,真正端到端交付
  7. spyder中以html输出图形,交互(?)用matplotlib在Spyder中绘图
  8. Linux FastDFS 分布式文件系统安装
  9. visual studio 删除附加项
  10. VM 虚拟机 分辨率问题
  11. Microbiome | 黄海所陈松林院士/华科宁康等-肠道菌群在龙利鱼(半滑舌鳎)抗弧菌病性状形成中的机制...
  12. 计算机理论什么是信道容量,(信道容量知识总结.doc
  13. [ERROR] [MY-010457][Server]--initialize specified but....
  14. zynq获取程序运行时间
  15. 关于虚拟机闪退及无法启动的问题
  16. 软件设计师(结构化开发)
  17. 陈景润4篇代表性数学论文
  18. 第十四届全国大学生电工数学建模竞赛A题-高比例风电电力系统储能运行及配置分析
  19. JAVA文件和安全性
  20. 企业版移动端钉钉对接

热门文章

  1. Ubuntu16.04下编译pr2机器人
  2. 【SMTP协议介绍】
  3. Jquery的鼠标移入移出事件
  4. android修改桌面app图标的问题,android修改桌面app图标的有关问题
  5. 「镁客·请讲」武谷论链粗院长:区块链社区的核心价值是一种共识文化
  6. 为什么曾经“火极一时”的曲面屏,如今却越来越被冷场?
  7. mysql 删除字段语法_mysql增添、修改、删除字段语法实例
  8. 两点间距离公式计算机,已知两点经纬度,用excel计算两点距离的公式?(转
  9. 《支付宝和蚂蚁花呗的技术架构及实践》阅读总结
  10. 计算机学历在职研究生用途,在职研究生的学历到底有什么用呢?