引子: 解决缓存击穿问题

synchronized (this){代码块}

public synchronized Map<String,List<Catelog2Vo>> getCatalog]sonFromDb()

:SpringBoot所有的组件在容器中都是单例的。

优点:速度快

所以这样只能有一个人拿到锁进入逻辑, 但是也是在单体服务下可行,在分布条件下,几个服务就生成几把锁(应该问题不大)

测试1: 有3个人进入拿到了锁

原因:  拿到锁的那个线程确认缓存没有,去查数据库,查询后释放锁 ,刚释放还没来得及往缓存中存数据, 另一个线程就拿到锁再进行操作

解决方案:  把 (往缓存中存数据) 放到锁中    [稍微影响效率]

这里推荐下阿里的jetcache 全部都封装好了

redis分布式锁

分布式中,即使多个服务,也只让一个人去查查数据

原理: redis  set hello world NX

先看缓冲中有没有, 没有的话就占分布式锁,执行下面的逻辑

可能遇到问题

1.拿到锁之后,执行完逻辑,在释放锁之前突然断电,无法释放锁导致其他服务无法获取,造成死锁  (解决办法: 锁设置过期时间:30S)

还有问题: 若是刚加锁,还没来得及设置过期时间,就闪断

解决办法:  原子操作

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",30, TimeUnit.SECONDS);

删锁的时候遇到问题

1.假设业务时间过长,锁自动过期了  就会有其他线程进来

2.自己的锁过期了,删掉了其他人正在用的锁

解决办法:可以加V值改为UUID,删锁执行判断UUID进行删除

String uuid = UUID.randomUUID().toString();

    新问题(无原子性): 因为数据传输慢的原因,去redis判断uuid是否是自己,判断后,在然后往回传结果true的路上  ,K过期了,其他人占了锁 , 此时,删除的锁,也是别人的锁

    解决办法: 删锁的原子性,利用lua脚本 

                String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call('del',KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
// 删除锁
redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("LOCK_KEY_CATALOG_JSON"), uuid);
Arrays.asList("LOCK_KEY_CATALOG_JSON") ==  uid  就删除锁

加锁保证原子性,解锁保证原子性

问题: 锁的自动续期问题,  [把时间放长一点]

Redisson(单机redis)

导入依赖

        <dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.3</version>
</dependency>

配置

@Configuration
public class MyRedissonConfig { /**
* 注入客户端实例对象
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}")String port) throws IOException {
// 1.创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);// 单节点模式
// config.useSingleServer().setAddress("rediss://" + host + ":" + port);// 使用安全连接
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");// 集群模式
// 2.创建redisson客户端实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}

测试

RedissonClient redissonClient

实现分布式锁

锁的自动续期+30S (看门狗 : 10秒续一次)   ,一旦设置过期时间,看门狗会失效

可重入锁(抢占一把锁)

    public Map<String, List<Catalog2VO>> getCatalogJsonFromDBWithRedissonLock() {
// 1.抢占分布式锁,同时设置过期时间
RLock lock = redisson.getLock("LOCK_KEY_CATALOG_JSON");
lock.lock(30, TimeUnit.SECONDS);
try {
// 2.查询DB
Map<String, List<Catalog2VO>> result = getCatalogJsonFromDB();
return result;
} finally {
// 3.释放锁
lock.unlock();
}
}
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}

公平锁(先到先得)

//所有请求线程会在一个队列中排队,当某个线程出现宕机时,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();

读写锁(写锁控制读锁)

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock(); //抽象lock(代表读写锁)

闭锁

等待全部成才能加锁

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(5); //等5个走完才结束
latch.await(); // 在其他线程或其他JVM里()
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();

信号量

做限流

提前在redis中存入信号量{"semaphore":"10"}

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//获取信号量-1,获取不到就一直等待
semaphore.tryAcquire(23, TimeUnit.SECONDS);  /.尝试等待23秒,

//其他线程 semaphore.release(); //释放信号量+1

可过期性信号量

红锁(大多数锁上就行)

注意锁的粒度

办法:  要么加锁牺牲效率,    要么允许暂时的不一致,有最终一致性就行     丨(加了缓存过期时间)

问题: 脏数据问题,  2号慢  , 3号读到了脏数据,也更新了脏数据     丨 (加了缓存过期时间)

经常修改的数据,直接读数据库就行

本地锁 & 分布式锁的更多相关文章

  1. 图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析

    图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析 大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步(超链):图数据 ...

  2. 本地锁、redis分布式锁、zk分布式锁

    本地锁.redis分布式锁.zk分布式锁 https://www.cnblogs.com/yjq-code/p/dotnetlock.html 为什么要用锁? 大型站点在高并发的情况下,为了保持数据最 ...

  3. SpringBoot--防止重复提交(锁机制---本地锁、分布式锁)

    防止重复提交,主要是使用锁的形式来处理,如果是单机部署,可以使用本地缓存锁(Guava)即可,如果是分布式部署,则需要使用分布式锁(可以使用zk分布式锁或者redis分布式锁),本文的分布式锁以red ...

  4. .net下 本地锁、redis分布式锁、zk分布式锁的实现

    为什么要用锁? 大型站点在高并发的情况下,为了保持数据最终一致性就需要用到技术方案来支持.比如:分布式锁.分布式事务.有时候我们在为了保证某一个方法每次只能被一个调用者使用的时候,这时候我们也可以锁来 ...

  5. 分布式改造剧集2---DIY分布式锁

    前言: ​ 好了,终于又开始播放分布式改造剧集了.前面一集中(http://www.cnblogs.com/Kidezyq/p/8748961.html)我们DIY了一个Hessian转发实现,最后我 ...

  6. Redisson分布式锁实现

    转: Redisson分布式锁实现 2018年09月07日 15:30:32 校长我错了 阅读数:3303   转:分布式锁和Redisson实现 概述 分布式系统有一个著名的理论CAP,指在一个分布 ...

  7. 【转】Redis学习笔记(四)如何用Redis实现分布式锁(1)—— 单机版

    原文地址:http://bridgeforyou.cn/2018/09/01/Redis-Dsitributed-Lock-1/ 为什么要使用分布式锁 这个问题,可以分为两个问题来回答: 为什么要使用 ...

  8. 分布式锁和Redisson实现

    http://thoreauz.com/2017/08/20/language/java/%E5%9F%BA%E7%A1%80/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81 ...

  9. redission 分布式锁

    https://my.oschina.net/haogrgr/blog/469439   分布式锁和Redisson实现 Aug 20, 2017 CONTENTS 概述 分布式锁特性 Redis实现 ...

  10. [转]分布式锁-RedisLockRegistry源码分析

    前言 官网的英文介绍大概如下: Starting with version 4.0, the RedisLockRegistry is available. Certain components (f ...

随机推荐

  1. GPT大语言模型引爆强化学习与语言生成模型的热潮、带你了解RLHF。

    GPT大语言模型引爆强化学习与语言生成模型的热潮.带你了解RLHF. 随着 ChatGPT 的爆火,强化学习(Reinforcement Learning)和语言生成模型(Language Model ...

  2. 2.3 CE修改器:浮点数扫描

    本关需要使用 Cheat Engine 工具对浮点数进行扫描,完成修改任务.浮点数是一种带有小数点的数值,通过"浮点数"扫描方式进行修改.本关中,健康值为单精度浮点数,弹药值为双精 ...

  3. JavaScript快速入门(一)

    JavaScript快速入门(二) 语句 只需简单地把各条语句放在不同的行上就可以分隔它们 var a = 1 var b = 2 如果想把多条语句放在同一行上,就需要用分号隔开 var a = 1; ...

  4. git~issue在github/gitlab中的使用

    本文档适用于github和gitlab issue介绍 GitHub 中的 issue 功能是一种用于跟踪项目中任务.缺陷.功能请求和讨论的工具.通过 issue,项目成员可以提出问题.报告 bug. ...

  5. Golang中make和new的区别

    1. 相同点 都是内建函数,都是在堆上分配内存,都需要传递类型参数 2. 不同点 传递的参数不一样,new函数只接收一个参数,make函数可以接收一个以上的参数 package main import ...

  6. 零基础入门学习Java课堂笔记 ——day02

    流程控制 1.Scanner对象 来源:java.util.Scanner 我们可以通过Scanner类来获取用户的输入输出 用户交互Scanner 通过new关键词创建Scanner对象 Scann ...

  7. HTTP 400 Bad Request 错误。

  8. NC20313 [SDOI2008]仪仗队

    题目链接 题目 题目描述 作为体育委员,C君负责这次运动会仪仗队的训练. 仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队 ...

  9. lsattr命令

    lsattr命令 lsattr命令用于显示文件的属性. 语法 lsattr [-RVadlv] [file | folder] 参数 -a: 显示所有文件和目录,包括以.为名称开头字符的额外内建,即现 ...

  10. ubuntu16.04 ssh启用root连接

    安装好ubuntu16.04 server版默认是不允许客户端ssh工具连接root的. 启用方法如下: 1.设置root密码 dylan@ubuntu:~$ sudo passwd root [su ...