redis实现分布式锁

一 场景

分布式环境,一共三台机器,跑批时,为了保证跑批触发时间点只有一个机器进行job跑批,故增加分布式锁来控制防重跑。

二 redis实现分布式锁

/**
* 对于分布式加锁本例中使用 setnx()含义是SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
* 另外使用 getset()也可以:这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
* ttl命令:当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间。(在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 )。
*/

  

@Component
public class RedisLock {

    private static final String PREFIX_LOCK_KEY = "RULE:LOCK:"; //缓存 key前辍
    private static final Integer EXPIRE_SECOND = 300; //缓存时间

    @Resource
    private RedisClusterClient redisClusterClient;

    public boolean tryLock(String key) {
        String redisKey = PREFIX_LOCK_KEY + key;
        Long val = redisClusterClient.tryLock(redisKey);
        LOGGER.info("获取锁,key:{},val:{}", redisKey, val);
        if (val > 0) {
            redisClusterClient.expire(redisKey, EXPIRE_SECOND);
            return true;
        } else {
            if (redisClusterClient.ttl(redisKey) < 0) {
                LOGGER.info("delete lock,key:{}", redisKey);
                redisClusterClient.expire(redisKey, EXPIRE_SECOND);
                return true;
            }
        }
        return false;
    }

    public void unLock(String key) {
        redisClusterClient.del(PREFIX_LOCK_KEY + key);
        LOGGER.info("释放锁 unLock,key:{}", key);
    }
}

  

  

应用代码如下,

public void xx() {
        if (redisLock.tryLock(key)) {
            try {
                LOGGER.info("拿到redis分布式锁!");
                .....
            } catch (Exception e) {
                LOGGER.error(e);
            } finally {
                redisLock.unLock(key);
            }
        } else {
            LOGGER.info("请稍后再试!");
}

  


缺点:

  1. getSet与expire不是一个原子操作,可能执行完setnx该进程就挂了。
  2. 当锁过期后,该进程还没执行完,可能造成同时多个进程取得锁。(貌似这个问题目前还没有很优雅的解决方案)

三 3种分布式锁方案对比:

数据库锁:

优点:直接使用数据库,使用简单。
缺点:分布式系统大多数瓶颈都在数据库,使用数据库锁会增加数据库负担。

缓存锁:

优点:性能高,实现起来较为方便,在允许偶发的锁失效情况,不影响系统正常使用,建议采用缓存锁。
缺点:通过锁超时机制不是十分可靠,当线程获得锁后,处理时间过长导致锁超时,就失效了锁的作用。

zookeeper锁:

优点:不依靠超时时间释放锁;可靠性高;系统要求高可靠性时,建议采用zookeeper锁。
缺点:性能比不上缓存锁,因为要频繁的创建节点删除节点。

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库

原文地址:https://www.cnblogs.com/wanghongsen/p/9792234.html