redis问题总结
缓存问题:
1.缓存穿透
查询一个不存在的数据,MySQL中也查询不到而且也写不进缓存,就导致每次请求都查询数据库
解决方案
- 缓存空数据,查询返回数据为空时把空结果缓存:缺点是内存开销变大
- 布隆过滤器:通过哈希检测一个元素是否存在于集合中:在缓存预热时同时预热过滤器,将传进来的key通过相应的hash将数据存入bitmap中,在查询时使用相同的hash查看对应位置。缺点是可能会存在误判的情况。
2.缓存击穿
热点key突然失效,大量并发请求过来瞬间压垮DB
解决方案
- 互斥锁:查询未命中会建立互斥锁,然后再写入缓存,写期间其他线程不可以抢夺锁,需要等待,完成后释放锁。其他线程等待休眠重试(强一致性,低可用性)
- 逻辑过期:热点key不设置实际过期时间,但设置逻辑过期时间。当key逻辑过期后,第一个请求会获取互斥锁,新开辟一个线程来重写缓存并重置过期时间。重写过程中原线程会直接返回过期数据,其他线程请求时,由于获取互斥锁失败,也会直接返回过期数据。当写缓存线程完成时会释放互斥锁,这时其他线程可正常访问(高可用,性能好)
3.缓存雪崩
同一时段内大量key同时失效,或者redis宕机了,导致请求都打在DB上
解决方案
给不同key的ttl设随机值
设计redis集群:哨兵模式集群模式
给缓存添加降级限流策略
添加多级缓存
4.双写一致性(这里要根据简历上的业务来选择一致性再选择下列方案)
强一致性:
- 写操作采用延迟双删:删除缓存------>修改数据库------>(延时)-------->删除缓存:在高并发的情况下,不论是先删数据库还是先删缓存都会出现脏数据。延时双删能有效控制脏数据产生的问题
- 采用分布式锁:redisson提供了读写锁,在写操作的时候上写锁
最终一致性:
- 采用异步通知
- 通过阿里的canal
5.持久化
RDB,就是把内存中的所有数据都记录在磁盘中。采用bgsave命令,使用子进程异步完成
AOF,记录每一次执行命令,相对于RDB记录较为,但是磁盘空间占的多,数据安全性更高
6.数据过期策略
惰性删除:访问key的时候再看是否过期,如果过期就删除
定期删除:定期检查一定量key是否过期(分为fast和slow两种,fast频率不固定,耗时和间隔都极短;slow则是定时任务,具有默认频率)一般情况下是使用fast+slow模式
7.数据淘汰策略
默认是不淘汰任何key,但是内存满了之后不会允许新数据写入。allkeys / volatile-lru 对所有key/有ttl的key基于LRU淘汰,allkeys / volatile-lfu 对所有key/有ttl的key基于LFU淘汰
分布式锁问题:
传统的synchronized只能管控当前进程,一旦进入分布式集群就会容易失效;分布式锁的解决了这个问题
- setnx:(set if not exists)获取锁:SET lock value NX EX 10(NX:只有不存在的时候才可以set,EX为过期时间);释放锁:del lock。
- 为什么要加过期时间:防止死锁(业务超时或者服务宕机)
- 为什么要用一条指令而不是setnx+过期时间:为了保证原子性
- 锁的有效时长如何控制:1.根据业务评估锁的时间(较难)2.给锁续期(需要新开一个线程,比较麻烦)
- redisson:通过setnx加锁,另开一个线程watchdog进行监控,他会不断监控锁给锁续期。当释放锁的时候也会通知watchdog,告诉他锁释放了。当新线程企图获取锁时,他会一直while循环等待锁释放再加锁,不过循环也是有阈值的
- 如何使用redisson:
- 通过RLock lock=redissonClient.getLock("key")获取锁
- 通过boolean isLock=lock.tryLock("等待时间","失效时间","时间的单位") (如果这里不传任何值,那么就默认用watchdog续期)
- isLock为true的时候就是锁获取成功了
- 注意:这里的加锁,过期时间都是通过lua脚本实现的,使用lua脚本是为了保持原子性
- redisson支持在同一个线程内可重入锁(锁里还能再加相同的锁)
- redisson的主从一致性:redisson通过红锁redlock实现了主从一致性,他要求至少要在n/2+1的节点上有创建锁。但是红锁性能差,运行复杂,官方都不建议使用。redis主要是基于AP思想,追求的是高可用;如果需要强一致性那么需要选择基于CP思想的zookeeper
- 如何使用redisson:
集群问题:
主从复制:提高读写性能。主负责写操作,从负责读操作:读写分离。一般情况下1主1从+哨兵集群就够用了
- 如何保证数据同步:两个前提概念:replication_id:master有唯一的replicationId,而slave会复制master的id,因此同一个集群的ID肯定是一样的,offset:偏移量
- 全量同步(第一次请求,replicationID不一致):从节点执行replicaof与主节点建立连接,如果是第一次同步则返回主节点版本信息,此时主节点执行bgsave将生成的rdb发送给从节点,从节点删除本地数据后加载rdb。执行期间的命令主节点也会打包成日志,发送给从节点执行。
- 增量同步(非第一次请求,携带的replicationID一致):从节点重启或数据变化会发生增量同步:携带replication和offset,只有在replicationId一致的时候才会继续。在repl_baklog取出offset后的数据,发送命令让从节点执行
哨兵模式:保证主从集群的高可用,实现集群的自动恢复并且会给客户端实时推送故障转移的消息
- sentinel通过redis的pingpong来对节点进行主观下线检测,如果超过配置数量的redis都检测不到对应节点,那就是客观下线,说明redis挂了
- 恢复:判断主从节点断开的时长,超过指定值则排除该节点;判断从节点的slave-priority,值越小优先级越高,相同的则判断offset,越大优先级越高;最后是判断slave的运行id,越小优先级越高
- 脑裂:master,slave,sentinel处于不同的网络分区:主节点没挂,还能正常用;但是由于网络问题被sentinel判定为挂了,这时会出现两个master并且数据依旧会写入老master。当网络恢复后,老master会成为slave,由于主从复制,新写入的数据会全部丢失(被新master里的旧数据覆盖了)
- 有两个配置可以解决该问题:
- min-replicas-to-writes 1 #表示至少要有一个slave,master才能写
- min-replicas-max-lag 5 #表示数据复制和同步延时不能超过5秒
集群模式:
- 海量数据存储与高并发写:集群中有多个master,每个master可以有多个slave,master互为sentinel监测是否存活
- 通过引入哈希槽的概念,每个节点负责一部分哈希,对应的key通过计算hash值后会放入对应的master
redis为什么这么快:
1.redis是纯内存操作
2.采用单线程,避免了上下文切换,而且不用考虑线程安全问题
3.使用I/O多路复用模型+事件派发
redis的性能瓶颈不在于执行速度,而是网络延迟,而IO多路复用模型主要是实现了高效的网络请求
用户空间和内核空间:用户空间:只能执行部分命令; 内核空间:执行特权命令,调用一切系统资源。
三种常见的IO模型
- 阻塞IO模型(BIO):
- 一阶段:用户进程尝试读取数据,数据尚未到达内核需要阻塞等待
- 二阶段:数据到达并拷贝到内核缓冲区,内核缓冲区再拷贝到用户缓冲区,拷贝完成后进程才会停止阻塞,处理数据
- 非阻塞IO模型(NIO):
- 一阶段:用户进程尝试读取数据,数据尚未到达内核,会直接给用户返回异常,用户会开启轮询
- 二阶段:内核数据拷贝到用户缓冲区,拷贝完成后进程停止阻塞,处理数据
- 第一阶段虽然是非阻塞的,但是由于忙等机制会使CPU使用率暴增
- IO多路复用模型:
- 利用单线程监听多个socket,在某个socket可读可写时通知,避免无效等待。
- 其中select和poll模式只会通知用户进程有socket就绪,需要用户进程逐个遍历socket来确认;而epoll会在通知的同时把已就绪的socket写入用户空间,下面以select为例
- 一阶段:用户进程调用select,指定要监听的socket集合,有socket数据就绪即返回readable,此过程中用户进程阻塞;
- 二阶段:用户找到就绪的socket,依次调用revcvfrom读取数据,内核将数据拷贝至用户空间
事件派发:
redis中提供了多个事件处理器,处理器分别用于实现不同的网络通信请求。
最经典的三个是:
- 连接应答处理器(处理客户端请求应答)
- 命令回复处理器(处理客户端响应)
- 命令请求处理器(接收数据,把数据转为redis命令,再把结果写入缓冲队列,再通过命令回复处理器处理客户端响应)
redis6.0引入了多线程,为了命令回复处理器以及命令请求处理器的解析部分添加了多线程。
标签:缓存,速通,redis,Redis,master,key,线程,节点 From: https://www.cnblogs.com/kun1790051360/p/18348926