redis- 多线程 布隆 锁 淘汰

Redis在4.0后引入了多线程处理内存释放,6.0版本开始在网络IO上使用多线程。文章讨论了Redis的单线程模型以及如何处理删除操作以避免主线程阻塞。双写一致性问题中提出了先删除缓存再更新数据库可能导致的问题及解决方案。此外,文章还涉及了Bloom过滤器的工作原理和优缺点,以及缓存穿透的解决策略,如逻辑过期和互斥锁。最后,提到了Redis的内存管理和淘汰策略,包括惰性删除和定期删除。
摘要由CSDN通过智能技术生成

1.redis 是否多线程

redis 4 之后慢慢支持多线程,知道6/7 才稳定。

1.1 redis 单线程是什么意思

主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。

Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发生数据给客户端」这个过程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因。

2
但Redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。

Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的;

Redis 在 4.0 版本之后 ,·新增了一个新的后台线程用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。

redis 6 后的多线程

Redis 6.0 对于网络请求采用多线程来处理,其实就是在 IO 就绪之后使用多线程提升读写解析数据的效率但是对于读写命令,Redis 仍然使用单线程来处理.

2. 双写一致性更新策略

2.1 先删除缓存,再更新数据库

如果数据库更新失败或超时或返回不及时,导致B线程请求访问缓存时发现redis里面没数据,缓存缺失,B再去读取mysql时,从数据库中读取到旧值,还写回redis,导致A白干了,o(╥﹏╥)o

(1)请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql…A还么有彻底更新完mysql,还没commit
(2)请求B开工查询,查询redis发现缓存不存在(被A从redis中删除了)
(3)请求B继续,去数据库查询得到了mysql中的旧值(A还没有更新完)
(4)请求B将旧值写回redis缓存
(5)请求A将新值写入mysql数据库
导致redis 缓存的一直的脏数据,

先删再更新的解决方案 延时双删

2
2
问题

  • 这个删除该休眠多久?·

线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。

第一种方法:

在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,

以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。

这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

  • 这种同步策略 导致吞吐量降低怎么办?

启动异步线程来进行删除。
3

2.2 先更新再删除

3.canal 落地

3.1 in 查询 不按照给的id 排序

select xxx from user where id in (5,1);
查询出来的结果是id=1在前,id=5在后。
需要根据给定的id顺序来返回顺序 给定 5,1 返回 5,1 的顺序

order by field (id,5,1);

MySQL中的排序ORDER BY 除了可以用ASC和DESC,还可以自定义字符串/数字来实现排序。

格式如下:

SELECT * FROM table ORDER BY FIELD(status,1,2,0);

这样子写的话,返回的结果集是按照字段status的1、2、0进行排序的,当然,也可以对字符串进行排序。

4.bloomfilter

由初始都为0的bit数组和多个hash函数构成,用来判断快判断集合中是否存在某个元素。
判断具体数据是否在一个大的集合中。

  • 重点
    如果存在,该元素不一定存在。如果不存在,那么该元素一定不存在。
    最好不要删除元素,a、b、c都在一个位置,把b删了,a、c也都删除了。增加误判率。
    2

4.1 bloomfilter 原理

添加key时

使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,

每个hash函数都会得到一个不同的位置,将这几个位置都置1就完成了add操作。

查询key时

只要有其中一位是零就表示这个key不存在,但如果都是1,则不一定存在对应的key。
·向布隆过滤器查询某个key是否存在时,先把这个 key 通过相同的多个 hash 函数进行运算,查看对应的位置是否都为 1,只要有一个位为零,那么说明布隆过滤器中这个 key 不存在;

如果这几个位置全都是 1,那么说明极有可能存在;

因为这些位置的 1 可能是因为其他的 key 存在导致的,也就是前面说过的hash冲突。。。。。


当我们向布隆过滤器中添加数据时,为了尽量地址不冲突,·会使用多个 hash 函数对 key 进行运算,算得一个下标索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。

例如,我们添加一个字符串wmyskxz,对字符串进行多次hash(key) → 取模运行→ 得到坑位
2

4.2 优缺点

  • 优点
    高效的插入和查询,内存占用bit空间小。
  • 缺点
    不能删除数据,删除数据会导致误判率增加。因为hash冲突,一个位置可能有多个元素。

5 缓存问题。

5.1 缓存穿透

  • 利用逻辑过期解决缓存击穿问题

思路分析:当用户开始查询redis时,判断是否命中,如果没有命中则直接返回空数据,不查询数据库,而一旦命中后,将value取出,判断value中的过期时间是否满足,如果没有过期,则直接返回redis中的数据,如果过期,则在开启独立线程后直接返回之前的数据,独立线程去重构数据,重构完成后释放互斥锁。

  • 利用互斥锁解决缓存击穿问题

核心思路:相较于原来从缓存中查询不到数据后直接查询数据库而言,现在的方案是 ·进行查询之后,如果从缓存没有查询到数据,则进行互斥锁的获取,获取互斥锁后,判断是否获得到了锁,如果没有获得到,则休眠,过一会再进行尝试,直到获取到锁为止,才能进行查询

如果获取到了锁的线程,再去进行查询,查询后将数据写入redis,再释放锁,返回数据,利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑,防止缓存击穿

6 分布式锁

独占 高可用 防死锁 可重入 不乱抢

6.1 分布式锁 解锁 保证原子性

线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了,那么此时线程2进来但是线程1他会接着往后执行,当他卡顿结束后他直接就会执行删除锁那行代码相当于条件判断并没有起到作用这就是删锁时的原子性问题,之所以有这个问题,是因为线程1的拿锁,比锁,删锁,实际上并不是原子性的,我们要防止刚才的情况发生,

2

6.2 可重入锁的设计

6.3 redlock 多实例分布式锁

n/2+1 <=获得锁的实例个数 时,获得锁成功。
3台 2台成功。

加锁成功条件

  1. 客户端 超过半数 (>= n/2+1) 的redis实例获得了锁。
    2.客户端获得锁的总消耗时间没有超过锁的有效时间。
  • 容错公式
    N=x*2+1 x 假设能允许宕机的机器数,N最终部署的机器数。
// 释放锁代码
finally {
            if(redissonLock.isLocked() && 
            redissonLock.isHeldByCurrentThread())
            {
                redissonLock.unlock();
            }
        }

6.4 trylock(time )可重试

尝试获取锁时间
使用消息订阅 和信号量机制,避免无限等待。等lock锁的线程释放了,再来尝试获取锁。
2

6.5 总结

Redisson分布式锁原理:

  • 可重入:利用hash结构记录线程id和重入次数
  • 可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
  • 超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间

2

7. 缓存淘汰 删除策略

7.1 内存

  • redis 默认内存多少可用

    • 默认0
    • 64位操作i系统下,不做限制。32位最多3GB内存。
    • 在64位下,maxmemory 设置位0,表示不限制redis 内存的使用。
    • 生产上,配置最大物理内存的3/4
  • 修改redis 内存设置

    • 2
    • 3

7.2 删除策略

一个key 过期了,采用的删除策略。

  • 立即删除
  • 惰性删除
    • 开启惰性淘汰,lazyfree-lazy-eviction=yes
  • 定期删除
    • 顾明思议是通过一个定时任务周期性的抽样部分过期的 key,然后执行删除。执行周期有两种:
    • Redis 服务初始化函数 initServer () 中设置定时任务,按照 server.hz 的频率来执行过期 key 清理,模式为 SLOW
    • Redis 的每个事件循环前会调用 beforeSleep () 函数,执行过期 key 清理,模式为FAST

7.3 缓存淘汰策略

noeviction 不淘汰任何key,内存满的时候,不写入任何key。 默认策略。

allkeys-lru 对所有的key,使用lru淘汰算法。
allkeys-random 对所有的key 随机淘汰。
allkeys-lfu 对所有的key,使用lfu算法。

volatile-ttl
volatile-lru
volatile-lfu
vloatile-random

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值