Redis面试
Redis缓存
缓存一致性
选择Cache Aside Pattern模式,在更新数据库的同时更新缓存
设置过期时间TTL,相当于给缓存一致性加了一个兜底的方案,在正常更新缓存失败的情况下,可以利用过期清理的方案保证于数据库的一致性
数据库增删改--->redis只做删:
我们对数据库进行增删改的操作时,redis没必要同步的去做增删改,只需要做删除就行,可以减少对redis不必要的占用,也不影响整个效果。除非该数据是热点数据,查询频率高,才需要同步增删改,大多数的数据不需要。
删除redis和数据库增删改的顺序问题:
1.先操作缓存,再操作数据库--->并发安全问题:
例如线程1修改数据,线程2查询数据。修改数据的线程1先来,删除redis数据,再去操作数据库,但当线程1还没来得及修改数据库时,来了查询数据的线程2,查到redis没数据(已被线程1删除),就去查询数据库查到旧的数据,将旧数据缓存到redis,于是线程1删除的数据白删了。然后线程1才修改完数据库的数据,会发现数据库和缓存的数据不一致。
2.先操作数据库,再删redis
也有可能导致并发安全问题,但是概率极低
缓存穿透
指客户端请求的数据在数据库中不存在,缓存无法生效,从而导致请求穿透缓存,直接打到数据库的问题(就是说,明知道数据库没有该数据,故该缓存也不可能有数据,则会直接打到数据库)
假如有大量的线程一直请求不存在的数据,就会直接打到数据库,给数据库带来巨大的压力,甚至数据库崩溃。
解决方案
1.缓存空对象
当请求一个不存在的数据,数据库中也没有,则以请求的这个参数为key(这里指的是将要查询的数据作为key,而不是来查的这个“人”作为key),在redis中缓存一个空的值(可能是null或者标记),下次再来查询时,则会在redis中查到这个特殊的值,就不会让其请求数据库。相当于给不存在的数据也建立了一个请求,避免大量不存在的请求打到数据库。
优点:简单方便
缺点:占用内存-->解决方法-->设置过期时间
2.布隆过滤
存在于redis和客户端请求之间
优点:内存占用小,没有多余的key
缺点:实现复杂。存在误差,不存在则一定不存在,但是不能说一定存在,误差概率很小
补充:缓存预热
在项目刚开始运行时,去大量的查询数据库,提前批量的写入缓存中,当后续用户访问时可以直接命中缓存而不用临时建立缓存。
缓存雪崩
是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量的请求到达数据库,带来巨大压力。
情况一:大量的缓存key同时失效
解决方案:
在缓存预热的阶段,在不同的key的过期时间的基础上再加随机过期时间,同时失效的概率降低。
情况二:Redis服务宕机
单点的Redis无法去避免,最佳方案是为其搭建集群,比如主从集群,哨兵。哨兵可以监控主从集群的状态,当发现出现宕机时,可以重新选主,从而保证整个集群的高可用。即使这样也不能完全保证服务器不宕机,因此需要考虑宕机之后的解决方案
例如,当服务器宕机后,限制请求并发的量,减小数据库的压力。
其次,还可以进行降级策略,直接拒绝一部分请求,甚至可以对整个业务进行熔断阻止访问,缓存恢复之后再解除熔断。
给业务添加多级缓存,例如在浏览器上添加静态资源的缓存。
缓存击穿
也叫热点key问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大冲击。
解决方案:
1.互斥锁
假设有一个线程查询时未命中,会想去进行缓存的重建(即查询数据库,查到的数据放入缓存的过程),但是为了避免无数的线程都去重建,要求对其加锁,只有获取锁成功的线程才能重建缓存数据,再释放锁。如果该线程没有释放锁,与此同时的其他线程无法获取锁也就无法重建缓存数据,需要休眠一会再重试。
特点:简单粗暴,但是如果重建的过程比较久,其他线程就需要等待,性能降低
2.监控数据
监控数据,实时调整:监控哪些数据是热门数据,实时的调整key的过期时长(将过期时长延长)。
Redis主从
主从同步原理
问题1:master是如何知道slave是第一次同步连接还是断开重连的
在建立主从集群之前,每个人都是master,都有自己的replid。建立主从集群以后,主从的replid都变了,变成了相同的replid。在第一次建立主从关系时,master会生成一个全新的replid,并且将该replid分享给每个slave。因此可以通过replid来进行判断是否是第一次来连接。
slave(此时还不是slave)尝试去成为slave,发出请求的同时还需要带上原本的replid,然后master根据其replid进行判断
replid不一致-->第一次来-->发送全部的数据
replid一致-->断开重连-->发送缺少的数据
问题2:如何发送全部数据
master执行bgsave(后台执行的命令,用于开启独立进程,把redis在内存中所有数据都持久化到硬盘中写到一个RDB文件里),生成RDB文件。也就是说RDB文件有master所有数据
此时,master只需要将该RDB文件发送给slave,slave将自己的数据清除,把RDB文件加载到内存即可达到主从完全一致。
问题3:master如何知道slave缺失的是什么数据
offset相当于写入命令的量
将slave和master各自的offset做比较,看看缺失的哪部分命令,将命令发给slave即可实现增量同步
哨兵原理
思考:哨兵如何知道节点是否健康?哨兵应该选哪个为主?选主的角色转换是如何实现的?
1.哨兵如何知道节点是否健康?
心跳机制
2.哨兵应该选哪个为主?
3.选主的角色转换是如何实现的?
Redis内存回收
过期key处理
思考:Redis如何知道一个key是否过期?是不是TTL到期就立即删除?
1.Redis如何知道一个key是否过期?
dict和expires两个指针分别指向两个hash表。dict指向的表存放所有的键值对,expires指向的表里面的键是设置了过期时间的key,值是key的到期时间。
因此只需要拿expires指向表的key去查,就能拿到对应的到期时间,再将到期时间和当前时间进行对比就知道是否过期。
总结:在Redis中有两个hash表,一个记录的是原始的key-value,另外一个记录的是带有过期时间的key和它的到期时间,只需要拿它的key去查就知道是否过期。
2.是不是TTL到期就立即删除?
总结
内存淘汰策略
Redis数据持久化
redis之所以能够高速读写是因为它基于内存,但是也带来一个问题,在服务器宕机或者重启的情况下,内存里面的数据就会丢失。为了解决该问题,redis提供了持久化的技术来保证数据的持久性和可靠性
持久化方式:
1.RDB
-
原理:RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是创建一个子进程(不是自己去做这件事,而是让子进程去做),先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
- 节省空间:RDB文件是二进制的,通常比AOF文件更紧凑,因此可以节省磁盘空间。
- 高性能:生成RDB快照时,Redis不会执行写入磁盘的操作,因此不会对性能产生很大影响。
- 恢复速度快:RDB能够将数据集压缩存储在磁盘上,因此在恢复大数据集时比AOF更为迅速。
- 数据备份简单:RDB文件非常适合用于数据备份,可以定时将RDB文件拷贝到其它存储介质上。
2.AOF
redis将自己执行的所有写入操作命令都记录下来,当redis重启之后,对所有写入命令进行回放以达到恢复数据
String和Hash的选择
1.String 和 Hash 哪一个存储对象数据更好?
(Hash是redis的一种数据结构/数据类型)
String 存储的是序列化后的对象数据,存放的是整个对象。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。
String 存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。
在绝大部分情况,建议使用 String 来存储对象数据即可!
2.购物车信息用 String 还是 Hash 存储更好呢?
由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:
- 用户 id 为 key
- 商品 id 为 field,商品数量为 value
那用户购物车信息的维护具体应该怎么操作呢?
- 用户添加商品就是往 Hash 里面增加新的 field 与 value;
- 查询购物车信息就是遍历对应的 Hash;
- 更改商品数量直接修改对应的 value 值(直接 set 或者做运算皆可);
- 删除商品就是删除 Hash 中对应的 field;
- 清空购物车直接删除对应的 key 即可。