你在最近的项目中哪些场景使用了Redis
缓存
缓存穿透
缓存穿透:查询一个不存在的数据,mysql查询不到数据也不好直接写入缓存,导致每次请求都查数据库。
解决方案一:缓存空数据,即使查询返回的数据为空,也把这个空结果进行缓存(如key:1,value:null)。
优点:简单
缺点:消耗内存(总有空数据),不一致问题(来真的数据了)
解决方案二:布隆过滤器(可以使用redisson实现):查询之前先访问布隆过滤器,如果不存在就直接返回
优点:内存占用少,没有多余key
缺点:实现复杂,存在误判(bitmap位图越小误判越大,数组越大误判越小,但有内存消耗,一半百分之五以内的误判都可以接收)
布隆过滤器作用:可以用于检索一个元素是否在一个集合中(bitmap位图,数组每个单元只能存储0/1)。
缓存击穿
缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求,只能访问数据库,会把db压垮。
解决方案一:互斥锁
未命中后获取互斥锁(如setnx),处理完后释放锁,其他线程拿到锁才能运行
解决方案二:逻辑过期
在设置key时设置过期时间字段存入缓存,查询时如果过期,获取互斥锁后直接返回过期数据,同时开启新线程进行缓存重建释放锁,其他线程如果获取互斥锁失败后,仍然返回过期数据。
缓存雪崩
缓存雪崩:同一时间大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库
解决方案:
-
给不同的Key的TTL添加随机值,避免同时过期
-
利用Redis集群提高服务的可用性(哨兵模式,集群模式)
-
给缓存业务添加降级限流策略(nginx或Spring cloud gateway)
-
给业务添加多级缓存
《缓存三兄弟》
穿透无中生有key,布隆过滤null隔离。
缓存击穿过期key,锁与非期解难题。
雪崩大量过期key,过期时间要随机。
面试必考三兄弟,可用限流来保底。
先删除缓存还是先修改数据库-双写一致性
读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间
写操作:延迟双删:先删除缓存,修改数据库,延时一段时间再删除缓存
分布式锁(强一致性,性能低)
共享锁:读锁readLock,加锁之后,其他线程可以共享读操作
排他锁:独占锁write,加锁之后,阻塞其他线程读写操作
异步通知(允许延时一致的业务):
修改数据后写入数据库同时发送消息到MQ,受到MQ消息后更新缓存,主要依赖于MQ的可靠性。
阿里的组件canal组件也可以实现数据同步,不需要更改业务代码,部署服务后,它会把自己伪装成mysql的一个从节点,mysql数据更新后,canal会读取binlog数据,然后通过canal客户端获取到数据,更新缓存即可。
Redis持久化
RDB
RDB(Redis Database Backup file)Redis数据备份文件,也叫Redis数据快照,可以把内存中的所有数据记录到磁盘中,当Redis故障时,从磁盘读取快照文件,恢复数据。
主动备份:save bgsave(不影响主进程,一般用这个)
被动备份:redis.conf修改文件
RDB执行原理:物理内存通过进程中的页表交换数据,bgsave开始时会fork主进程得到子进程(复制页表),子进程完成fork后读取内存数据并写入RDB文件,fork采用copy-on-write技术(主进程读取内存时,访问共享内存;主进程执行写操作时,从内存中拷贝一份数据副本执行写操作,以后主进程读也从副本读)
AOF
AOF(Append Only File)追加文件,redis处理的每一个写命令都会记录在AOF文件中,可以看做是命令日志文件。
因为是记录命令,所以AOF文件会很大,通过bgrewriteaof命令,让AOF执行重写功能,只有最后一次有意义。也可以在配置文件里设置触发阈值自动重写。
Redis过期策略
惰性删除
设置key过期时间后,不去管它,下次使用该key时,检查是否过期,过期删除。
优点:CPU友好,不用浪费时间检查
缺点:内存不友好,不用就永远不会释放
定期删除
每隔一段时间对key检查,从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key。氛围SLOW(执行频率10hz,每次不超过25ms)和FAST(每次时间循环尝试执行,间隔不低于2ms,耗时不超过1ms)两种模式。
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对cpu的影响,也能释放过期key占用的内存
缺点:难以确定删除操作的时长和频率
所以:Redis的过期删除策略为:惰性删除+定期删除配合使用
数据淘汰策略
使用建议:
-
优先使用allkeys-lru策略,把最近最常访问的数据留在缓存中,如果业务有明显的冷热数据区分建议使用。
-
访问数据频率差别不大,没有明显冷热区分,建议使用allkeys-random随机淘汰
-
如果有置顶要求,使用volatile-lru策略,同时置顶数据不设置过期时间
-
如果业务有短时高频访问的数据,建议使用lfu策略
分布式锁
可以使用setnx命令,或者直接用redisson(底层是setnx和lua脚本)
看门狗机制
每隔一段时间自动做一次续期(默认每隔十秒续期一次)
可重入性
基于redssion实现的分布式锁可重入,利用hash结构记录线程id和重入次数
主从一致性
RedLock(红锁):布置在一个redis实例上创建锁,应该实在多个redis实例上创建锁(n/2+1)。
但是红锁这个思想,实现复杂,性能差,运维繁琐,因为redis是基于AP思想啊(可用性),如果要实现主从一致,可以利用CP思想(一致性)的zookeeper来实现。
Redis集群方案
主从复制
单节点redis的并发能力是有上限的,要提高并发能力就要搭建主从集群,实现读写分离,往主节点里写,从节点里读。
主从全量同步
slave节点执行replicaof命令建议连接,请求master数据同步(发送replid和offset),master判断是否是第一次同步(replid是否一致),如果是第一次,就向slave返回master的数据版本信息(replid,offset),slave保存版本信息,master执行bgsave生成RDB文件并向slave发送,slave清空本地数据,加载RDB文件。在这个发送期间,主节点记录RDB期间的所有命令存入到repl_baklog中发送到slave,slave执行接收到的命令。
主从增量同步
适用于slave重启或后期数据变化,从节点发送replid和offset,主节点判断是否一致,如果不是第一次,就回复continue,之后去repl_back中获取offset后的数据,发送命令。
哨兵机制
Sentinel哨兵机制实现主从集群的自动故障恢复。
服务状态监控
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:如果超过sentinel数量一半的哨兵都认为该实例主观下线,该实例客观下线。
哨兵选主规则
判断主与从断开时间长短,超过指定值就排该从节点
然后判断从节点的slave-priority值,越小优先级越高
如果优先级一样,判断offset值,越大优先级越高
判断slave节点的运行id大小,越小优先级越高
集群脑裂
主节点和从节点和哨兵处于不同网络分区,使得哨兵灭有能够心跳感应到主节点,所以选举出了一个新的主节点,这样就存在了两个master,像大脑分裂一样,导致客户端孩子啊老主节点写入数据,新节点无法同步数据,网络恢复后,哨兵会将老的主节点降为从节点,再从新master中同步数据,导致数据丢失。
解决:修改redis配置,设置最小的从节点数量以及缩短主从数据同步的延迟时间。
分片集群结构
主从和哨兵可以解决高可用,高并发读的问题,但是没有解决海量数据存储和高并发写问题,这时可以使用分片集群。
分片集群特征:
集群中有多个master,每个master保存不同数据
每个master有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会转发到正确节点
分片集群结构:数据读写
引入哈希槽概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
Redis是单线程的为什么速度这么快?
Redis是存内存操作,执行速度非常快
采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
使用I/O多路复用模型,非阻塞IO
I/O多路复用模型
redis是纯内存操作,执行速度非常快,性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型实现了高效的网络请求
常见的IO模型
阻塞IO:等待数据和从内核拷贝数据到用户空间都需要阻塞等待
非阻塞IO:recvform阶段立即返回结果而不阻塞第二阶段依旧阻塞
IO多路复用:利用单个线程来同时监听多个Socket,并在某个Socket可读,可写时得到通知,避免无效等待,充分利用cpu资源。
Redis网络模型
提供了连接应答处理器,命令回复处理器(多线程回复事件),命令请求处理器(命令转换使用多线程,增加速度,命令执行依旧是单线程)。