Redis-分布式锁笔记

1. 非关系型Nosql数据库-

  1. 单线程

  2. 使用多路i/o,非阻塞式

2. 设置过期时间

EXPIRE 将key的生存时间设置为ttl秒
PEXPIRE 将key的生成时间设置为ttl毫秒

3. 优点-缺点

  • 优点

    1. 读写性能强
    2. 支持持久化
    3. 支持事物
    4. 数据结构多
    5. 支持主从复制
  • 缺点

    1. 容量受内存影响
    2. 不会自动恢复,需要重启
    3. 不支持在线扩容

4. 为什么使用

  1. 高性能 直接从内存中获取数据
  2. 高并发 能承受的请求数远远大于数据库的

5. 数据类型

  1. 字符串 string k-v 一个k对应一个value

    • 场景: 存储验证码, 存储序列化后的对象,存储json
  2. Hash 散列表 是一个键值对 k-filed-v 适合存储对象一个k对饮一个map

    • 购物车数据,每个用户一个组, 每个商品一个key
    • 无法存储大量对象
  3. list 列表 一个k对应多个value,可重复,有序

    • 秒杀中使用, 有数据限制
  4. set 集合 一个k对应多个value,不可重复,无序

    • 共同好友, 粉丝等
  5. SortedSet 有序集合 有序,不能重复

    • 搜索引擎搜索的数据

6. 持久化

  1. rdb 默认的,按照一定时间或者操作后才能持久

    • 二进制快照, 保存当前内存数据本身
    • 新的快照覆盖之前的快照
  2. aof 将每次写命令都记录到日志文件,aof要单独开启

    • 保存的写命令
    • 一直写会导致文件越来越大, aof拥有压缩功能, 即文件过大会合并命令, 只保存最终信息

两个都配置,优先加载aof

7. 架构模式

  1. 单机,启用及调用

  2. 主从复制: 将读库的压力转交给从库

  3. 哨兵模式:

    • 监控: sentinel会一直监控你的主服务器和从服务器是否正常运行
    • 提醒: 当某个服务器出问题, sentinel会通过api发送通知
    • 自故障迁移: 当一个主不能工作,sentinel会将一个从库投票为一个主库
  4. 集群proxy

    • redis-cluster

8. redis除了缓存还可以做什么

img

8.1 redis作为队列-异步

rpush 把数据插入列表list尾部,----发送消息-生产者

rpop 移除第一个元素,并返回----接收消息-消费者

这样就形成了一个队列

同步,发一个接一个

异步,一直发,然后慢慢接收

8.2 缓存淘汰策略

  • 淘汰访问量最少的

  • 淘汰过期时间短的

  • 随机淘汰

  • 先进先出淘汰

8.3 缓存预热

  • 知道那些是热点数据, 提前将数据塞入缓存中

  • 开发避免差集(在某个时间段某些数据访问频率高)

    • 防止击穿, 配合加锁的方式

9. 缓存穿透

查询一个不存在的数据,缓存不命中,将去查询数据库,数据库也没有,那么每次查询不存在的数据都会去数据库中查询,导致缓存失去意义

解决:将null结果也进行缓存,并加入一个短暂的过期时间

10. 缓存雪崩

缓存中非常多的数据,在同一时间失效,那么所有请求都会转发到数据库,数据库压力过大,导致雪崩

解决:在原有的失效时间上加上一个随机的过期时间1-5分钟,这样就不会发生集体失效的情况

11. 缓存击穿

如有有个热点的数据,高并发的访问,如果大量的请求过来,他刚好失效,那么所有的查询,都会落到数据库

解决:给当前数据加锁,有一个请求查询到数据后,才让其他的请求查询这个数据

加锁:

  1. 在查询数据库的时候进行加锁,如果第一个请求查询到数据后,需要在这里在进行查询缓存一次-

  2. 在微服务,每一个业务不止一个服务,本地锁只能锁住当前服务的请求,需要添加分布式锁,锁住所有的服务

  3. 保证查数据库和放入缓存在同一把锁下,不然后导致第一个请求查到数据,还没有放入缓存的时候,第二个请求就开始查询缓存中的数据,导致数据库查询次数增加

12. 主从原理master-salve

  1. 全量同步,刚开启主从的时候,将master的所有数据同步到Slave
  2. 增量同步,主服务器发生的写操作,同步到从服务器
  • 主要是读写分离, 主库写, 从库读
  • 如果redis从节点断开, 那么同步机制会判断数据差值, 如果差值过大则全量同步一次, 如果差值小则使用增量同步
  • 缺点: 如果主节点挂了, 则redis就不能使用了

13. 哨兵sentinel

当有一个主服务器宕机后, 哨兵会投票出一个从服务器当做主服务器,

如果主服务器恢复了,那么他就会变成一个从服务器

  • 保证了主节点挂掉, redis还可以继续使用
  • 缺点: 还是有写入瓶颈

14. 集群机制redis cluster

每个服务器都存储一部分的数据,降低了单服务器的压力

(1)自动将数据进行分片,每个master上放一部分数据
(2)提供内置的高可用支持,部分master不可用时,还是可以继续工作的

  • 支持1684个哈希槽
  • 缺点: 资源占用高, 成本高

能解决什么问题

  • 大key: 拆分数据,防止资源倾斜
  • 热key: 多级缓存
  • 大热key: 通过jd-het工具发现它, 或者提前预判, (数据打散,多级缓存)

15. 脑裂问题1主3从

  1. 当主服务器1和从服务器的通讯断开,

  2. 哨兵与slave通讯正常,那么哨兵就会投票一个从服务器当做主服务器2

  3. 那么salve1连接master1,salve2连接master2 那么数据就不一致了

  4. 连接成功

16. 分布式锁 set k v nx ex (避免重复, 过期时间)

  1. 所有分布式服务去占一把锁

  2. 所有服务,用一个key在redis中存放一个值,要求没有这个值才能存放

    • redis实现:set key value nx

    • java实现:ops.setIfAbsent(key,value)

      如果返回值是true锁门占锁成功,如果是false说明占锁不成功

      占锁成功才执行查询数据库操作,执行完业务后就释放锁(删除redis中的key)

      占锁失败,重试----休眠100ms后再次调用这个方法

  3. 注意:

    • 如果在删除锁之前服务器宕机,其他服务就不能占锁,那么就会形成死锁,所以需要设置过期时间,

    • 而且需要设置锁和设置过期时间需要在使用原子命令执行,防止设置锁后就宕机,还没有设置过期时间,一样会形成死锁:

      • *redis实现:set key value ex 300 nx

      • **java实现:ops.setIfAbsent(key,value,timeout过期时间,timeunit过期时间单位) **

    • 业务超时,那么锁过期了,当前请求操作还没有完成,那么其他请求就会再次占到锁

      • 在占锁的时候,指定value是唯一的uuid,然后在删锁的时候获取锁的值,如果和你的uuid一致,就可以删锁,防止删除了别人的锁

      • 获取值和删除需要是原子操作,防止你获取到值,还没有删锁的时候,锁过期了

        需要使用redis脚本解锁 (加锁保证原子性,解锁也要保证原子性)

        //保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        /*redis执行脚本,第一个参数:脚本和返回值类型
        第二个参数:key的数组,将key存入数组,传入参数
        第三个参数:value值
        效果:先查询到数据,然后判断数据和你的uuid是否一致,一致则删除数据
        */
        redisTemplate.execute(new DefaultRedisScript<Longr>(script,Long.class),Arrays.asList("lock"),uuid)
        
      • 锁的自动续期

      • 删锁,使用try-finally,不论业务执行怎么样,最终都执行解锁

17. 分布式锁框架Redisson

  1. redisson

    引入依赖

    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.16.0</version>
    </dependency>
    
  2. 配置reidsson

    https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

  3. 可重入锁

    方法A调用方法B,B方法要和A方法持有同一把锁,B看到A加了锁,直接拿来用就可以了

    设置所有锁都是可重入锁,避免死锁问题

  4. 自旋锁

    没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁,这种锁叫做自旋锁,它不用将线程阻塞起来(NON-BLOCKING)

    finally,再次调用当前方法,实现自旋

  5. redisson实现juc中的锁,可以直接使用本地锁,实现redisson的锁功能,不需要重新学习

17.1 使用lock方法

//1.获取一把锁,只要锁的名字一样就是同一把锁
Rlock lock = redisson.getLock("lock");
//2.加锁,
lock.lock();//阻塞式等待,如果拿到锁,就执行下面的程序
//锁的时间会自动续期,如果业务超长,运行期间会自动上新30s,不用担心业务时间长,锁会自动过期
//加锁的业务只要运行完成,就不会给当前锁自动续期,即使不手动解锁,锁也会在30s后自动删除
try{
 //执行业务代码
}finally{
 //解锁,不论业务代码执行怎么样,都进行解锁
 lock.unlock();
}
//只要占锁成功,就会启动一个定时任务,重新给锁设置过期时间,新的过期时间就是看门狗的默认时间
//internallLockLeaseTime【看门狗时间】/3
//每隔10秒就给锁上新30秒的过期时间



//最佳使用,不自动续期 设置30秒就可以,如果30秒都没有执行完任务,那么就等用户重新发送请求吧
lock.lock(30,TimeUnit.SECONDS);
//省掉了续期的过程,与上面lock.lock()不同,
## 使用tryLock
boolean flag=lock.tryLock(最多等待时间,上锁后多少时间解锁,时间单位);
//判断这个值是什么就可以了

17.2 公平锁 fair lock

Rlock fairLock = redisson.getFairLock("lock");
//使用方法和lock一致
fairLock.lock();

17.3 读写锁**

读写锁还是同一把锁,使用位置不同

  1. 改数据加写锁

    //获取一把锁,名字相同就是同一把锁
    RReadWriteLock lock == redisson.getReadWriteLock("lock");
    //获取写锁
    RLock wLock = lock.writeLock();
    //加锁
    wLock.lock();
    //解锁
    wLock.unlock();
    
  2. 读数据加读锁

    //获取一把锁,名字相同就是同一把锁
    RReadWriteLock lock == redisson.getReadWriteLock("lock");
    //获取写锁
    RLock rLock = lock.readLock();
    //加锁
    rLock.lock();
    //解锁
    rLock.unlock();
    

写完成写锁释放,才能进行读–保证一定能读到最新数据

写锁是一个排他锁(互斥锁,独享),写锁是一个共享锁

读+读:相当于无锁–并发锁,只会在redis中记录,所有的读都会加锁成功

写+读:等待写锁释放

写+写:阻塞方式

读+写:有读锁为释放,写也需要等待

只要有写的存在,都必须等待

17.4 闭锁

其他的锁都完成了,当前锁才释放

RContDownLatch door = redisson.getContDownLatch("door");//获取闭锁door.trySetCount(5);//闭锁有5个,都需要完成door.await();//等待闭锁都完成
RContDownLatch door = redisson.getContDownLatch("door");//获取闭锁door.countDown();//闭锁计数减一//当前方法执行5次,上面的锁才释放

17.5 信号量 Semaphore

模拟:车库停车3个车位,在redis中park存入数字3

RSemaphore park = redisson.getSemaphore("park");//获取锁//park.acquire();//获取一个信号量,获取一个值,占一个车位,会一直等待boolean b =park.tryAcquire();//获取到值,返回true,否则返回false
RSemaphore park = redisson.getSemaphore("park");//获取锁park.release();//释放一个信号量,释放一个车位

可以做限流,设置100万访问量, 当超出就必须要等待,等待其他的请求释放

18.缓存一致性

  1. 经常读的数据,不加入缓存,直接读数据库

在这里插入图片描述
在这里插入图片描述

  • redis强一致性
    • 加锁
    • 事物
    • MQ操作

19. redis单线程,多线程

  • 单线程, 多线程6.0版本以后
  • 多线程使用在命令的前置处理, 最终执行还是

20.redis扩容机制

  • 类似hashMap的扩容机制
  • 底层有两层链表, 重写进行hash运算, 数据重拍序
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值