分布式锁的实现方式
分布式锁通常有3种实现方式,即数据库乐观锁、基于redis的分布式锁和基于zookeeper的分布式锁。
一、基于redis的乐观锁
选用Redis实现分布式锁原因
Redis有很高的性能
Redis命令对此支持较好,实现起来比较方便
使用命令介绍
SETNX
SETNX key val
当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
expire
expire key timeout
为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
delete
delete key
删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
实现
使用的是jedis来连接Redis。
实现思想
获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
redisson是redis官网推荐的java语言实现分布式锁的项目。
二、基于数据库的乐观锁
所谓乐观锁,就是假设数据在极大多数情况下不会形成冲突,因此只有在数据提交的时候,才会对数据是否产生冲突进行检验。如果产生数据冲突了,则返回错误信息,进行相应的处理。那我们如何来实现乐观锁呢?一般采用以下方式:使用版本号(version)机制来实现,这是乐观锁最常用的实现方式。那什么是版本号呢?版本号就是为数据添加一个版本标志,通常我会为数据库中的表添加一个int类型的"version"字段。当我们将数据读出时,我们会将version字段一并读出;当数据进行更新时,会对这条数据的version值加1。当我们提交数据的时候,会判断数据库中的当前版本号和第一次取数据时的版本号是否一致,如果两个版本号相等,则更新,否则就认为数据过期,返回错误信息。
三、基于数据库的悲观锁
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。悲观锁主要通过 select for update 实现。
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;
四、基于zookeeper的分布式锁
1. 客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推;
2. 客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;
3. 执行业务代码;
4, 完成业务流程后,删除对应的子节点释放锁。
虽然zookeeper原生客户端暴露的API已经非常简洁了,但是实现一个分布式锁还是比较麻烦的…我们可以直接使用curator这个开源项目提供的zookeeper分布式锁实现。