分布式锁简单实现

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.tangyh.basic.cache.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Component
public class RedisLock {

/** * redis超时时间 */
public static final int LOCK_EXPIRE = 1000*10;// ms
@Autowiredprivate StringRedisTemplate redisTemplate;

/**
* 分布式锁
*
* @param key key值
* @return 是否获取到
*/
public boolean lock(String key) {
String lock = key;
try {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE;
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
//判断该key上的值是否过期了
byte[] value = connection.get(lock.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
// 如果锁已经过期
byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE).getBytes());
// 防止死锁
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
}
}

/**
* 删除锁
*
* @param key
*/
public void delete(String key) {
try {
redisTemplate.delete(key);
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
}
}
}
  1. 首先,定义了一个 lock 字符串,作为锁的键名。
  2. 使用 redisTemplate.execute 方法执行 Redis 回调操作,在回调函数中进行分布式锁的获取逻辑。
  3. 在回调函数内部,计算了锁的过期时间 expireAt,并通过 Redis 的 setNX 命令尝试设置键值对,如果设置成功(即获取到了锁),则返回 true
  4. 如果 setNX 返回 false,说明锁已经被其他客户端持有,则尝试检查该锁的过期时间是否已经过期。
  5. 如果锁已经过期,则使用 getSet 命令更新锁的过期时间,并返回 true,表示成功获取到了锁。这里使用 getSet 命令是为了保证更新过期时间的原子性,防止出现竞态条件。
  6. 如果锁未过期,则返回 false,表示未能成功获取到锁。

delete 方法中,执行删除锁操作:

  1. 使用 redisTemplate.delete 方法删除指定键名的锁。
  2. finally 块中,无论是否发生异常,都会执行 RedisConnectionUtils.unbindConnection 方法释放 Redis 连接。

这样,无论是获取锁还是删除锁,在操作完成后都会正确释放 Redis 连接,避免了连接资源泄漏的问题。