1.redis 是否多线程
redis 4 之后慢慢支持多线程,知道6/7 才稳定。
1.1 redis 单线程是什么意思
主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”
。这也是Redis对外提供键值存储服务的主要流程。
Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发生数据给客户端」这个过程是由一个线程(主线程)来完成的
,这也是我们常说 Redis 是单线程的原因。
但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 缓存的一直的脏数据,
先删再更新的解决方案 延时双删
问题
这个删除该休眠多久?·
线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。
第一种方法:
在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,
以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒
即可。
这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
这种同步策略 导致吞吐量降低怎么办?
启动异步线程来进行删除。
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也都删除了。增加误判率。
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) → 取模运行→ 得到坑位
4.2 优缺点
- 优点
高效的插入和查询,内存占用bit空间小。 - 缺点
不能删除数据,删除数据会导致误判率增加。因为hash冲突,一个位置可能有多个元素。
5 缓存问题。
5.1 缓存穿透
- 利用逻辑过期解决缓存击穿问题
思路分析:当用户开始查询redis时,判断是否命中,如果没有命中则直接返回空数据,不查询数据库,而一旦命中后,将value取出,判断value中的过期时间是否满足,如果没有过期,则直接返回redis中的数据,如果过期,则在开启独立线程后直接返回之前的数据,独立线程去重构数据,重构完成后释放互斥锁。
- 利用互斥锁解决缓存击穿问题
核心思路:相较于原来从缓存中查询不到数据后直接查询数据库而言,现在的方案是 ·进行查询之后,如果从缓存没有查询到数据,则进行互斥锁的获取,获取互斥锁后,判断是否获得到了锁
,如果没有获得到,则休眠,过一会再进行尝试,直到获取到锁为止,才能进行查询
如果获取到了锁的线程,再去进行查询,查询后将数据写入redis,再释放锁,返回数据,利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑,防止缓存击穿
6 分布式锁
独占 高可用 防死锁 可重入 不乱抢
6.1 分布式锁 解锁 保证原子性
线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了
,那么此时线程2进来
,但是线程1他会接着往后执行,当他卡顿结束后
,他直接就会执行删除锁那行代码
,相当于条件判断并没有起到作用
,这就是删锁时的原子性问题,之所以有这个问题,是因为线程1的拿锁,比锁,删锁,实际上并不是原子性的
,我们要防止刚才的情况发生,
6.2 可重入锁的设计
6.3 redlock 多实例分布式锁
n/2+1 <=获得锁的实例个数 时,获得锁成功。
3台 2台成功。
加锁成功条件
- 客户端 超过半数 (>= n/2+1) 的redis实例获得了锁。
2.客户端获得锁的总消耗时间没有超过锁的有效时间。
- 容错公式
N=x*2+1 x 假设能允许宕机的机器数,N最终部署的机器数。
// 释放锁代码
finally {
if(redissonLock.isLocked() &&
redissonLock.isHeldByCurrentThread())
{
redissonLock.unlock();
}
}
6.4 trylock(time )可重试
尝试获取锁时间
使用消息订阅 和信号量机制,避免无限等待。等lock锁的线程释放了,再来尝试获取锁。
6.5 总结
Redisson分布式锁原理:
- 可重入:利用hash结构记录线程id和重入次数
- 可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
- 超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间
7. 缓存淘汰 删除策略
7.1 内存
-
redis 默认内存多少可用
- 默认0
- 64位操作i系统下,不做限制。32位最多3GB内存。
- 在64位下,maxmemory 设置位0,表示不限制redis 内存的使用。
- 生产上,配置最大物理内存的3/4
-
修改redis 内存设置
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