分布式锁在分布式系统中是非常常见的,redis以及ZK都可以实现分布式锁,在文章Curator从实战的层面进行了实际的分布式锁的实现,具体看这个文章即可。下面是再唠叨唠叨。

redis实现分布式锁

之前,在redis实现分布式锁实现过一个基于redis的分布式锁,用来保证一个系统去定时关单。

image

这上面两个例子,一个是针对多个系统修改同一个资源,一个是面临高并发下订单时控制同一时间只能有一个用户拿到锁然后下订单(可能是多个系统,比如是订单服务器和库存服务器,当然,为了解耦和提高速度,那么可以把写订单表这个逻辑用MQ异步出去),首先我要判断库存是不是真的够,那么这个时候我就要用分布式锁控制,防止两个用户同时来查库存数量然后都觉得自己可以下订单(极端情况,库存只有1,那么这两个用户同时查到为1,那么都认为自己可以下订单咋办?),其他的用户必须等到这个用户释放锁或者超时才可以再拿到锁再去执行操作。这样,有效地解决了商品的超卖问题。

优点:实现简单,吞吐量十分客观,对于高并发情况应付自如,自带超时保护,对于网络抖动的情况也可以利用超时删除策略保证不会阻塞所有流程。但是redis存在一些问题:

  • 单点问题:因为redis一般都是单实例使用,那么对于单点问题,可以做一个主从。当然主从切换的时候也是不可用的,因为主从同步是异步的,可能会并发问题。如果对于主从还是不能保证可靠性的话,可以上Redis集群,对于Redis集群,因为使用了类一致性Hash算法,虽然不能避免节点下线的并发问题(当前的任务没有执行完,其他任务就开始执行),但是能保证Redis是可用的。可用性的问题是出了问题之后的备选方案,如果我们系统天天都出问题还玩毛啊,对于突发情况牺牲一两个请求还是没问题的。
  • 锁删除失败:分布式锁基本都有这个问题,可以对key设置失效时间。这个超时时间需要把控好,过大那么系统吞吐量低,很容易导致超时。如果过小那么会有并发问题,部分耗时时间比较长的任务就要遭殃了。

redis集群的同步策略是需要时间的,有可能A线程setNX成功后拿到锁,但是这个值还没有更新到B线程执行setNX的这台服务器,那就会产生并发问题。

zookeeper实现分布式锁

Zookeeper是一个分布式一致性协调框架,主要可以实现选主、配置管理和分布式锁等常用功能,因为Zookeeper的写入都是顺序的,在一个节点创建之后,其他请求再次创建便会失败,同时可以对这个节点进行Watch,如果节点删除会通知其他节点抢占锁。

Zookeeper实现分布式锁虽然是比较重量级的,但实现的锁功能十分健全,由于Zookeeper本身需要维护自己的一致性,所以性能上较Redis还是有一定差距的。

“惊群”就是在一个节点删除的时候,大量对这个节点的删除动作有订阅Watcher的线程会进行回调,这对Zk集群是十分不利的。所以需要避免这种现象的发生。

为了解决“惊群“问题,我们需要放弃订阅一个节点的策略,那么怎么做呢?详细看这里:https://www.jianshu.com/p/5d12a01018e1

最后想说明一点,其实对于Zookeeper的一些常用功能是有一些成熟的包实现的,像Curator。Curator的确是足够牛逼,不仅封装了Zookeeper的常用API,也包装了很多常用Case的实现。形如:

1
2
3
4
5
6
7
8
9
10
11
12
InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) )
{
try
{
// do some work inside of the critical section here
}
finally
{
lock.release();
}
}

具体的时间在分布式电商项目-码码购中用Curator实现分布式锁实现了某些场景的需求。