别吵吵,分布式锁也是锁

栏目: Java · 发布时间: 7年前

内容简介:Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来处理,有的执行登录,有的查看购物车,有的下订单,看着属下们尽心尽职地工作,完成人类的请求,Tomcat就很有成就感。与此同时,它也很得意,所有的业务逻辑尽在掌握。MySQL算啥!不就是一个保存数据的地方吗? Redis算啥!不就是一个加快速度的缓存吗?

别吵吵,分布式锁也是锁

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的思想很重要。

别吵吵,分布式锁也是锁

【本文为51CTO专栏作者“刘欣”的原创稿件,转载请通过作者微信公众号coderising获取授权】

戳这里,看该作者更多好文


以上所述就是小编给大家介绍的《别吵吵,分布式锁也是锁》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design and Analysis of Distributed Algorithms (Wiley Series on P

Design and Analysis of Distributed Algorithms (Wiley Series on P

Nicola Santoro / Wiley-Interscience / 2006-10-27 / USD 140.95

This text is based on a simple and fully reactive computational model that allows for intuitive comprehension and logical designs. The principles and techniques presented can be applied to any distrib......一起来看看 《Design and Analysis of Distributed Algorithms (Wiley Series on P》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具