当前参与的项目中会遇到一些线程安全问题,由于业务是多节点部署的,Java的单机的并发同步手段synchronized
和java.util.concurrent
包已经不太够用了,这个时候我们需要分布式锁来保证线程安全问题,所以这里学习总结了几种分布式锁的实现思路。
分布式的CAP理论告诉我们任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项
。 一般情况下,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性
,只要这个最终时间是在用户可以接受的范围内即可。在很多时候,为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。这里我们主要介绍对象分布式锁,分布式锁的的具体实现方案主要如下三种:
redis
)的实现zookeeper
的实现一个可靠的、高可用的分布式锁需要满足以下几点
乐观锁的通常是基于数据版本号来实现的。比如,有个商品表t_goods
,有一个字段left_count
用来记录商品的库存个数。在并发的情况下,为了保证不出现超卖现象,即left_count
不为负数。乐观锁的实现方式为给商品表增加一个版本号字段version
,默认为0,每修改一次数据,将版本号加1。
无版本号并发超卖示例:
-- 线程1查询,当前left_count为1,则有记录
select * from t_goods where id = 10001 and left_count > 0
-- 线程2查询,当前left_count为1,也有记录
select * from t_goods where id = 10001 and left_count > 0
-- 线程1下单成功库存减一,修改left_count为0,
update t_goods set left_count = left_count - 1 where id = 10001
-- 线程2下单成功库存减一,修改left_count为-1,产生脏数据
update t_goods set left_count = left_count - 1 where id = 10001
有版本号的乐观锁示例:
-- 线程1查询,当前left_count为1,则有记录,当前版本号为999
select left_count, version from t_goods where id = 10001 and left_count > 0;
-- 线程2查询,当前left_count为1,也有记录,当前版本号为999
select left_count, version from t_goods where id = 10001 and left_count > 0;
-- 线程1,更新完成后当前的version为1000,update状态为1,更新成功
update t_goods set version = 1000, left_count = left_count-1 where id = 10001 and version = 999;
-- 线程2,更新由于当前的version为1000,udpate状态为0,更新失败,再针对相关业务做异常处理
update t_goods set version = 1000, left_count = left_count-1 where id = 10001 and version = 999;
可以发现,这种和CAS的乐观锁机制是类似的,所不同的是CAS的硬件来保证原子性,而这里是通过数据库来保证单条SQL语句的原子性。顺带一提CAS的ABA问题一般也是通过版本号来解决。
基于数据库的排他锁需要通过数据库的唯一性约束UNIQUE KEY
来保证数据的唯一性,从而为锁的独占性提供基础。
表结构如下:
CREATE TABLE `distribute_lock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘主键‘,
`unique_mutex` varchar(64) NOT NULL COMMENT ‘需要锁住的资源或者方法‘,
-- `state` tinyint NOT NULL DEFAULT 1 COMMENT ‘1:未分配;2:已分配
PRIMARY KEY (`id`),
UNIQUE KEY `unique_mutex`
);
其中,unique_mutex
就是我们需要加锁的对象,需要用UNIQUE KEY
来保证此对象唯一。
加锁时增加一条记录:
insert into distribute_lock(unique_mutex) values(‘mutex_demo‘);
如果当前SQL执行成功代表加锁成功,如果抛出唯一索引异常(DuplicatedKeyException
)则代表加锁失败,当前锁已经被其他竞争者获取。
解锁锁时删除该记录:
delete from distribute_lock(unique_mutex) values(‘muetx_demo‘);
除了增删记录,也可以通过更新state
字段来标识是否获取到锁。
-- 获取锁
update distribute_lock set state = 2 where `unique_mutex` = ‘muetx_demo‘ and state=1;
更新之前需要SELECT确认锁在数据库中存在,没有则创建之。如果创建或更新失败,则说明这个资源已经被别的线程占用了。
数据库排他锁可能出现的问题及解决思路:
总的来说,基于数据库的分布式锁,能够满足一些简单的需求,好处是能够少引入依赖,实现较为简单,缺点是性能较低,且难以满足复杂场景下的高并发需求。
redis
的实现一个简单的分布式锁机制是使用setnx
、expire
、del
三个命令的组合来实现的。setnx
命令的含义为:当且仅当key不存在时,value设置成功,返回1;否则返回0。另外两个命令,见名知意,就不多做解释了。
# 加锁,设置锁的唯一标识key,返回1说明加锁成功,返回0加锁失败
setnx key value
# 设置锁超时时间为30s,防止死锁
expire key 30
# 解锁, 删除锁
del key
这种思路存在的问题:
setnx
和expire
的非原子性:如果加锁之后,服务器宕机,导致expire
和del
均执行不了,会导致死锁。del
导致误删:A线程超时之后未执行完, 锁过期释放;B线程获得锁,此时A线程执行完,执行del
将B线程的锁删除。对应的解决思路:
Redis 2.6.12
版本之后,set命令增加了NX
可选参数,可替代setnx
命令;增加了EX
可选参数,可以设置key的同时指定过期时间del
释放锁之前先判断一下锁是不是自己的。(释放和判断不是原子性的,需要封装在lua脚本中)redis
分布式锁的缺点在大型的应用中,一般redis
服务都是集群形式部署的,由于Slave同步Master是异步的,所以会出现客户端A在Master上加锁,此时Master宕机,Slave没有完成锁的同步,Slave变为Master,客户端B此时可以完成加锁操作。
为了解决这一问题,官方给出了redlock
算法,即使这样在一些较复杂的场景下也不能100%保证没有问题。较复杂,留待后续研究。
zookeeper
的实现zookeeper
是一个开源的分布式协调服务框架,主要用来解决分布式集群中的一致性问题和数据管理问题。zookeeper
本质上是一个分布式文件系统,由一群树状节点组成,每个节点可以存放少量数据,且具有唯一性。
zookeeper
有四种类型的节点:
基于zookeeper
实现的分布式锁主要利用了zookeeper
临时顺序节点的特性和事件监听机制。主要思路如下:
其他思路:
zookeeper
分布式锁的缺点zookeeper
分布式锁有着较好的可靠性,但是也有如下缺点:
zookeeper
分布式锁是性能可能没有redis
分布式锁高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。zookeeper
也有可能带来并发问题,只是并不常见而已。比如,由于网络抖动,客户端与zk
集群的session
连接断了,那么zk
以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk
有重试机制,一旦zk
集群检测不到客户端的心跳,就会重试,curator
客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
zookeeper
>= redis
> 数据库
zookeeper
和redis
需要考虑的情况更多,实现相对较为复杂,但是都有现成的分布式锁框架curator
和redision
,用起来代码反而可能会更简洁。redis
> zookeeper
> 数据库
redis
数据存在内存,速度很快;zookeeper
虽然数据也存在内存中,但是本身维护节点的一致性。需要耗费一些性能;数据库则只有索引在内存中,数据存于磁盘,性能较差。zookeeper
> redis
> 数据库
zookeeper
天生设计定位就是分布式协调,强一致性,可靠性较高;redis
分布式锁需要较多额外手段去保证可靠性;数据库则较难满足复杂场景的需求。原文:https://www.cnblogs.com/wchaos/p/13721460.html