基于Redis实现简单的分布式锁

摘要

分布式锁在不少应用场景下是很是有效的手段,好比当运行在多个机器上的不一样进程须要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问。分布式锁实现的方案有不少,好比基于ZooKeeper实现、或者基于Mysql实现等等,今天咱们来一块儿看看如何基于Redis实现分布式锁服务。redis

 

分布式锁要点

对于分布式锁的目标,咱们必须首先明确三点:算法

  1. 任何一个时间点必须只可以有一个客户端拥有锁。
  2. 不可以有死锁,也就是最终客户端都可以得到锁,尽管可能会经历失败。
  3. 错误容忍性要好,只要有大部分的Redis实例存活,客户端就应该可以得到锁。

 

一种简单的方法

理解了上面咱们列出的三个点,咱们来分析一下通常的基于Redis实现的分布式锁:sql

使用Redis实现锁最简单的办法是建立一个key,且这个key一般有有限的存活时间,这一点能够利用Redis的过时时间特性,因此锁最终会被释放掉,当客户端须要释放资源的时候,客户端delete这个key便可。安全

So far so good!可是有个单点问题,假如Redis master挂掉怎么办,所以咱们须要加个slave,当master挂掉的时候能够切换到slave。这又带来了新的问题,因为Redis的复制是异步的,所以咱们不能保证同时只有一个客户端得到锁。dom

这个模型有很显然的竞态:异步

  1. Client在Master上面得到了锁。
  2. master在数据同步到slave以前挂掉了。
  3. slave升级成为master。
  4. Client B申请了一样的资源的锁,成功了!

在特定条件下这种状况是会发生的,当出现多个客户端同时得到锁的时候,咱们就认为能够这种锁方案是不可靠的。分布式

 

基于Redis单例的实现

为了后面更好的了解分布式锁的实现,咱们先来看看如何基于Redis单例实现锁服务。咱们能够用下面方法得到锁:spa

SET resource_name my_random_value NX PX 30000

上面的命令在只有当key不存在的时候会执行成功(NX选项),同时会设置过时时间为30000ms(PX选项)。key的值会被设置为my_random_value。这个值在多个客户端和锁中必须是惟一的,咱们使用random value是为了方便安全地释放锁,看看下面的脚本:code

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

只有当key存在且值是预期的值的时候才会删除key。这种方式能够避免误删除其余客户端建立的锁。例如,当客户端获取锁以后执行一个很长时间的逻辑,一直过了锁的过时时间,这个时候锁会被自动释放掉,而另一个客户端又获取了这个锁,前一个客户端终于执行完了逻辑执行,回头释放锁,删除key,其实这个时候释放的已是另一个客户端持有的锁了。使用DEL是不安全的,由于客户端有可能误删其余客户端持有的锁。上面脚本的方法的好处是每次得到锁的时候加上一个随机的签名,当释放锁的时候去看看是否是本身持有的锁,这个时候就不会误删。    进程

如今咱们学会了如何在Redis单例上获取锁和释放锁,那么接下来咱们看看如何在Redis集群上获取锁和释放锁。

 

基于Redlock算法的实现

在分布式环境下,假设咱们有N个master,这些节点都是独立的,所以咱们没有配置复制策略。上面咱们已经学会了如何在单机环境下获取锁和释放锁,咱们假设的更具体一些,N=5,为了能获取锁,客户端的步骤为:

  1. 获取当前系统的时间,以毫秒为单位。
  2. 顺序的获取N个Redis实例上的锁,在每一个实例中都用一样的key和value。在步骤2中,客户端须要一个比过时时间小不少的超时时间,例如,若是自动过时时间为10s,那么超时时间大概是5~50ms,这样能够避免客户端一直被阻塞,而不能继续请求下一个实例。
  3. 客户端每次都要计算已通过去了多长时间,使用的时间是否小于key自动过时的时间同时又获取了至少3个实例的锁。若是是,那么咱们认为客户端这次获取锁成功。
  4. 若是锁被获取了,锁的过时时间必需要减去获取锁花费的时间。
  5. 若是当前客户端获取锁失败,客户端须要释放全部以前获取到的锁。

 

总结

这篇文章主要介绍Redis实现分布式锁的基本方法,而后分别介绍经过Redis单例和Redis集群实现分布式锁的方法。

 

参考文献

《Redis官方文档》

相关文章
相关标签/搜索