天天看点

redis cluster 分布式锁_redis实现分布式锁

redis cluster 分布式锁_redis实现分布式锁

什么是分布式锁

分布式锁是控制不同系统访问共享资源的一种锁机制,保证共享资源的可用、准确。

分布式锁需要具备什么条件

1.互斥(必须):同一时刻,分布式部署的应用中,同一个方法/资源只能被一台机器上的一个线程占用。

2.锁失效保护(必须):出现客户端断电等异常情况,锁仍然能被其他客户端获取,防止死锁。

3.可重入(可选):同一个线程在没有释放锁之前,如果想再次操作,可以直接获得锁。

4.阻塞/非阻塞(可选):若没有获取到锁,返回获取失败

5.高可用、高性能(可选):获取释放锁最好是原子操作,获取释放锁的性能要好

分布式锁的实现有哪些

1.基于数据库实现

2.基于缓存(redis,memcached)实现

3.基于zookeeper实现

redis实现方案

本篇文章,我们先来讲讲基于redis缓存的实现方案

version1

lock:

SETNX key value

unlock:

DEL key [key ...]

指令含义参考:http://doc.redisfans.com/string/setnx.html

这是第一版最简单的方案,保证在没有出现任何异常的时候多个客户端可以使用分布式锁。

但是问题来了,如下图中所示,client2在获取锁之后突然挂了,这时候锁k将无法释放,其他client就永远拿不到这把锁了。这就是需要解决的锁失效保护问题。

redis cluster 分布式锁_redis实现分布式锁
version2

我们可以给锁引入一个过期时间,这样即使client2挂了,锁过期之后其他client仍然能用。

EXPIRE key seconds

但此时同样会存在一些问题:

1)误删

redis cluster 分布式锁_redis实现分布式锁

解决方法是每个client塞给锁的value设定为唯一的随机字符串,在删除的时候先get一把,如果还是这个字符串的话才去删。

2)过期时间需大于业务执行时间,不然任务还没搞完就被别人抢了

这个时候需要开启另外一个线程专门去刷新锁的过期时间。

version3

我们需要尽量保证获取、释放锁的操作是原子性的,才能避免极端的异常情况。

原子性地加锁

SET key uniquevalue NX EX 20

原子性地解锁

我们可以使用原生的lua脚本

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0           
// java
public void unlock() {
    // 使用lua脚本进行原子删除操作
    String checkAndDelScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                                "return redis.call('del', KEYS[1]) " +
                                "else " +
                                "return 0 " +
                                "end";
    jedis.eval(checkAndDelScript, 1, lockKey, lockValue);
}           
version4

对于阻塞/非阻塞的要求,我们可以根据自己的业务特性,如果要阻塞,使用while循环调用;如果要非阻塞,这次调用失败,就需要增加事后的补偿机制。

对于可重入的特性,在一个线程获取到锁之后,可以把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。

总结

通过redis实现分布式锁的必要可选条件之后,方案基本成型了,这个方案可以提供很好的性能。但是对于超时时间的设置,以及集群部署redis避免单点问题等还需要进一步优化。

继续阅读