Java锁?分布式锁?乐观锁?行锁?
转载自:公众号来源:码农翻身 作者:刘欣
Tomcat的锁
Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来处理,有的执行登录,有的查看购物车,有的下订单,看着属下们尽心尽职地工作,完成人类的请求,Tomcat就很有成就感。
与此同时,它也很得意,所有的业务逻辑尽在掌握。MySQL算啥!不就是一个保存数据的地方吗? Redis算啥!不就是一个加快速度的缓存吗?
没有他们,我也能找到替代品,而我不可替代的, Tomcat经常这么想。
昨天MySQL偶然说起隔壁机器入驻了一个叫做Node.js的家伙,居然只用一个线程来执行JavaScript代码,实现各种业务逻辑,JavaScript也能到后端来?还用回调? 这不是胡闹吗?不过得小心,别被他把业务都给抢走了。
想到此处,Tomcat立刻去查看各个线程活干得怎么样,有没有人故意偷懒。
线程0x9527和0x7954又在吵架了,原因非常简单,他们俩都去做扣减库存的操作:读取库存,修改库存,写回数据库。
线程的并发执行导致三个操作交织在了一起,最后数据出现了不一致。
Tomcat说:“你们怎么搞的,为什么要把库存读出来,直接update 库存不行吗? 让MySQL老头儿去保证正确性。要学会甩锅啊!”
0x7954回答道:“没办法,张大胖的代码就是这么写的,好像是业务要求的,扣减库存之前要检查库存够不够。”
Tomcat一阵牙疼, 不由得想起了Redis的处理办法, 对于每个读写缓存的请求,Redis都把他们给排成了队,用一个线程挨个去处理,肯定没有这个并发的问题了。
可是自己这里不行啊,访问数据库是极慢的操作,如果只用一个线程,一个个地处理请求,所有的请求都得等待,人类会急死的。
没办法,Tomcat扔给他们俩一个Java对象:“这是一把锁,以后谁先抢到谁才能执行扣减库存的三个操作。”
“如果抢不到怎么办?”
“阻塞等待,别人释放了锁,JVM自然会唤醒你,然后再去抢! 什么时候抢到,什么时候执行。”
分布式的锁
张大胖觉得有点不对劲, 这几天程序执行怎么有点儿慢了呢?
他还以为是机器性能不够,就申请了几台新机器,又安装了几个Tomcat,组成了一个集群。
这下可好,三个Tomcat, 每个Tomcat都有一把锁来控制对库存的访问。
在Tomcat这个JVM进程内部,同一个时刻只有一个幸运儿线程可以扣减库存,可是现在有三个Tomcat,出现了三个幸运儿。
这三个幸运儿在扣减库存的时候,仍然会出现0x7954和0x9527那样的错误,只不过现在他们互不知晓,连吵架的机会都没有了。
三个Tomcat都觉得头大,在这个分布式的环境中,多个进程在运行,原来那种进程内的锁已经失效,当务之急是找一个客观、公正、独立的第三方来实现锁的功能。
MySQL提议: “到我这里来找锁啊!”
“你那里能提供一个锁服务? 暴露出来让我们使用? ” Tomcat A问道。
“不不,不是一个锁服务,我给你们一个数据库表,这个表中的字段lock_name有个唯一性约束。”
“你的意思是,我们的线程每次想获得锁的时候,都去数据库插入一条数据? ” Tomcat A 反映很快。
insert into locks(lock_name,...) values('stock',...);
“对啊,我的唯一性约束只能保证一个成功,其他的都失败,就相当于获得锁了。 当然那个线程的操作完成以后,需要释放锁。”
delete from locks where lock_name='stock'
这倒是一个简单的办法, 但也是一个重量级的办法:每次获得锁都得访问一次数据库!
假设来自TomcatA的0x9527捷足先登,插入了一条数据,获得了锁, 那来自Tomcat B的0x7954操作肯定失败,这时候0x7954该怎么办? 能阻塞等待TomcatB来唤醒他吗? 不行,因为连TomcatB 都不知道0x9527什么时候操作完成, 除非MySQL来通知各个Tomcat, 这是肯定不行的。
那0x7954@TomcatB只能做一件事情: 等待一会儿,然后重试! 如此循环下去,直到获得锁为止。
可是如果0x9527获得了锁,在执行的过程中TomcatA 挂掉了,那数据库记录一直存在,无人删除,那锁就永远也无法释放了! 还得弄一个清理者, 清理那些过期没释放的锁, 这实在是太麻烦了。
Redis
这时候Redis说道:“千万别上MySQL的贼船!他的办法太笨重了,不就是找个第三方来保存锁的信息吗? 用我的缓存多好!”
“Redis这小子操作的是内存,速度会快很多!” Tomcat B说道。
“对,MySQL不是给你们提供了一张表让你们插入数据吗? 我这里不用那么麻烦,你们Tomcat的线程,都可以尝试到我的缓存中设置一个值,比如stock_lock=true, 谁先设置成功,谁就获得了锁,可以去扣减库存。”
“ 如果有多个线程去设置,你能保证只有一个成功,别的都失败吗? ”
Redis拍拍胸脯: “绝对保证!”
(码农翻身老刘注:其实就是setnx命令了)
MySQL撇撇嘴:“和我的方案本质上是一样的。人家Tomcat 的线程对库存做了修改以后,也还得去解锁,去删除这个stock_lock。”
Redis说:“我这里还能设置过期时间,如果Tomcat A上线程获得了锁,然后Tomcat A挂掉了, 到了过期时间,我就可以自动把这个stock_lock删除,别的线程又可以获得锁了!”
“嗯,是比MySQL先进,并且速度更快,我们还是用这个锁吧。” 三个Tomcat都表示同意。
定期自动释放的问题
“且慢,这个自动删除过期的锁有问题啊 !” MySQL突然反击。
“什么问题?” Redis没想到数据库老头儿还想负隅顽抗。
“假设Tomcat A上的0x9527获得了锁, 去执行扣减库存的操作,然后由于某种原因被阻塞了,阻塞的时间超过了过期时间,锁被你释放掉了,最终还是会出现不一致!”
“你这是吹毛求疵,绝对是小概率事件!” Redis叫道!
“再说了,用你的数据库方案,也得定期清理那些锁,道理是一样的。”
行锁
第二天, MySQL高兴得去找Tomcat:“兄弟们,我昨天晚上和Quartz(一个著名的定时执行框架)聊了半宿,他告诉了我一个新的用数据库实现分布式锁的办法, 行锁。”
“看到没有, 通过添加一个for udpate ,这个SQL语句会把这一行给锁定,就是获得了锁! 只要事务一提交,这个行锁就自动释放了。”
“那没有获得锁的别的线程呢? ”
“自然是阻塞住了,等到别的线程释放了行锁,它可以自动去获取,代码中都不用循环重试,你看,之前的方案都做不到这一点吧。” MySQL说道。
“那要是有个线程迟迟不释放行锁,会发生什么问题?” Tomcat最关心这个。
“那其他线程都会等待,并且占用着数据库连接不释放,嗯,如果连接被占用得过多,连接池就要出问题了......” MySQL底气不足了,这可是个致命的问题。
“哈哈,看你出的什么馊注意!还是用我的锁吧!” Redis笑道。
“那人家Quartz为什么可以用?”MySQL不死心。
“估计Quartz业务单一,并且锁释放得很快,不会出问题吧。”
CAS
正在这时,Node.js悄悄地走过来, 把数据库老头儿拉走了:“前辈,别给他们一般见识,不就是扣减库存吗,用啥分布式锁!, 咱们这么做:”
#old_num = 先获取现有的库存数量
#new_num = #old_num - 10
update stock set stock_num = #new_num where product_id=#product_id and stock_num = #old_num
MySQL眼前一亮, 是啊,每次把这个#old_num 作为条件传进去调用update语句,如果能成功,说明在这段时间内没有别的线程更新库存;
如果不成功,那就重新执行这三条语句,直到成功为止, 就这个么简单, 完全不用锁,真是太爽了。
过了几天,Tomcat他们也听说了这个方案, 惊讶地说:“这不就是我们Java常用的Compare And Set(CAS)吗?”
总结
与此同时,张大胖开始做总结:分布式锁和进程内的锁本质上是一样的。
1. 要互斥,同一时刻只能被一台机器上的一个线程获得。
2. 最好支持阻塞,然后唤醒,这样那些等待的线程不用循环重试。
3. 最好可以重入(本文没有涉及,参见《编程世界的那把锁》)
4. 获得锁和释放锁速度要快
5. 对于分布式锁,需要找到一个集中的“地方”(数据库,Redis, Zookeeper等)来保存锁,这个地方最好是高可用的。
6. 考虑到“不可靠的”分布式环境, 分布式锁需要设定过期时间
7. CAS的思想很重要。
Java锁?分布式锁?乐观锁?行锁?的更多相关文章
- Mysql锁机制--索引失效导致行锁变表锁
Mysql 系列文章主页 =============== Tips:在阅读本文前,最好先阅读 这篇(Mysql锁机制--行锁)文章~ 在上篇文章中,我们看到InnoDB默认的行锁可以使得操作不同行时不 ...
- MySQL 笔记整理(7) --行锁功能:怎么减少行锁对性能的影响?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 7) --行锁功能:怎么减少行锁对性能的影响? MySQL的行锁是在引擎层由各个引擎自己实现的.因此,并不是所有的引擎都支持行锁,如 ...
- 极客时间 Mysql实战45讲 07讲行锁功过:怎么减少行锁对性能的影响笔记 极客时间
极客时间 Mysql实战45讲 07讲行锁功过:怎么减少行锁对性能的影响笔记 极客时间极客时间 Mysql实战45讲 07讲行锁功过:怎么减少行锁对性能的影响笔记 极客时间 笔记体会: 方案一,事务相 ...
- MySQL 啥时候用表锁,啥时候用行锁?
大家好,我是树哥. MySQL Innodb 的锁可以说是执行引擎的并发基础了,有了锁才能保证数据的一致性.众所周知,我们都知道 Innodb 有全局锁.表级锁.行级锁三种,但你知道什么时候会用表锁, ...
- 【锁】MySQL和Oracle行锁比较
InnoDB INNODB表是索引组织的表,主键是聚集索引,非主键索引都包含主键信息. INNODB默认是行锁. INNODB行锁是通过给索引项加锁来实现的,即只有通过索引条件检索数据,InnoDB才 ...
- MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
转载. https://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...
- MySQL锁(行锁、表锁、页锁、乐观锁、悲观锁等)
锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一 ...
- MySQL中锁详解(行锁、表锁、页锁、悲观锁、乐观锁等)
原文地址:http://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...
- HBase行锁原理及实现
请带着例如以下问题阅读本文. 1.什么是行锁? 2.HBase行锁的原理是什么? 3.HBase行锁是怎样实现的? 4.HBase行锁是怎样应用的? 一.什么是行锁? 我们知道.数据库中存在事务的概念 ...
- Mysql 行锁 for update
Mysql 只有Innodb支持行锁 使用行锁需要 事务支持 首先打开两个 mysql-client 分别执行 - client1 select * from my_entity1 for updat ...
随机推荐
- 【接口自动化】Python+Requests接口自动化测试框架搭建【一】
公司项目启用新框架,前后端分离,所以接口测试成为测试工作中不可缺失的一个环节,现在将从0开始搭建接口自动化测试框架的路程,一步步记录下来. 开发语言我们采用Python+第三方库Requests,测试 ...
- cinder 卷迁移进度的代码分析
一.cinder-api服务的入口函数 D:\code-program\cinder-ocata_cinder\cinder\api\contrib\admin_actions.py from cin ...
- 个人项目(C语言)
GitHub地址:https://github.com/dachai9/personal-project.git 1. WC 项目要求 wc.exe 是一个常见的工具,它能统计文本文件的字符数.单词数 ...
- SpringBoot(19)---SpringBoot整合Apollo
SpringBoot(19)---SpringBoot整合Apollo 有关Apollo之前已经写了两篇文章: 1.[Apollo](1)--- Apollo入门介绍篇 2.[Apollo](2)-- ...
- Pyinstaller打包: 将资源文件或文件夹打包到最后生成的exe中
前提:用pyinstaller打包时部分资源文件可以利用qrc转成py文件来读取,但是有部分文件类型不适用. 原理:Pyinstaller 将资源文件一起打包到exe中.当exe运行时,会生成一个临时 ...
- facebookPixel代码安装详解
最近接到一个需求,优化独立站的facebookPixel代码,完成后对这个项目进行复盘.首先要介绍facebookPixel的理论知识. Facebook像素是一段JavaScript代码,其中加载了 ...
- 微信小程序|小游戏
[官]小游戏开发 https://developers.weixin.qq.com/minigame/dev/index.html 官网 https://mp.weixin.qq.com 做了4个微信 ...
- android studio配置so和assets目录
so配置: 1. 建立src/main/libs/armeabi目录,so文件放入armeabi目录 2.配置build.gradle android { defaultConfig{ XXXXXX ...
- Python 中的数字到底是什么?
花下猫语:在 Python 中,不同类型的数字可以直接做算术运算,并不需要作显式的类型转换.但是,它的"隐式类型转换"可能跟其它语言不同,因为 Python 中的数字是一种特殊的对 ...
- FZU - 2037 -Maximum Value Problem(规律题)
Let’s start with a very classical problem. Given an array a[1…n] of positive numbers, if the value o ...