Redis
Redis的使用场景有哪些?
结合自己项目的业务去回答,体现项目的真实性
一、缓存
1、什么是缓存穿透?怎么解决?
正常数据查询步骤是先到缓存(Redis)查,要是缓存(Redis)没有就会去数据库查,如果数据库中查到了这条数据,这条数据就会写入到缓存;没查到就不会写入缓存
缓存穿透是指:在查询缓存(Redis)中不存在的数据时,就会直接查询数据库,如果数据库中不存在这条数据就不会写入缓存;数据库能承受的压力比较小,别人可以利用这一点,人为制造大量数据库中不存在的数据发送请求,让你的数据库短时间内处理不了这么多查询操作,导致你数据库宕机。
缓存空数据解决:如果数据库中不存在这条数据就给缓存中添加一条key-null记录。这会造成数据不同步问题(当数据库中新添加了这个key的数据,缓存中的数据不会变化)
使用布隆过滤器解决:在预热缓存(Redis)的时候预热布隆过滤器,在查缓存(Redis)前必须先查布隆过滤器;如果布隆过滤器中不存在就直接返回。这样会存在误判
2、什么是布隆过滤器
主要用于检索一个元素是否存在于某个集合中,我们当时是用的Redission实现的布隆过滤器
(数位图)bitmap
可以理解为一个只存了0和1的数组,一开始里边只存了0;对于想要记录的每一个数据都经历了多个hash函数的计算,根据计算结果将数组对应的多个位置的值改为1;查询数据有没有记录时只需要 让数据经历相同的hash运算得到的值 所对应的数组位置的值 看它是不是都是1,如果是说明缓存中存在这个数据,允许通过布隆过滤器;否则直接返回。
优点:只存了0和1,内存占用较小。
缺点:实现复杂,存在误判。
误判的原因:多个hash函数之间产生的值可能相等;比如数据一对应的hash1=2;hash2=4;hash3=6;数据二对应的hash1=1;hash2=3;hash3=5;这两个数据都是唯一的;这时候有一个数据三经过布隆过滤器,数据三的hash1=4;hash2=2;hash3=1,刚好满足条件,会误判通过过滤器。
可以增加数位图的长度来降低误判率,但是会增加内存开销。
3、什么是缓存击穿?怎么解决?
缓存击穿是指:在缓存中的某个数据过期的时刻与缓存数据重建完成的时刻之间进行数据查询时会把查询请求发送到数据库;如果这一段时间内存在大量对该数据的查询请求,这些请求都会发到数据库,容易引起数据库宕机。
分不同的业务选择不同的解决方案:
用互斥锁解决:当一个线程的数据查询请求发送到缓存(Redis)的时候,如果缓存(Redis)里没有这条数据,就获取互斥锁。如果获取成功,就会查询数据库,将数据写入缓存,最后释放互斥锁。如果获取失败,就会每隔一段时间重新启动该线程去查询缓存,然后获取互斥锁,直到获取互斥锁成功,此时之前占用互斥锁的线程已经完成了缓存重建,这个线程就可以直接通过缓存查询到这条数据。
优势:保证数据的强一致性
劣势:性能差:数据重建完成之前其他线程都会重试
用逻辑过期解决:当一个线程的数据查询请求发送到缓存(Redis)的时候,如果缓存(Redis)里这条数据逻辑时间已过期,就会获取互斥锁。如果获取成功,就会新开启一个线程,最后返回过期数据。新开启的线程负责查询数据库,将数据写入缓存并设计逻辑过期时间,然后释放互斥锁。如果获取互斥锁失败,就会返回过期数据。
优势:高可用性,性能也高
劣势:可能导致请求数据不一致(新线程将新数据写入缓存之前,缓存里的数据是原来的过期数据,如果两个数据不一样,会导致其他线程在这段时间请求到的数据不是最新的)
4、什么是缓存雪崩?怎么解决?
缓存中大量key在同一时间过期导致数据查询请求都去访问数据库,可能导致数据库宕机。
主要解决方式有两种:
a、给key设置过期时间时加上一个随机数。(主要解决方案,简单好用)
b、降级处理:gateway让请求分流、限流(所有流量压力问题的保底解决方案)
5、mysql的数据如何与Redis进行同步?(双写一致性)
结合自己项目业务背景去回答,不同的场景下回答不同。
双写一致问题描述
无论是先删除缓存还是先修改数据库,都有可能导致缓存和数据库数据不一致问题。
基本解决方案:延时双删:删除缓存=>删除数据库=>延迟一段时间=>再删除缓存;删除两次缓存的目的是为了防止多线程操作的情况缓存没有成功更新的情况;延迟的目的是让数据库数据尽可能是新数据(主数据库数据同步到从数据库需要时间,这段时间是不容易把控的,所以还是有可能导致数据不一致)
主要分为两种情况:数据一致性要求高;允许数据延迟一致
对于一致性要求高的情况:
a、可以加互斥锁,保证每个线程之间的操作不会互相影响,但是性能极低。
b、对于一般情况来说:加入缓存的数据都是读多写少,在这个前提下,我们可以考虑使用共享锁和排它锁来限制其他线程的写操作(读操作的代码中加入共享锁,写操作的代码中加入排他锁,这样可以多线程同时读,但是当有一个线程进行写操作的时候,不允许其他线程的读写操作),在保证强一致性的前提下尽可能提升读操作的性能,但还是性能低。
共享锁和排他锁:共享锁只允许其他线程的读操作,不允许其他线程的写操作;排他锁不允许其他线程的读写操作。
对于允许数据延迟一致的情况(大多数都是这种情况):
解决方案有很多:
a、可以考虑使用MQ异步通知来实现,后端向数据库发送修改数据库请求的同时向MQ发布消息,让缓存服务监听消息,缓存服务收到消息以后进行更新缓存的操作。这种数据一致的可靠性完全取决于MQ的可靠性。
b、使用Canal的异步通知,Canal是阿里的一个中间件,主要基于Mysql的主从同步来实现的。Canal可以监听Mysql的binlog,当数据库发生变化时,会把变化记录到binlog里,如果在binlog里发现我们需要更新缓存的表发生变化时,Canal会向缓存服务发送通知让缓存服务更新缓存。优势是:基本不用改动业务代码。(推荐解决方案)
6、Redis数据持久化是怎么做的?
两种持久化方式
RDB
RDB是一个快照文件,它每隔一段时间,把Redis内存储存的数据写到磁盘上(二进制数据),当Redis宕机恢复数据的时候,可以直接使用快照文件快速恢复数据。
AOF
AOF是一个追加文件,当Redis执行写命令时,会把这条命令记录在AOF中;当Redis宕机时会再次执行AOF中的写命令来恢复数据。速度慢。
对比图
如果对数据安全性要求较高,可以两种方式结合使用。
7、Redis中数据过期以后怎么删除的?(Redis的过期策略是什么?)
两种过期策略
惰性删除
惰性删除:设置该key过期时间后,我们不去主动删除key,当需要再次使用这个key时,我们再去检查该key是否过期,如果过期我们才去删除。
优点:对CPU友好,不需要特别的去处理每一个过期的key。
缺点:对内存不友好,长期以来会积累很多过期的key在内存中
定期删除
定期删除:每隔一段时间,我们就检查一部分key(轮巡检查,确保所有key都会被检查到),删除其中已经过期的key。
定期删除有两种模式:
-
SLOW模式是定时任务,执行频率默认为10 Hz,每次不超过25 ms,可以通过Redis.config配置文件来设置频率和时间。
-
FAST模式执行频率不固定,但是两次执行间隔不超过2 ms,每次耗时不超过1 ms;
优点:可以通过限制每次执行删除操作的时长和频率来减少删除操作对CPU的影响,另外定期删除能有效释放过期key占用的内存。
缺点:难以确定删除操作执行的时长和频率(执行时间太长、频率太大的话会对CPU产生较大影响,执行时间太短、频率太少的话容易积压过期的数据)。
注意:
在Redis中实际使用时两种策略结合使用。
8、Redis数据淘汰策略是什么?(Redis内存用完了会发生什么?如何保证Redis中保存的都是热点数据?)
默认淘汰策略是 noeviction(不淘汰,内存不够直接报错);此外还有随机淘汰,最少最近使用淘汰(LRU:最少最近使用。用当前时间减去最后一次使用时间,这个值越大,淘汰的优先级越高),最少频率使用淘汰(LFU :最少频率使用,会统计每个key访问的频率,值越小则淘汰优先级越高);这些淘汰算法又分为两种:对所有数据生效和只对设置了过期时间的数据生效;一共8种淘汰策略。
Redis内存用完了:如果使用的是默认淘汰策略,会直接报错。其他情况按使用的数据淘汰策略处理数据。
如何保证Redis中保存的都是热点数据?:使用最少最近使用数据淘汰策略(LRU),能保留绝大多数热点数据。
二、分布式锁
业务情景分析:抢券
如果使用线程锁只能保证单台服务器内部线程不会相互影响,但是多台服务器之间,线程锁会不起作用(service 1的线程锁无法作用于service 2的线程锁)。
分布式锁孕育而生
分布式锁是指构建在服务器外部的锁;这样每个服务需要锁可以在外部获取锁,确保锁的统一性。
1、 setnx分布式锁
setnx 分布式锁:在Redis中使用setnx指令写入一个key并设置过期时间,该key可作为分布式锁使用,key的过期时间就是锁的过期时间。
执行流程:尝试获取锁,如果失败直接终止线程,成功的话执行业务;业务如果执行完成就释放锁,如果执行超时自动释放锁(这个自动释放时间不好把控)。
2、Redission 分布式锁
基于setnx分布式锁和lua脚本(lua脚本保证setnx操作的原子性),并优化了执行流程:尝试获取锁,如果成功获取则触发看门狗机制:自动创建一个线程每隔一段时间(releaseTime/3)对锁进行一次续期(避免 业务没完成锁先释放了);同时原线程执行业务操作,需要手动释放锁并处理看门狗线程。如果获取失败则循环获取,并设置循环时间(单个业务执行时间都是很短的,有效避免资源浪费)。
Redis实现的分布式锁(setnx)是不可重入的;Redission做了优化,使用Redission的分布式锁可以重入(Redission利用hash结构记录线程id和重入次数,每重入一次,重入次数加一)。
Redission实现的分布式锁不能解决主从一致性问题,可以用Redission提供的红锁来解决,但是需要给过半的Redis节点都加上锁,性能低;可以使用 zookeeper 实现的分布式锁解决。
三、Redis集群相关问题
1、Redis主从数据同步
描述:单节点的Redis并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。一般是一主多从,主节点负责写数据,从节点负责读数据。
全量同步(第一次主从数据同步)
流程:从节点将repid、offset传给主节点请求连接;主节点根据 repid判断是否是第一次同步(repid是否一致)。如果是第一次(repid不一致)就返回主节点的数据版本信息(包括repid、offset),从节点保存版本信息。然后主节点执行 bgsave,生成RDB文件(快照文件)并发送给从节点,从节点清空本地数据并加载RDB文件。主节点在 repl_baklog 记录 从节点执行RDB文件这段时间 主节点中执行的所有命令,然后发送给从节点,从节点执行收到的命令。
增量同步(后期数据同步)
流程:从节点将repid、offset传给主节点请求连接;主节点根据 repid判断是否是第一次同步(repid是否一致)。判断不是第一次后回复给从节点continue;然后去repl_baklog中获取offset之后的命令并发送给从节点,从节点根据主节点传来的命令执行实现数据同步。
2、Redis哨兵模式
怎么保证Redis的高并发高可用?
哨兵模式:实现主从集群的自动故障恢复(监控、自动故障恢复、通知)
服务状态监控
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
-
主观下线:如果Sentinel节点发现某实例未在规定时间相应,则认为该实例主观下线。
-
客观下线:若超过指定数量(最好设置超过Sentinel实例数量的一半)的Sentinel都监测到该实例主观下线,则该实例客观下线
哨兵选主规则
主要判断哪个从节点的offset值大;越大优先级越高。
3、脑裂问题
问题描述
Redis的集群脑裂是指因为网络问题,导致Redis master(主)节点跟Redis slave(从)节点和Sentinel集群处于不同的网络分区,此时因为Sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。此时存在两个不同的master节点,就像一个大脑分裂成了两个。 集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据,那么新的master节点将无法同步这些数据,当网络问题解决之后,Sentinel集群将原先的master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。
解决方法
配置Redis中的两个参数:
-
min-replicas-to-write 1 :表示最少的从节点为1个
-
min-replicas-max-lag 5:表示数据复制和同步的延迟不能超过5秒
按照上面的配置,要求至少1个slave节点,且数据复制和同步的延迟不能超过5秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失。