Redis分布式锁学习总结

简介: Redis分布式锁学习总结

⭐️ 前言

想必大家都有过并发编程的经验,在一个单体应用中,可以通过java提供的各种锁机制来控制多线程对于单体应用中同一资源的并发访问;那么在分布式场景下,想要控制多个应用对于同一外部资源的并发访问,就要用到分布式锁。分布式锁不但要保证单个应用程序内部不会产生并发问题,同时也要保证多个应用程序之间不能产生并发问题。分布式锁有很多实现方式,比如使用redis、zookeeper或关系型数据库的唯一索引,也有现成的分布式锁架构,比如redisson、curator等。本文利用spring-data-redis手动实现一个简易的redis分布式锁,剖析redis分布式锁的原理。

⭐️ redis分布式锁实现原理浅析

实现redis分布式锁最简单的想法就是各个应用利用setnx命令向redis中争抢设置key的机会,但我们还应该考虑得更加周全。

死锁

如果成功加锁的应用程序在未释放锁之前就异常终止了,那么这个锁永远无法释放,其他应用程序则永远也无法获取到锁,为了解决这个问题,需要给代表锁的redis的key加上过期时间。

原子性

很多时候我们需要保证多个操作具有原子性,

例如,加锁和设置过期时间

若它们无法保证原子性,则应用程序在刚刚成功加锁后就异常终止了,则仍然会出现上面的死锁问题。

原子性可以通过让redis执行Lua脚本来保证,eval命令可以原子性的执行Lua脚本(Lua的多个步骤会被原子性的执行),在redis内置的Lua脚本中有一个redis对象,可以通过redis.call()方法执行各种redis命令。

防误删

根据上面的讨论,我们需要给redis锁加上过期时间,当业务执行完毕之前锁就过期了,这种情况下,其他线程就会成功加锁,那么之前的程序运行到解锁逻辑时,就会造成对后面线程获得锁的误删。

误删可以通过给每一个线程设置一个id,我们可以叫它 线程标识码

可重入性

另外,在应用程序中免不了方法的彼此调用,若锁无法重入,则业务根本无法执行,比如A方法需要加锁,它在执行过程中会调用B方法,B方法也需要加锁,若锁不具有可重入性,则程序根本无法运行。

可重入性可以通过hash数据结构来实现

key: 代表锁的key

field:线程的唯一标识id,即上文说的 线程标识码

value:重入次数

自动续期

若应用程序执行需要的时间大于锁的过期时间,则锁过期后,应用程序便不再受锁保护,这样就会导致并发问题。所以分布式锁还要具有自动续期的功能,即只要应用程序业务没有执行完毕,则锁需要不断的自动延长过期时间。

自动续期可以通过Timer定时任务配合Lua脚本来实现。

本文参考了上硅谷课程《【尚硅谷】分布式锁全家桶丨一套搞定Redis/Zookeeper/MySQL实现分布式锁》,B站上就有,更详细的内容读者可以去看这门课程。

这里贴出关键代码,完整代码我已经上传到了gitee。欢迎围观啊!!点这里哟

⭐️ 加锁主要代码

/**
     * 加锁
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        // redis加锁的lua脚本
        String lockStr="if redis.call('exists', KEYS[1])==0 or redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                "then " +
                "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "redis.call('expire', KEYS[1], ARGV[2]) " +
                "return 1 " +
                "end " +
                "return 0";
        // 加锁
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(lockStr, Boolean.class), Arrays.asList(this.lockName), this.uuid, String.valueOf(this.expire))){
            Thread.sleep(50);
        }
        // 自动续期
        this.autoExpire();
        return true;
    }

⭐️ 解锁主要代码

/**
     * 解锁
     */
    @Override
    public void unlock() {
        // 解锁lua脚本
        String unlockStr = "if redis.call('hexists', KEYS[1], ARGV[1])==0 " +
                "then " +
                "return nil " +
                "end " +
                "if redis.call('hincrby', KEYS[1], ARGV[1], -1)==0 " +
                "then " +
                "return redis.call('del', KEYS[1]) " +
                "end " +
                "return 0";
        // 解锁
        Long del = this.redisTemplate.execute(new DefaultRedisScript<>(unlockStr, Long.class), Arrays.asList(this.lockName), this.uuid);
        if (del == null){
            throw new IllegalMonitorStateException("lock wrong");
        }
        if (del == 1L){
            System.out.println("lock deleted");
        }
    }

⭐️ 自动续期主要代码

/**
     * 自动续期
     */
    private void autoExpire(){
        String expireStr="if redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                         "then return redis.call('expire', KEYS[1], ARGV[2]) " +
                         "end " +
                         "return 0";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(new DefaultRedisScript<>(expireStr, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
                    autoExpire();
                }
            }
        }, this.expire * 1000 /3);
    }

⭐️ 运行架构

运行架构比较简单,可以启动两个应用实例,利用nginx做负载均衡。

⭐️ 压力测试

压力测试可以使用jmeter,其设置如下图所示

笔者水平有限,若有不对的地方欢迎评论指正!

相关文章
|
8月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
557 2
|
8月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
618 6
|
9月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
6月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
757 6
|
7月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
647 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
7月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
缓存 NoSQL Java
为什么分布式一定要有redis?
1、为什么使用redis 分析:博主觉得在项目中使用redis,主要是从两个角度去考虑:性能和并发。当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookpeer等)代替,并不是非要使用redis。
1552 0
|
机器学习/深度学习 缓存 NoSQL
|
11月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
6月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
672 25