分布式锁
JVM和数据库都提供锁,但是JVM的锁只在同一虚拟机中起作用,数据库锁也只能在本数据库起作用或者通过XA与关联数据库一同协作。
在集群和分布式环境中,JVM的锁就无法起作用了,关于分布式数据库锁,目前还是以应用程序控制最多,另外单数据环境下,在比如秒杀这样的情景下,如果使用数据库锁,代价还是比较大。
使用Redis的方案
将数据库库存拷贝到Redis,秒杀时2步走,第一步用户点击按钮时查询Redis库存是否还有,如果有就进入第二步同时Redis库存减去,第二步用户填写验证信息或者确认订单,点击下单或者提交验证信息时查询真实库存,如果还有,可以下单,如果没有,提示抢光。
原理:视觉欺骗,用户是不知道真实的库存数量的,系统所做的只是将符合库存量的用户队列允许进入下单流程中,其他的挡在系统外。
基于这个原理,有很多的实现,比如nginx也可以做到,并且很高效。
蘑菇街秒杀方案
因为商品数量不多,库存数量不用一个字段记录,而是有多少库存量就创建多少行商品记录,这个记录中记录本记录是否被消费,然后用户抢购时是竞争数据库行锁。
1 | Tips: |
基于zk的分布式锁
锁的分类
S锁:共享锁,如果资源被加S锁,那么它只能再加S锁,不能加其他锁。所有加锁的事务都可以读取资源,不可对资源修改。
X锁:排它锁,如果资源被加S锁,那么它不能被加其他任何锁,只有锁持有者能够对资源进行读取,修改。
基于zk的锁设计
资源类型的排它锁
秒杀,利用zk只能创建一个节点特性,所有线程创建同一个节点,创建成功者视为抢到锁,可以对资源进行操作,没有抢到锁的线程等待或者结束,抢到锁的线程操作完后,删除创建的节点,后来的线程开始抢锁,持续这一过程。
共享锁设计
利用zk创建顺序节点的功能,加锁的线程到同一个父节点下创建顺序节点,节点顺序号最小的节点视为当前锁持有者,可以进行后续操作,操作完成后删除自己创建的节点。
因为有2类锁,根据锁的特性,所以有以下情况:
- 我创建了节点,并且我要加S/X锁,我创建的节点序号是最小的,那么我可以进行我后面的业务逻辑处理了。
- 我创建了节点,我要加S锁,我创建的节点序号不是当前最小的,并且在我前面的节点全是S锁节点,此时我也可以进行后续业务逻辑处理。
- 我创建了节点,我要加S锁,我创建的节点序号不是当前最小的,并且在我前面的节点有X锁节点,此时我不能进行后续业务逻辑处理,只能等待我前面距我最近的X锁节点删除,才能进行后续业务逻辑处理。
- 我创建了节点,我要加X锁,我创建的节点序号不是当前最小的,此时无论前面有何种锁,我都不能进行后续业务逻辑处理,只能等待我前面所有的锁节点删除,才能进行后续业务逻辑处理。
具体的实现:
1 | 规定锁对应的节点格式: |
基本实现例子
1 | Tips: |