文章

如何设计一个分布式锁?如何对锁性能进行优化?

如何设计一个分布式锁?如何对锁性能进行优化?

如何设计一个分布式锁?如何对锁性能进行优化?

分布式锁的本质:就是在所有进程都能访问到的一个地方设置一个锁资源,让这些进程都来竞争锁资源。(保证只有一个进程获取资源)数据库、zookeeper、redis等。通常对于分布式锁,会要求响应快、性能高、与业务无关。

如何使用redis实现分布式锁

SETNX key value:当key不存在时,将key设置为value,并返回1。如果key存在,就返回0

EXPIRE key locktime:设置key的有效时长

DEL key:删除。

GETSET key value:先GET,再SET,先返回key对应的值,如果没有就返回空。然后再将key设置成value

最简单的分布式锁:SETNX加锁,DEL解锁。

```plain text public boolean tryLock(RedisConnection conn){ if(conn.SETNX(“mykey”,”1”) == 1){ return true; } else { return false; } }

DEL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
问题:如果获取到锁的进程执行失败,他就永远不会主动解锁,那这个锁就被锁死了。

给锁设置一个过期时长

```plain text
public boolean tryLock(RedisConnection conn){
    if(conn.SETNX("mykey","1") == 1){
        conn.EXPIRE("mykey",1000);
        return true;
    } else {
        return false;
    }
}

DEL

问题:SETNX和EXPIRE并不是原子性的,所以获取到锁的进程有可能还没有执行EXPIRE指令,就挂了,这时锁还是会被索斯。

将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取锁失败时,拿这个时间跟当前时间比对,如果是过期的锁,就先删除锁,再重新上锁

```plain text public boolean tryLock(RedisConnection conn){ long nowTime = System.currentTimeMills(); long expireTIme = nowTime + 1000; if(conn.SETNX(“mykey”,expireTIme) == 1){ conn.EXPIRE(“mykey”,1000); return true; } else { long val = conn.get(“mykey”); if(val != null && val < nowTime){ conn.SET(“mykey”,expireTime); return true; } return false; } }

DEL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
问题:在高并发场景下,会产生多个进程同时拿到锁的情况。

SETNX失败后,获取锁上的时间戳,然后用getset,将自己的过期时间更新上去,并获取旧值。如果这个旧值,跟之前获得的时间戳是不一致的,就表示这个锁已经被其他进程占用了,自己就要放弃竞争锁。

```plain text
public boolean tryLock(RedisConnection conn){
    long nowTime = System.currentTimeMills();
    long expireTIme = nowTime + 1000;
    if(conn.SETNX("mykey",expireTIme) == 1){
        conn.EXPIRE("mykey",1000);
        return true;
    } else {
        long oldVal = conn.get("mykey");
        if(oldVal != null && oldVal < nowTime){
            long currentVal = conn.GETSET("mykey",expireTime);
            if(oldVal == currentVal){
                conn.EXPIRE("mykey",1000);
                return true;
            }
            return false;
        }
        return false;
    }
}

DEL

上面就形成了一个比较高效的分布式锁。分析一下,上面各种优化的根本问题在于SETNX与EXPIRE两个指令无法保证原子性。Redis2.6提供了直接执行lua就脚本的方式,通过lua脚本来保证原子性。redission

本文由作者按照 CC BY 4.0 进行授权