当前位置: 移动技术网 > IT编程>开发语言>Java > springboot 中单机 redis 实现分布式锁

springboot 中单机 redis 实现分布式锁

2020年01月03日  | 移动技术网IT编程  | 我要评论

在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。

以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常简单使用使用如下简单方法即可。即偶尔不执行任务不影响业务。

实现要点

1)获得锁、释放锁需要是原子操作。要么获取成功,要么失败。释放要么成功,要么失败

2)任务完成需要自己释放自己的锁,不能释放别人的锁。

3)锁要有过期时间限制,防止任务崩溃没有释放锁,导致其他节点无法获得锁。

4)执行节点超时长时间不释放锁,到下次任务开始执行并行存在的情况

 

要考虑的风险点

1)获取锁失败,偶尔不执行任务要不影响业务或告警人工干预

2)redis 宕机,导致无法获取锁

 

方案一:低版本使用 jedis 实现

1 添加依赖,高版本或低版本有些方法可能没有

<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>redis.clients</groupid>
<artifactid>jedis</artifactid>
<version>2.10.2</version>
</dependency>

2 实现方法

@slf4j
public abstract class absdistributelock {

    private jedis jedis;

    public void initdistributelock(string ip, int port, integer database, string password) {
        jedis = new jedis(ip, port);
        if (password != null && !password.isempty()) {
            jedis.auth(password.trim());
        }
        if (database == null || database < 0 || database > 15) {
            database = 0;
        }
        jedis.select(database);
    }

    private static final string lock_success = "ok";
    private static final string set_if_not_exist = "nx";
    private static final string set_with_expire_time = "px";

    private static final long release_success = 1l;

    /**
     * 具体的任务需要子类去实现
     *
     * @throws rtexception 分布式锁异常
     */
    public abstract void taskservice() throws rtexception;

    /**
     * 任一执行,any of
     * 所有节点任意一个执行任务 taskservice 即可,没有获得锁的节点不执行任务
     *
     * @param lockkey    lockkey
     * @param keyvalue   keyvalue
     * @param expiretime 过期时间 ms
     */
    public void onlyonenodeexecute(string lockkey, string keyvalue, int expiretime) {
        boolean getlock = false;
        try {
            if ((getlock = getdistributedlock(jedis, lockkey, keyvalue, expiretime))) {
                taskservice();
            }
        } finally {
            if (getlock) {
                releasedistributedlock(jedis, lockkey, keyvalue);
            }
        }
    }

    /**
     * 所有串行执行,all in line
     * 所有节点都必须执行该任务,每次只能一个执行。
     *
     * @param lockkey    lockkey
     * @param keyvalue   keyvalue
     * @param expiretime 过期时间 ms
     */
    public void allnodeexecute(string lockkey, string keyvalue, int expiretime) {
        try {
            while (!(getdistributedlock(jedis, lockkey, keyvalue, expiretime))) {
                try {
                    thread.sleep(200);
                } catch (interruptedexception e) {
                    log.info(e.getmessage());
                }
            }
            taskservice();
        } finally {
            releasedistributedlock(jedis, lockkey, keyvalue);
        }
    }

    /**
     * @param jedis      客户端
     * @param lockkey    key
     * @param keyvalue   key值
     * @param expiretime 过期时间,ms
     */
    public static boolean getdistributedlock(jedis jedis, string lockkey, string keyvalue, int expiretime) {
        string result = jedis.set(lockkey, keyvalue, set_if_not_exist, set_with_expire_time, expiretime);
        if (lock_success.equals(result)) {
            log.info("ip:[{}] get lock:[{}], value:[{}], getlock result:[{}]", iputil.getlocalipaddr(), lockkey, keyvalue, result);
            return true;
        } else {
            return false;
        }
    }

    public static boolean releasedistributedlock(jedis jedis, string lockkey, string keyvalue) {
        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(keyvalue));
        log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", iputil.getlocalipaddr(), lockkey, keyvalue, result);
        if (release_success.equals(result)) {
            return true;
        }
        return false;
    }

}

 

方案二:高版本的springboot,使用 lua 脚本执行

1 添加依赖

<dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-data-redis</artifactid>
        <version>2.2.0.release</version>
      </dependency>

2 代码实现

@slf4j
public abstract class absdistributelocklua {

    private redistemplate<string, string> redistemplate;

    public absdistributelocklua(redistemplate<string, string> redistemplate) {
        this.redistemplate = redistemplate;
    }

    /**
     * 具体的任务需要子类去实现
     *
     * @throws rtexception 分布式锁异常
     */
    public abstract void taskservice() throws rtexception;

    /**
     * 任一执行,any of
     * 所有节点任意一个执行任务 taskservice 即可,没有获得锁的节点不执行任务
     *
     * @param lockkey    lockkey
     * @param keyvalue   keyvalue
     * @param expiretime 过期时间 ms
     */
    public void onlyonenodeexecute(string lockkey, string keyvalue, int expiretime) {
        boolean getlock = false;
        try {
            if ((getlock = getdistributelock(redistemplate, lockkey, keyvalue, expiretime))) {
                taskservice();
            }
        } finally {
            if (getlock) {
                releasedistributelock(redistemplate, lockkey, keyvalue);
            }
        }
    }

    /**
     * 所有串行执行,all in line
     * 所有节点都必须执行该任务,每次只能一个执行。
     *
     * @param lockkey    lockkey
     * @param keyvalue   keyvalue
     * @param expiretime 过期时间 ms
     */
    public void allnodeexecute(string lockkey, string keyvalue, int expiretime) {
        try {
            while (!(getdistributelock(redistemplate, lockkey, keyvalue, expiretime))) {
                try {
                    thread.sleep(200);
                } catch (interruptedexception e) {
                    log.info(e.getmessage());
                }
            }
            taskservice();
        } finally {
            releasedistributelock(redistemplate, lockkey, keyvalue);
        }
    }

    /**
     * 通过lua脚本 加锁并设置过期时间
     *
     * @param key    锁key值
     * @param value  锁value值
     * @param expire 过期时间,单位毫秒
     * @return true:加锁成功,false:加锁失败
     */
    public boolean getdistributelock(redistemplate<string, string> redistemplate, string key, string value, int expire) {
        defaultredisscript<string> redisscript = new defaultredisscript<string>();
        redisscript.setresulttype(string.class);
        string strscript = "if redis.call('setnx',keys[1],argv[1])==1 then return redis.call('pexpire',keys[1],argv[2]) else return 0 end";
        redisscript.setscripttext(strscript);
        try {
            object result = redistemplate.execute(redisscript, redistemplate.getstringserializer(), redistemplate.getstringserializer(), collections.singletonlist(key), value, expire);
            system.out.println("redis返回:" + result);
            return "1".equals("" + result);
        } catch (exception e) {
            //可以自己做异常处理
            return false;
        }
    }

    /**
     * 通过lua脚本释放锁
     *
     * @param key   锁key值
     * @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁)
     * @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放)
     */
    public boolean releasedistributelock(redistemplate<string, string> redistemplate, string key, string value) {
        defaultredisscript<string> redisscript = new defaultredisscript<>();
        redisscript.setresulttype(string.class);
        string strscript = "if redis.call('get', keys[1]) == argv[1] then return redis.call('del', keys[1]) else return 0 end";
        redisscript.setscripttext(strscript);
        try {
            object result = redistemplate.execute(redisscript, redistemplate.getstringserializer(), redistemplate.getstringserializer(), collections.singletonlist(key), value);
            return "1".equals("" + result);
        } catch (exception e) {
            //可以自己做异常处理
            return false;
        }
    }
}

 

 

代码地址:https://github.com/crazycodelove/distribute-lock

 

参考文献:

https://www.cnblogs.com/bcde/p/11132479.html

https://blog.csdn.net/u013985664/article/details/94459529

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网