文章目录
本篇文章记录的是我对这些八股的理解,后续会更新正确答案进行对比理解。
数据结构
redis的底层结构
- string:简单动态字符串sds,在原先c数组的基础上做了封装,解决了\0特殊字符会导致错误识别string结尾的问题,还可以o1时间度获得string的长度,不用去遍历提高效率
- hashmap:底层是一个dict的数据结构,保存着key和value
- zset:当数据不大的时候,用的是ziplist,数据量大的时候用的是跳表+hashmap,后来ziplist有连锁更新问题,后续的redis优化为了listpack
zset用过吗
- zset是在set的基础上还有一个分数,支持按照分数进行升序和降序的操作,很适合像排行榜这种既需要分数排序又需要o1时间获得对应数据的场景
跳表是怎么实现的
- 跳表就是在链表的基础上引入了二分查找的思想,就是创建了一些列的辅助节点,这些辅助节点按照粒度分为不同的层,越往上层,节点越少,跨度越大,这样就可以通过logn的复杂度快速找到相应的节点,这是一种空间换时间的思想
跳表是怎么设置层高的
- 跳表的层高设置要通过具体的场景来取舍,如果设置了太多层高,对于内存的需求过大,如果设置过少的层高,查找效率不高,应该按实际的需要,设置中间一个甜点值层高
redis为什么使用跳表而不是b+树
- b+树的非叶子节点都没有保存具体的数据,都需要查找到叶子节点后才能找到数据,而且内存占用大,跳表通过层节点找到了即可以返回,效率较高
ziplist是怎么实现的
- ziplist是一段连续的内存,里面有一系列的node,里面保存着当前这个ziplist node的尾部对应的位置以及具体的key-value对,通过这个长度可以快速遍历整个ziplist,加上连续内存所以查找的效率很高,但是会存在连锁更新的问题
介绍下redis中的listpack
- 由于ziplist会存在连锁更新的问题,也即如果key-value对更新了之后,如果长度改变,起所占的字节长度可能会变化,需要更新其尾部所对应的位置,同时后面的所有节点的尾部位置都需要一起更改,这样会影响效率
- 所以后续redis更新为了listpack,listpack也是一段连续内存,里面有一系列的节点,也是存具体的key-value,和ziplist不同的listpack会在每个节点的末尾存储这个节点的长度,这样就能避免了连锁更新的问题
hash表是怎么扩容的
- hash表扩容也是分配一段新的内存,然后渐进式每次迁移一点数据,直到迁移完成
hash表扩容时有读请求怎么查
- 如果旧表中没有查到对应的数据就会去新表查,同时完成一部分的渐进式扩容的任务
string是使用什么存储的,为什么不用c语言的字符串
- string在redis底层是使用简单动态字符串sds进行存储的,sds也是在c语言的基础上做了一层封装,其还保存了字符串的长度,可以o1时间复杂度获取string的长度,同时sds还支持类似\0这种特殊字符的存取,而c语言的string不仅\0会影响其保存的结果,同时获取string长度还是个on时间复杂度
线程模型
redis为什么这么快
- 单线程模型,没有复杂的线程切换开销
- 底层的数据结构很高效
- 底层依赖于linux系统中的epoll函数和I/O多路复用,可以及时处理已就绪的IO请求
- 基于内存
redis中哪些地方使用了多线程
- redis中执行主命令的线程是单线程的,但是其他地方为了效率也采用了多线程的设计
- redis rdb和aof的备份的时候是fork一个子进程完成备份的
- 同时redis的会同时监听多个网络IO,这也需要多线程的支持
redis的网络模型是怎么样的
- redis网络模型是epoll函数+IO多路复用,因为性能瓶颈不在cpu和内存,而是在于网络IO
- 所以redis会监听多个网络IO,用IO多路复用的思想,底层用一个epoll函数监听,只要有就绪的epoll函数就能捕捉到进行处理,这样多个就绪的网络IO才能被及时处理,效率高
事务
如何实现redis的原子性
- lua脚本,lua脚本可以保证一系列的redis操作在redis中是按照一条命令似的去执行,期间redis不会执行其他的命令,从而达到原子性
除了lua,还有什么形式保证原子性
- redis还可以用锁的形式来保证原子性,拿到锁的线程才能执行,其他的都只能阻塞等待
- redis还可以用事务的形式来保证其原子性
日志
redis有哪两种持久化方式,各自的优缺点都是什么
- rdb和aof,rdb是会保存当时内存的一个快照,只需要加载rdb文件即可恢复,而aof会以追加写的形式把每条redis执行的命令写入aof文件中,需要把aof文件的命令依次执行才可恢复
- rdb速度快,但是会丢数据,比如会最近一次rdb后几秒内redis崩了,期间的命令会丢失;aof记录了所有的redis命令,所以aof丢数据的情况比较少,较全面,但是由于记录了所有的命令,需要依次加载,速度慢
缓存淘汰和过期删除
过期删除策略和内存淘汰策略有什么区别
- 过期删除策略侧重的是redis中的key,过期后是怎么被删除的;而内存淘汰策略,侧重的是redis内存满了之后,应该淘汰哪些key,过期删除删除的一定是过期的key,但是内存淘汰可能淘汰为过期的key
介绍一下redis内存淘汰策略
- 内存淘汰策略指的是在redis内存满了的时候需要淘汰掉一些key来保证redis不会oom
- 全部key:随机,ttl(淘汰即将过期的key),lfu,lru
- 过期key:随机,lfu,lru
介绍一下过期删除策略
- 过期删除指的是redis怎么处理这些过期的key,是什么时候删除
- 立即删除
- 定时删除:每秒扫描redis内存中的key,如果没过期就不管,如果过期以百分之几的概率删除该key
redis缓存实效会不会立即删除
- 不会,redis内部设计的是定期扫描+惰性删除策略,只有用到了发现过期才会删除,而且定期扫描也有可能没有扫描到,扫描到了也有一定的概率不被删除
为什么redis不过期立即删除该key
- cpu消耗大,如果过期立即删除的话,如果一段时间内有多个key同时过期,cpu全部在处理过期的key了,会影响其他进程的进行
集群
redis主从同步的增量和完全同步怎么理解
- redis一开始还没进行主从同步的时候,会发送sync信号,要求进行主从同步,sync信号同步后,第一次同步会是全量同步,也就是redis主机会把当前的内存存的数据打包为一个rdb文件,然后传给从机,这期间所发生的命令也会被主机存在一个缓冲区中,后续会发送给从机,全量备份完成后后续就是增量备份,主机会把相关的命令存在缓冲区定时发送给从机,以达到同步的效果
redis主从和集群可以保证数据一致性吗
- redis不能保证强一致性,但是可以保证最终一致性,因为数据的传输时候延时的
哨兵机制原理是什么
- 哨兵机制是指,redis会有一个哨兵监控整个redis集群,如果master挂了,哨兵会检测到这个情况,然后选择一台从机作为master,哨兵负责监听+选举+修改主从关系
哨兵机制的选主节点的算法是什么
- raft算法,会在从节点中发起投票,获得过半数投票的被选举为主节点
redis集群模式了解吗,优缺点知道吗
- redis集群就是把key通过hash算法,分配到不同的redis主机内,实现负载均衡
- 优点:存储量大,负载均衡
- 缺点:扩容麻烦,脑裂问题
场景
为什么使用redis
- redis一般都是为了当作缓存加速的作用,查询mysql效率低,通过基于内存的redis,速度快,能及时返回给用户而且支持较高的并发
为什么redis比mysql快
- redis是基于内存,而mysql数据是存储在磁盘上的
- redis单线程,数据结构高效
- redis的IO多路复用和epoll函数的使用
本地缓存和redis缓存的区别
- 本地缓存没有持久化,redis持久化支持rdb和aof方式
- 本地缓存不支持多种高效的数据结构
redis支持并发操作吗
- redis本身是单线程的,但是由于底层的IO多路复用和epoll函数的设计,所以能监听多个网络IO达到类似并发操作的操作,但实际上还是单线程的
高并发场景,redis单节点+mysql单节点能有多大的并发
- redis 11w并发 mysql 4w
redis的应用场景是什么
- 缓存,减少mysql的查询压力
- 排行榜,利用zset
- 基数统计,uv pv,利用hyperloglog
- 地理位置相关查询,geo
- 分布式锁
redis分布式锁的实现原理,什么场景下需要用到分布式锁
- 在高并发下,有多个线程需要对同一资源进行请求操作的时候,由于并发可能会出现相关冲突问题,所以需要分布式锁来限制,只有抢到锁的才能对资源进行操作,其他没抢到锁的只能等待
- redis的分布式锁采用的是redrock的思想,会设置多个锁(单数),获取锁的时候,只有获得了超多半数的锁,且锁的过期时间小于请求锁的时间,则认为获取分布式锁成功,如果没有只能等待
redis中的大key问题是什么
- 大key是指redis中key所对应的value项数过大或者每一项所占大小过大,则称为大key
- 大key的读取和删除都会很慢很耗资源,过多的大key还会导致oom
大key问题的缺点是什么
- 大key过大,读写效率低
- 大key过大,删除时如果不是异步,会阻塞主进程
redis大key如何解决
- 删除大key的时候最好是unlink而不是直接delete,因为前者是异步的
- 读取大key的时候,可以一部分一部分读取,而不是一整个读取,fetch
什么是热key
- 热key是指一段时间内,请求次数很多的key
- 热key所对应的数据频繁被请求到,是重要业务数据
如何解决热key问题
- 热key问题指的是同一时间热key同时过期导致的大量请求同时打到mysql从而拖垮mysql
- 热点key永不过期
- 用一个线程去获取分布式锁更新热点key,其他先暂时等待
- 热点key设置过期时间有random的差距,确保不同时过期
如何保证redis和mysql数据一致性
- canlog监听mysql,如果有修改就直接修改redis中的key
- 先修改mysql,再删除对应key
缓存雪崩、击穿、穿透是什么,怎么解决
- 缓存雪崩,同一时间内大量热点key同时过期,大量请求打到mysql导致mysql崩溃,可以设置热key永不过期或者是给热key一个过期时间上再加对应的random值,或者是在key过期时候设置分布式锁,又一个线程去更新key,其他线程等待
- 缓存击穿,是指一个热点key,某一时间刚好过期了,刚好这段时间里有大量的请求请求这个key,导致mysql压力骤增最终奔溃,可以设置热点key永不过期,或者是分布式锁的方式只允许一个线程拿到锁去更新redis中的key,其他线程先等待,还可以设置空缓存对象,请求失败时优先返回空缓存对象做一个降级处理
- 缓存穿透,指的是请求的key在redis中和mysql中都是不存在的,请求redis找不到回去请求mysql,但由于mysql里面也是没有的,这种无效冗余的查询会白白增加mysql的压力,可以使用布隆过滤器粗略判断key是否存在于系统中,如果存在则放行,如果不存在则拒绝请求,减少不必要的数据库查询
布隆过滤器原理介绍一下
- 布隆过滤器是用来减少缓存穿透的现象的,通过布隆过滤器我们可以很快的知道一个key是否存在于我们的mysql内,布隆过滤器是通过一个大长bitmap,通过多个hash函数,我们首先会把所有的mysql现有的key过一遍,对于每个key,都会通过多个hash函数映射到多个bitmap的位置中,把对应位置置为1,对于新来的key,还是用相同的那些hash函数处理,看对应的那几位是否都是1,如果有1位不为1,则不存在,拒绝请求,如果都为1,则放行,但是由于hash冲突的存在,所以都为1也不能证明这个key真的存在,需要后续在代码中判断,但是已经能避免掉很多无效查询了