欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot 中单机 redis 实现分布式锁

程序员文章站 2022-06-22 11:02:01
在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。 以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常简单使用使用如下简单方法即可。即偶尔不执行任务不影响业务。 实现要点 1)获得锁、释放锁需要是原子操 ......

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

以下说明非严格意义的分布式锁,因为 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