青岛企业网站建设,有口碑的南昌网站设计,网络营销成功案例ppt免费,it培训班大概需要多少钱Tomcat是这个系统的核心组成部分#xff0c; 每当有用户请求过来#xff0c;Tomcat就会从线程池里找个线程来处理#xff0c;有的执行登录#xff0c;有的查看购物车#xff0c;有的下订单#xff0c;看着属下们尽心尽职地工作#xff0c;完成人类的请求#xff0c;Tom…Tomcat是这个系统的核心组成部分 每当有用户请求过来Tomcat就会从线程池里找个线程来处理有的执行登录有的查看购物车有的下订单看着属下们尽心尽职地工作完成人类的请求Tomcat就很有成就感。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_namestock 这倒是一个简单的办法 但也是一个重量级的办法每次获得锁都得访问一次数据库!假设来自TomcatA的0x9527捷足先登插入了一条数据获得了锁 那来自Tomcat B的0x7954操作肯定失败这时候0x7954该怎么办? 能阻塞等待TomcatB来唤醒他吗? 不行因为连TomcatB 都不知道0x9527什么时候操作完成 除非MySQL来通知各个Tomcat 这是肯定不行的。那0x7954TomcatB只能做一件事情 等待一会儿然后重试! 如此循环下去直到获得锁为止。可是如果0x9527获得了锁在执行的过程中TomcatA 挂掉了那数据库记录一直存在无人删除那锁就永远也无法释放了! 还得弄一个清理者 清理那些过期没释放的锁 这实在是太麻烦了。Redis这时候Redis说道“千万别上MySQL的贼船!他的办法太笨重了不就是找个第三方来保存锁的信息吗? 用我的缓存多好!”“Redis这小子操作的是内存速度会快很多!” Tomcat B说道。“对MySQL不是给你们提供了一张表让你们插入数据吗? 我这里不用那么麻烦你们Tomcat的线程都可以尝试到我的缓存中设置一个值比如stock_locktrue 谁先设置成功谁就获得了锁可以去扣减库存。”“ 如果有多个线程去设置你能保证只有一个成功别的都失败吗? ”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的思想很重要。