业务场景: 程序中有个一分钟执行一次的策略遍历与判断的任务,为了防止多台机器重复执行,需要设置分布式锁
查询资料,这个技术方案比较成熟
1.使用数据库 2.使用redis 3.使用zk
下面介绍下单机redis的使用分布式锁的思路,整体思路较为完善,其他资料较残缺
注意点:
1.设置随机value值 ,防止A释放锁出现释放了其他客户端B在使用的锁的问题
2.设置失效时间 ,防止死锁
3.Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行
1
2 1set lock:codehole true ex 5 nx
2
4,释放锁在finally中执行 ,确保锁得到释放
5.删除锁要原子性,原因看附属文第一篇
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 1 //只有当key不存在时,才设置指定key的值,返回true; 否则不设置,返回false,如果key是新key,则设置超时时间(以秒为单位
2 boolean setnx = redisUtils.setnx(KEY_PRE + minute, value, 30 * 60);
3
4redisUtils中的方法封装:
5
6
7
8private JedisCluster jedisCluster;
9private Pool<Jedis> jedisPool;
10
11public boolean setnx(String key, String value, int expireSeconds) {
12 if (this.isCluster) {
13 return "OK".equals(this.jedisCluster.set(key, value, "NX", "EX", (long)expireSeconds));
14 } else {
15 Jedis jedis = (Jedis)this.jedisPool.getResource();
16 Throwable var5 = null;
17
18 boolean var6;
19 try {
20 var6 = "OK".equals(jedis.set(key, value, "NX", "EX", expireSeconds));
21 } catch (Throwable var15) {
22 var5 = var15;
23 throw var15;
24 } finally {
25 if (jedis != null) {
26 if (var5 != null) {
27 try {
28 jedis.close();
29 } catch (Throwable var14) {
30 var5.addSuppressed(var14);
31 }
32 } else {
33 jedis.close();
34 }
35 }
36
37 }
38
39 return var6;
40 }
41 }
42 }
43
上述sebnx 与exp在代码里也并不是原子性,稍晚点修改 其实和上面思路相同
我的业务中并没有这样使用.因为如果三台主机时间不同步,前后差十几秒或者几分钟,同样不可避免的本来这一分钟的已经被执行了,锁也释放了,但是另一台机器稍晚点才到达这个时间节点,也会执行,不可避免重复写入的问题,
解决方案:在redis 中设置key-value但不删除,保存半小时,每一分钟生成一个唯一,生成了这一分钟就会只有一个机器去执行
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 1try
2 //每分钟生成一个key,保存30分钟,避免服务器之间时间差异产生重复写入的问题
3 //只有当key不存在时,才设置指定key的值,返回true; 否则不设置,返回false,如果key是新key,则设置超时时间(以秒为单位
4 if (getLock()) {
5 return;
6 }
7
8
9private boolean getLock() {
10 int minute = getMinute();
11 String value = fetchLockValue();
12 boolean setnx = redisUtils.setnx(KEY_PRE + minute, value, 30 * 60);
13 log.info("Redis Lock key : " + KEY_PRE + minute + ",value : " + value);
14 //获取不到锁,退出
15 if (!setnx) {
16 return true;
17 }
18 log.info("获取到锁:{}", KEY_PRE + minute);
19 return false;
20 }
21
22 /**
23 * @return
24 */
25 private String fetchLockValue() {
26 return UUID.randomUUID().toString();
27 }
28
29 /**
30 * 获取当前分钟
31 *
32 * @return
33 */
34 private int getMinute() {
35 Date date = DateUtil.getDate();
36 Calendar calendar = Calendar.getInstance();
37 calendar.setTime(date);
38 return calendar.get(Calendar.MINUTE);
39 }
40