一,问题描述


  由于楼主公司paas应用是双边部署,异地双活机制,导致每台服务器的定时任务都启动一次。

二,解决方案

  想过用数据库乐观锁解决问题,执行定时任务之前去数据库获取锁,其他线程将无法获取锁执行程序。考虑到性能问题,采用另一种方案:redis锁。当然还有zookeeper方式实现,这里先不讨论。

三,实现细节

  1,首先引入jedis依赖(楼主使用Java)



<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>



  2,使用工具类



public class RedisTool {
/**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, “NX”, "EX", expireTime);

        if ("OK".equals(result)) {
            return true;
        }
        return false;

    }
/**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (1L.equals(result)) {
            return true;
        }
        return false;

    }
}



四,分析

1,可以看到,加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:

  • 第一个为key,我们使用key来当锁,因为key是唯一的。
  • 第二个为value,通过给value赋值为requestId,实现绑定请求,方便后续解锁,楼主实际使用通过UUID.randomUUID().toString()方法生成。
  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  • 第四个为expx,EX是秒,PX是毫秒。
  • 第五个为time,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

注意本方法,只适用于Redis单机部署的场景,分布式部署参考redisession。

2,不适用setNX方法在于其和expire配合使用,这样操作就不是原子性了

3,同理,释放锁的时候,通过lua执行能保证原子性,如果通过判断requestId,使用delete方法删除,可能判断同一个id时其他线程获取到锁,导致删除其他线程的锁。