分布式锁实现方式
一、什么是分布式锁
1、当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
2、用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
二、分布式锁的使用场景
为了保证一个方法或属性在高并发情况下的同一时间只能被同一线程执行
就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
三、分布式锁应具备的条件
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用、高性能的获取锁与释放锁;
3、具备可重入特性;
4、具备锁失效机制,防止死锁;
5、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
四、分布式锁的实现方式
分布式锁常见的多种实现方式:
1、数据库悲观锁;
所谓悲观锁,就是是对数据被的修改持悲观态度(认为数据在被修改的时候一定会存在并发问题),因此在整个数据处理过程中将数据锁定。
悲观锁的实现,往往依靠数据库提供的锁机制
使用场景举例:
在秒杀案例中,生成订单和扣减库存的操作,可以通过商品记录的行锁进行保护
通过使用select...for update语句,在查询商品表库存时将该条记录加锁,待下单减库存完成后,再释放锁。
(1)、查询出商品信息
select stockCount from seckill_good where id=1 for update;
(2)、根据商品信息生成订单
insert into seckill_order (id,good_id) values (null,1);
(3)、修改商品stockCount减一
update seckill_good set stockCount=stockCount-1 where id=1;
2、数据库乐观锁;
乐观锁的具体实现主要就是两个步骤:冲突检测和数据更新。
有一种比较典型的就是Compare and Swap(CAS)技术:当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,
失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS的实现中,在表中增加一个version字段,操作前先查询version信息,在数据提交时检查version字段是否被修改,如果没有被修改则进行提交,否则认为是过期数据。
比如前面的扣减库存问题,通过乐观锁可以实现如下:
1、查询出商品信息
select stockCount, version from seckill_good where id=1;
2、根据商品信息生成订单
insert into seckill_order (id,good_id) values (null,1);
3、修改商品库存
update seckill_good set stockCount=stockCount-1, version = version+1 where id=1, version=version;
我们在更新之前,先查询一下库存表中当前版本(version),然后在做update的时候,以version 作为一个修改条件。
CAS 乐观锁有两个问题:
1、 CAS 存在一个比较重要的问题,即ABA问题. 解决的办法是version字段顺序递增。
2、乐观锁的方式,在高并发时,只有一个线程能执行成功,会造成大量的失败,这给用户的体验显然是很不好的。
3、基于Redis的分布式锁;
通过Redis的setnx、expire命令可以实现简单的锁机制:
1、key不存在时创建,并设置value和过期时间,返回值为1;成功获取到锁;
2、如key存在时直接返回0,抢锁失败;
3、持有锁的线程释放锁时,手动删除key; 或者过期时间到,key自动删除,锁释放。
线程调用setnx方法成功返回1认为加锁成功,其他线程要等到当前线程业务操作完成释放锁后,才能再次调用setnx加锁成功。
如果出现了这么一个问题:如果setnx是成功的,但是expire设置失败,一旦出现了释放锁失败,或者没有手工释放,那么这个锁永远被占用,其他线程永远也抢不到锁。
所以,需要保障setnx和expire两个操作的原子性,要么全部执行,要么全部不执行,二者不能分开。
解决的办法有两种:
1、使用set的命令时,同时设置过期时间,不再单独使用 expire命令
2、使用lua脚本,将加锁的命令放在lua脚本中原子性的执行
4、基于ZooKeeper的分布式锁。
略

浙公网安备 33010602011771号