【谷粒商城之分布式锁Redisson-lock】

news2024/11/15 19:39:42

本笔记内容为尚硅谷谷粒商城分布式锁Redisson-lock部分

目录

一、分布式锁与本地锁

二、分布式锁实现

使用 RedisTemplate 操作分布式锁

三、Redisson 完成分布式锁

1、简介

2、导入依赖 

3、配置

4、使用

1.可重入锁

2.公平锁(Fair Lock)

3.读写锁(ReadWriteLock)

4.闭锁(CountDownLatch)

5.信号量(Semaphore)

6.缓存的数据一致性问题


一、分布式锁与本地锁


二、分布式锁实现


使用 RedisTemplate 操作分布式锁

public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
    //1 、占分布式锁。去 redis 占坑
    String uuid = UUID.randomUUID().toString();
    Boolean lock =
    redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
    if(lock){
        System.out.println(" 获取分布式锁成功...");
        // 加锁成功 ... 执行业务
        //2 、设置过期时间,必须和加锁是同步的,原子的
        //redisTemplate.expire("lock",30,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> dataFromDb;
        try{
            dataFromDb = getDataFromDb();
        }finally {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then
            return redis.call('del', KEYS[1]) else return 0 end";
            // 删除锁
            Long lock1 = redisTemplate.execute(new
            DefaultRedisScript<Long>(script, Long.class)
            , Arrays.asList("lock"), uuid);
     }
        // 获取值对比 + 对比成功删除 = 原子操作 lua 脚本解锁
        // String lockValue = redisTemplate.opsForValue().get("lock");
        // if(uuid.equals(lockValue)){
        // // 删除我自己的锁
        // redisTemplate.delete("lock");// 删除锁
        // }
        return dataFromDb;
    }else {
    // 加锁失败 ... 重试。 synchronized ()
    // 休眠 100ms 重试
    System.out.println(" 获取分布式锁失败... 等待重试");
        try{
            Thread.sleep(200);
        }catch (Exception e){
    }
    return getCatalogJsonFromDbWithRedisLock();// 自旋的方式
    }
}

三、Redisson 完成分布式锁


1、简介

Redisson 是架设在 Redis 基础上的一个 Java 驻内存数据网格(In-Memory Data Grid)。充分的利用了 Redis 键值数据库提供的一系列优势, 基于 Java 实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。 使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。 

官方文档:目录 · redisson/redisson Wiki · GitHub

2、导入依赖 

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

3、配置

@Configuration
public class MyRedissonConfig {
    /**
     * config.useSingleServer() 单节点模式
     * @Description 所有对Redisson操作都是通过RedissonClient对象
     * @Param destroyMethod = "shutdown" 销毁方法 服务停止销毁
     * @return org.redisson.api.RedissonClient
     */
    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws IOException {
        //创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.88.130:6379");
        //创建实例
        return Redisson.create(config);
    }
}

4、使用

1.可重入锁

看门狗:锁的自动续期,如果业务时间超长,在业务运行期间自动给锁续上30s。

 @Autowired
    RedissonClient redisson;
 @ResponseBody
    @GetMapping("/hello")
    public String hello() {
    
    //获取一把锁 只要名字一样就是一把锁
        RLock lock = redisson.getLock("MY-LOCK");
     //lock.lock();
    //加锁 阻塞式等待 默认过期时间30s
    //锁自动续期 如果业务超长 运行期间自动续上新的30s 不用担心业务时间长锁自动过期被删除
    //加锁的业务只要运行完成就不会给当前锁续期 即使不手动解锁 默认30s删除
    
    lock.lock(10, TimeUnit.SECONDS);
         //10s自动解锁 自动解锁时间一定要大于业务执行时间
        //问题 lock.lock(10, TimeUnit.SECONDS); 锁时间到了以后不会自动续期
        //1.如果传了锁的超时时间 就发给reids执行脚本 进行占锁 默认超时时间就是我们指定的时间
        //2.如果没传锁的超时时间 就使用30*1000    LockWatchdogTimeout看门狗的默认时间
             //只要占锁成功 就会启动一个定时任务 (重新给锁设定过期时间 新的过期时间就是看门狗的默认时间)
         //定时任务时间 = internalLockLeaseTime(看门狗时间 )/ 3 10s
        //最佳实战  lock.lock(30, TimeUnit.SECONDS); 省掉了整个续期操作 自动解锁给长一点 手动解锁
        try {
            System.out.println("加锁成功 执行业务" + Thread.currentThread().getId());
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
            System.out.println("释放锁" + Thread.currentThread().getId());
        }
        return "hello";
    }

redisson默认就是可重入锁

2.公平锁(Fair Lock)

它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。

=========不设置过期时间 使用默认过期时间=========
RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

=========设置过期时间========
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();

3.读写锁(ReadWriteLock)

基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。

分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

  • 读数据加读锁,写数据加写锁
  • 写锁和写锁之间、写锁和读锁之间是独占锁,读锁之间是共享锁
  • 保证总是能读到最新数据。
	/**
     * 保证一定能读的最新数据 
     * 修改期间 写锁是一个排他锁(互斥锁) 读锁是一个共享锁
     * 写锁没释放 读锁就必须等待
     * 读 + 读 相当于无锁 并发读只会在reids中记录好当前的读锁 都会同时加锁成功
     * 写 + 读 等待写锁释放
     * 写 + 写 阻塞方式
     * 读 + 写 有读锁 写也需要等待
     */
     
    @ResponseBody
    @GetMapping("/read")
    public String read() {

        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.readLock();
        rLock.lock();
        try {
            System.out.println("读锁加锁成功 执行业务" + Thread.currentThread().getId());
            s = redisTemplate.opsForValue().get("writeValue");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();//解锁
            System.out.println("读锁释放锁" + Thread.currentThread().getId());
        }
        return s;
    }

    @ResponseBody
    @GetMapping("/write")
    public String write() {
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.writeLock();
        rLock.lock();
        try {
            System.out.println("写锁加锁成功 执行业务" + Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue", s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();//解锁
            System.out.println("写锁释放锁" + Thread.currentThread().getId());
        }
        return s;
    }

4.闭锁(CountDownLatch)

  /**
     * @Description 放假锁门 闭锁
     */
    @ResponseBody
    @GetMapping("/lockDoor")
    public String lockDoor() throws InterruptedException {
        RCountDownLatch countDownLatch = redisson.getCountDownLatch("door");
        countDownLatch.trySetCount(5);
        countDownLatch.await();//等待闭锁都完成
        return "放假了";
    }

    @ResponseBody
    @GetMapping("/gogogo/{id}")
    public String gogogo(@PathVariable("id") Long id){
        RCountDownLatch countDownLatch = redisson.getCountDownLatch("door");
        countDownLatch.countDown();//计数减一
        return "ok"+id;
    }

5.信号量(Semaphore)

  • 信号量被消耗的时候,信号量的 value 减少
  • 信号量被补充的时候,信号量的 value 增加
  • 当信号量不足时,消耗信号就会阻塞,直到信号量被补充

信号量也可以用作分布式限流

比如当前服务只能承受1w的并发请求,设置1w个信号量,让所有服务先获取信号量 能获取到证明有空闲线程来处理 否则等待

/**
     * @Description 模拟停车 限流 信号量
     */
    @ResponseBody
    @GetMapping("/park")
    public String park() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.acquire();//获取一个信号量 获取一个值 阻塞式获取 一定要获取 
        park.tryAcquire();//有就获取 没有就算了
        return "ok";
    }

    @ResponseBody
    @GetMapping("/go")
    public String go(){
        RSemaphore park = redisson.getSemaphore("park");
        park.release();//释放一个信号量 释放一个值
        return "ok";
    }

6.缓存的数据一致性问题

如果缓存中已经存在数据,但是数据在数据库被修改,那么如何保证缓存的数据与数据库的数据有一致性?

双写模式:

  • 在数据更新时,同时更新缓存和数据库
  • 缺点:先写数据库的一号线程可能由于卡顿等等,导致写缓存在二号线程之后,出现了脏数据的问题。

失效模式:

  • 更新数据时,删除缓存
  • 缺点:假设之前有一个线程修改数据,删除了缓存。紧接着在一号线程写数据库的过程中,二号线程读缓存,没有,读db,获取到的是一号线程修改数据库之前的db,紧接着在二号线程更新缓存之前,一号线程删除了缓存,导致缓存中的数据是二号线程修改之前的数据,产生了脏数据。

解决方法:

 结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498327.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记录-VUE中常用的4种高级方法

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 1. provide/inject provide/inject 是 Vue.js 中用于跨组件传递数据的一种高级技术&#xff0c;它可以将数据注入到一个组件中&#xff0c;然后让它的所有子孙组件都可以访问到这个数据。通常情况下&a…

DC-8通关详解

信息收集 漏洞发现 找个扫描器扫一下 msf试了几个exp都没用 那么手动找找 发现传参nid出存在sql注入 python sqlmap -u "http://192.168.45.144:80/?nid1" --union-cols1 -D d7db -T users -C name,pass --columns --tables --dump 用john爆密码 admin爆了20分钟没…

为Linux系统添加一块新硬盘,并扩展根目录容量

我的原来ubuntu20.04系统装的时候不是LVM格式的分区&#xff0c; 所以先将新硬盘转成LVM&#xff0c;再将原来的系统dd到新硬盘&#xff0c;从新硬盘的分区启动&#xff0c;之后再将原来的分区转成LVM&#xff0c;在融入进来 1&#xff1a;将新硬盘制作成 LVM分区 我的新硬盘…

Python进阶

1.Json数据格式&#xff08;用于不同语言的数据交互&#xff09; 特定格式的字符串 第一种形式的Json &#xff0c;转换成字典 第二种形式的Json&#xff0c;转换成字典列表 1.1 Python的Json转化 dumps 方法 Python转Json loads 方法 Json转Python 1.2 字典转json 需要…

Studio One6Mac中文免费版数字音乐工作站DAW

无论你是第一次接触数字音乐工作站&#xff08;DAW&#xff09;&#xff0c;还是第一次尝试 制作属于自己的音乐&#xff0c;Studio One 都能给你非凡的体验&#xff01;Studio One 6中文版是一款音乐制作软件&#xff0c;通过新的智能模板、直观的拖放工作流、可定制的用户界面…

C# 使用自带的组件PrintPreviewDialog 和 PrintDocument实现打印预览(一)

文章目录 前言相关内容了解打印预览功能1 创建winform工程2 创建要打印的测试数据3 绘制打印页绘制页脚绘制内容PrintPage事件 完整的代码工程小节 前言 有这么个需求&#xff1a;DataTable中有一些数据是需要给显示或直接可以连接打印机进行打印的&#xff0c; 查阅了一下资料…

jenkins共享ci阶段

jenkins共享ci阶段 需求 一个产品包含多个服务&#xff0c;这些服务的流水线都是类似的&#xff1a;制作制品构建并推送镜像构建并推送chart包触发自动部署。我们期望将流水线拆分为ci流水线、cd流水线&#xff0c;ci流水线包含&#xff1a;制作制品构建并推送镜像构建并推送…

蓝牙协议栈之L2CAP使用

目录 前言一、逻辑链路层及自适应协议层&#xff08;L2CAP&#xff09;二、常用的L2CAP术语三、L2CAP的工作模式四、L2CAP通道五、L2CAP帧类型六、Fragmentation/Recombination七、Segmentation/Reassembly八、L2CAP MTU九、Controller to Host Flow Control十、总结 前言 本文…

七个值得推荐的物联网分析平台

物联网分析平台是一种软件工具&#xff0c;可以帮助企业收集和分析来自其广泛的物联网设备的数据。企业可以通过物联网收集大量数据&#xff0c;从消费者支出模式到流量使用&#xff0c;物联网数据分析平台在帮助企业获得竞争优势所需的洞察力方面至关重要。 物联网分析平台已…

「2023 最新」 Github、Gitlab Git 工作流「常用」 git 命令、规范以及操作总结

Git commit 规范 关于提交信息的格式&#xff0c;可以遵循以下的规则&#xff1a; feat: 新特性&#xff0c;添加功能fix: 修改 bugrefactor: 代码重构docs: 文档修改style: 代码格式修改test: 测试用例修改chore: 其他修改, 比如构建流程, 依赖管理 Git 基础知识 当我们通过…

Midjourney生成LOGO指南

目录 常见的Logo 宠物店Logo Graphic Logo​ Lettermark Logo​ Geometric Logo​ Mascot Logo​ 增加风格——艺术运动​ 每个产品都有自己的专属名称&#xff0c;也有自己专属的Logo&#xff0c;经过前几篇的学习&#xff0c;我相信你也有了一定的基础&#xff0c;今天…

TiDB实战篇-PD调度常见问题处理方法

常见的问题 调度产生和执行 常见的调度类型 参数调度的速度 调度典型场景 Leader分布不均匀监控 leader分布算法&#xff0c;每一个leader的size作为总和&#xff0c;还有TiKV的剩余空间等等。 可以手动设置权重。 分布不均衡处理 TiKV节点下线速度慢 TiKV下线速度慢解决方法 …

一文说透IO多路复用select/poll/epoll

概述 如果我们要开发一个高并发的TCP程序。常规的做法是&#xff1a;多进程或者多线程。即&#xff1a;使用其中一个线程或者进程去监听有没有客户端连接上来&#xff0c;一旦有新客户端连接&#xff0c;就新开一个线程(进程)&#xff0c;将其扔到线程&#xff08;或进程&…

C++——类和对象[下]

0.关注博主有更多知识 C知识合集 目录 1.再谈构造函数 1.1初始化列表 1.2初始化列表的初始化顺序 1.3构造函数的隐式类型转换 1.4explicit关键字 2.static成员 2.1static成员变量 2.2static成员函数 3.友元 3.1友元函数 3.2友元类 4.内部类 5.匿名对象 6.编译器…

美颜sdk对于移动端视频直播的优化效果研究报告

随着移动互联网的快速发展&#xff0c;移动端视频直播应用也越来越受到用户的青睐。然而&#xff0c;对于许多用户来说&#xff0c;直播的画质却成为了一个令人头疼的问题。为了解决这个问题&#xff0c;许多直播应用开始引入美颜sdk&#xff0c;以期提升直播画质和用户体验。本…

凌思微-蓝牙框架-流程理解

1.蓝牙SOC芯片主函数流程 int main() { sys_init_app(); ble_init(); dev_manager_init(dev_manager_callback); gap_manager_init(gap_manager_callback); gatt_manager_init(gatt_manager_callback); rtos_init(); ble_task_init(); app_task_init(); vTaskStartScheduler();…

第8章:聚合函数

目录 一、常见的聚合函数 二、GROUP BY 的使用 三、HAVING 的使用&#xff0c;过滤数据 四、SQL底层的执行原理 五、练习 一、常见的聚合函数 1.概念 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。 2.聚合函数的类型 AVG(),SUM(),MAX(),MIN(),COUNT() 3. AV…

【Spring篇】AOP

&#x1f353;系列专栏:Spring系列专栏 &#x1f349;个人主页:个人主页 目录 一、AOP简介 1.什么是AOP? 2.AOP作用 3.AOP核心概念 二、AOP入门案例 1.需求分析 2.思路分析 3.环境准备 4.AOP实现步骤 三、AOP工作流程 1.AOP工作流程 2.AOP核心概念 四、AOP配置管…

Python小姿势 - 1. Python的设计理念

Python的设计理念 Python的设计理念是“优雅”、“明确”、“简单”。 优雅&#xff1a;Python代码风格优美&#xff0c;语法简洁明了&#xff0c;代码可读性高&#xff0c;易于理解和维护。 明确&#xff1a;Python语言规范清晰&#xff0c;标准库丰富&#xff0c;可用于开发各…

第五章 作业(123)【编译原理】

第五章 作业【编译原理】 前言推荐第五章 作业123 随堂练习课前热身04-17随堂练习04-17课前热身04-24 最后 前言 2023-5-3 22:12:46 以下内容源自《【编译原理】》 仅供学习交流使用 推荐 第四章 作业&#xff08;123&#xff09;【编译原理】 第五章 作业 1 1.令文法G为…