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

 更新时间:2024年11月28日 11:15:49   作者:2301_79308687  
本文介绍了如何使用Spring Boot、Redis和Lua脚本实现分布式锁,包括实现原理、代码实现和存在的问题,感兴趣的朋友跟随小编一起看看吧

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分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现调用ElasticSearch API的示例详解

    Java实现调用ElasticSearch API的示例详解

    这篇文章主要为大家详细介绍了Java调用ElasticSearch API的效果资料,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的可以了解一下
    2023-03-03
  • Java启动时循环依赖处理方式

    Java启动时循环依赖处理方式

    解决Spring Boot启动时由于SwaggerAutoConfiguration注解重复启用导致的循环导入问题,通过排除SwaggerAutoConfiguration并手动开启Swagger配置
    2026-01-01
  • java lambda 表达式中的双冒号的用法说明 ::

    java lambda 表达式中的双冒号的用法说明 ::

    这篇文章主要介绍了java lambda 表达式中的双冒号的用法说明 ::具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot 开发提速神器 Lombok+MybatisPlus+SwaggerUI

    SpringBoot 开发提速神器 Lombok+MybatisPlus+SwaggerUI

    这篇文章主要介绍了SpringBoot 开发提速神器 Lombok+MybatisPlus+SwaggerUI,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 解决idea不显示Services工具栏的问题

    解决idea不显示Services工具栏的问题

    这篇文章主要介绍了解决idea不显示Services工具栏的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Spring Boot 的注解生效详细步骤解析

    Spring Boot 的注解生效详细步骤解析

    Spring通过扫描、解析、注册分层处理配置类注解(如@Configuration、@ComponentScan等),利用递归、延迟加载和缓存机制,实现BeanDefinition的动态注册与灵活扩展,解决依赖和性能问题,本文给大家介绍Spring Boot的注解生效详细步骤解析,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • 一起来学习Java的栈和队列

    一起来学习Java的栈和队列

    这篇文章主要为大家详细介绍了Java的栈和队列,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Java JMH进行基准测试的使用小结

    Java JMH进行基准测试的使用小结

    在 Java 的依赖库中,有个大名鼎鼎的 JMH(Java Microbenchmark Harness),是由 Java虚拟机团队开发的 Java 基准测试工具,下面小编来和大家讲讲它的具体使用吧
    2025-05-05
  • JavaSE中compare、compareTo的区别

    JavaSE中compare、compareTo的区别

    本文主要介绍了JavaSE中compare、compareTo的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • Java结合uniapp实现验证码功能的示例详解

    Java结合uniapp实现验证码功能的示例详解

    UniApp 是一个基于 Vue.js 的跨平台应用开发框架,允许开发者使用统一的代码库来构建多平台应用,这篇文章将给大家介绍Java结合uniapp实现验证码功能,文中通过详细的代码示例讲解的非常详细,需要的朋友可以参考下
    2024-07-07

最新评论