Redis分布式锁学习总结

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 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,其设置如下图所示

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
19天前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
53 5
|
23天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
43 8
|
1月前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
57 16
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
40 5
|
2月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
51 1
|
NoSQL Redis 数据库
用redis实现分布式锁时容易踩的5个坑
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 近有不少小伙伴投入短视频赛道,也出现不少第三方数据商,为大家提供抖音爬虫数据。 小伙伴们有没有好奇过,这些数据是如何获取的,普通技术小白能否也拥有自己的抖音爬虫呢? 本文会全面解密抖音爬虫的幕后原理,不需要任何编程知识,还请耐心阅读。
用redis实现分布式锁时容易踩的5个坑
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 canal 缓存
|
NoSQL PHP Redis
redis实现分布式锁
redis实现分布式锁
177 0
redis实现分布式锁