使用redis构建分布式锁
Redis使用WATCH命令来代替对数据进行加锁,因为WATCH只会在数据被其他客户端抢先修改了的情况下通知执行了这个命令的客户端,但是不会阻止其他客户端对数据进行修改,所以这个命令被称为乐观锁。
但是使用WATCH命令来监视被频繁访问的键可能会引起性能问题,所以我们需要使用锁。而且相比操作系统级别的锁和编程语言级别的锁,使用Redis构建锁如果一个客户端访问一个锁,可以让所有客户端都看得见,我们将
基于SETNX命令构建。
1.简易版的锁
SETNX命令只会在键不存在的情况下为键设置值,而锁要做的就是将一个随机生成的128位UUID设置为键的值,并使用这个值来防止锁被其他进程取得。如果程序在尝试获取锁的时候失败,那么它将不断地重试,直到成功地
取得锁或者超过给定的时限为止。
1 public String acquireLock(Jedis conn, String lockName, long acquireTimeout){
2 String identifier = UUID.randomUUID().toString();
3
4 long end = System.currentTimeMillis() + acquireTimeout;
5 while (System.currentTimeMillis() < end){
6 if (conn.setnx("lock:" + lockName, identifier) == 1){
7 return identifier;
8 }
9
10 try {
11 Thread.sleep(1);
12 }catch(InterruptedException ie){
13 Thread.currentThread().interrupt();
14 }
15 }
16
17 return null;
18 }
释放锁的代码其实就是删除锁。
1 public boolean releaseLock(Jedis conn, String lockName, String identifier) { 2 String lockKey = "lock:" + lockName; 3 4 while (true){ 5 conn.watch(lockKey); 6 if (identifier.equals(conn.get(lockKey))){//检查进程是否仍然持有锁 7 //释放锁 8 Transaction trans = conn.multi(); 9 trans.del(lockKey); 10 List<Object> results = trans.exec(); 11 if (results == null){ 12 continue; 13 } 14 return true; 15 } 16 17 conn.unwatch(); 18 break; 19 } 20 21 return false; 22 }
但是这种锁在持有者崩溃的情况下不会自动释放锁,这将导致锁一直处于已被获取的状态,所以需要将锁加上超时功能。
2.带有超时特性的锁
1 public String acquireLockWithTimeout(
2 Jedis conn, String lockName, long acquireTimeout, long lockTimeout)
3 {
4 String identifier = UUID.randomUUID().toString();
5 String lockKey = "lock:" + lockName;
6 int lockExpire = (int)(lockTimeout / 1000);
7
8 long end = System.currentTimeMillis() + acquireTimeout;
9 while (System.currentTimeMillis() < end) {
10 //获取锁并设置过期时间
11 if (conn.setnx(lockKey, identifier) == 1){
12 conn.expire(lockKey, lockExpire);
13 return identifier;
14 }
15 //检查过期时间,并在需要时对其进行更新
16 if (conn.ttl(lockKey) == -1) {
17 conn.expire(lockKey, lockExpire);
18 }
19
20 try {
21 Thread.sleep(1);
22 }catch(InterruptedException ie){
23 Thread.currentThread().interrupt();
24 }
25 }
26
27 // null indicates that the lock was not acquired
28 return null;
29 }
之前写的释放函数依旧可以使用。