当前位置: 代码网 > it编程>编程语言>Java > springboot+redis+lua实现分布式锁的脚本

springboot+redis+lua实现分布式锁的脚本

2024年11月29日 Java 我要评论
1 分布式锁java锁能保证一个jvm进程里多个线程交替使用资源。而分布式锁保证多个jvm进程有序交替使用资源,保证数据的完整性和一致性。分布式锁要求互斥。一个资源在某个时刻只能被一个线程访问。避免死

1 分布式锁

java锁能保证一个jvm进程里多个线程交替使用资源。而分布式锁保证多个jvm进程有序交替使用资源,保证数据的完整性和一致性。
分布式锁要求

互斥。一个资源在某个时刻只能被一个线程访问。避免死锁。避免某个线程异常情况不释放资源,造成死锁。可重入。高可用。高性能。非阻塞,没获取到锁直接返回失败。

2 实现

1 lua脚本

为了实现redis操作的原子性,使用lua脚本。为了方便改脚本,将脚本单独写在文件里。

-- 加锁脚本
if redis.call('setnx', keys[1], argv[1]) == 1 then
    redis.call('pexpire', keys[1], argv[2]);
    return true;
else
    return false;
end
-- 解锁脚本
if redis.call('get', keys[1]) == argv[1] then
    redis.call('del', keys[1]);
    return true;
else
    return false;
end
-- 更新锁脚本
if redis.call('get', keys[1]) == argv[1] then
    redis.call('pexpire', keys[1], argv[2]);
    -- pexpire与expire的区别是:pexpire毫秒级,expire秒级
    return true;
else
    return false;
end

将脚本装在springboot容器管理的bean里。

@configuration
public class redisconfig {
    @bean("lock")
    public redisscript<boolean> lockredisscript() {
        defaultredisscript redisscript = new defaultredisscript<>();
        redisscript.setresulttype(boolean.class);
        redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/lock.lua")));
        return redisscript;
    }
    @bean("unlock")
    public redisscript<boolean> unlockredisscript() {
        defaultredisscript redisscript = new defaultredisscript<>();
        redisscript.setresulttype(boolean.class);
        redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/unlock.lua")));
        return redisscript;
    }
    @bean("refresh")
    public redisscript<boolean> refreshredisscript() {
        defaultredisscript redisscript = new defaultredisscript<>();
        redisscript.setresulttype(boolean.class);
        redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/refresh.lua")));
        return redisscript;
    }
}

redis分布式锁业务类

@service
public class lockservice {
    private static final long lock_expire = 30_000;
    private static final logger logger = loggerfactory.getlogger(lockservice.class);
    @autowired
    private redistemplate<string, object> redistemplate;
    @autowired
    @qualifier("lock")
    private redisscript<boolean> lockscript;
    @autowired
    @qualifier("unlock")
    private redisscript<boolean> unlockscript;
    @autowired
    @qualifier("refresh")
    private redisscript<boolean> refreshscript;
    public boolean lock(string key, string value) {
        boolean res = redistemplate.execute(lockscript, list.of(key), value, lock_expire);
        if (res == false) {
            return false;
        }
        refresh(key, value);
        logger.info("lock, key: {}, value: {}, res: {}", key, value, res);
        return res;
    }
    public boolean unlock(string key, string value) {
        boolean res = redistemplate.execute(unlockscript, list.of(key), value);
        logger.info("unlock, key: {}, value: {}, res: {}", key, value, res);
        return res != null && boolean.true.equals(res);
    }
    private void refresh(string key, string value) {
        thread t = new thread(() -> {
            while (true) {
                redistemplate.execute(refreshscript, list.of(key), value, lock_expire);
                try {
                    thread.sleep(lock_expire / 2);
                } catch (interruptedexception e) {
                    e.printstacktrace();
                }
                logger.info("refresh, current time: {}, key: {}, value: {}", system.currenttimemillis(), key, value);
            }
        });
        t.setdaemon(true); // 守护线程
        t.start();
    }
}

测试类

@springboottest(classes = demoapplication.class)
public class lockservicetest {
    @autowired
    private lockservice service;
    private int count = 0;
    @test
    public void test() throws exception {
        list<completablefuture<void>> tasklist = new arraylist<>();
        for (int threadindex = 0; threadindex < 10; threadindex++) {
            completablefuture<void> task = completablefuture.runasync(() -> addcount());
            tasklist.add(task);
        }
        completablefuture.allof(tasklist.toarray(new completablefuture[0])).join();
    }
    public void addcount() {
        string id = uuid.randomuuid().tostring().replace("-", "");
        boolean trylock = service.lock("account", id);
        while (!trylock) {
            trylock = service.lock("account", id);
        }
        for (int i = 0; i < 10_000; i++) {
            count++;
        }
        try {
            thread.sleep(100_000);
        } catch (exception e) {
            system.out.println(e);
        }
        for (int i = 0; i < 3; i++) {
            boolean releaselock = service.unlock("account", id);
            if (releaselock) {
                break;
            }
        }
    }
}

3 存在的问题

这个分布式锁实现了互斥,redis键映射资源,如果存在键,则资源正被某个线程持有。如果不存在键,则资源空闲。
避免死锁,靠的是设置reds键的过期时间,同时开启守护线程动态延长redis键的过期时间,直到该线程任务完结。
高性能。redis是内存数据库,性能很高。同时lua脚本使得redis以原子性更新锁状态,避免多次spirngboot与redis的网络io。
非阻塞。lock()方法没有获取到锁立即返回false,不会阻塞当前线程。

没有实现可重入和高可用。高可用需要redis集群支持。

到此这篇关于springboot+redis+lua实现分布式锁的文章就介绍到这了,更多相关springboot redis lua分布式锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com