Redis面试篇
Redis篇
一、Redis单线程为什么快?
-
网络层面:
在操作系统层面使用了操作系统提供的Epoll函数,并且在Epoll模型中也只是使用同一个线程去接受请求与处理请求,这有点类似于IO模型中的单Reactor单线程模型。
也就是因为在取操作与处理是单线程的,那么对数据的操作自然也就不需要添加很多的锁来保护共享资源。因此操作自然没有线程上下文切换与加锁的耗时。
-
架构方面:
-
数据完全存储在内存中,不涉及到磁盘IO(当然了,这里先撇开数据持久化的问题)
-
Redis内部的数据结构,如:sds(int、embstr、raw)、hashtable、ziplist、quicklist、skiplist等优秀的数据结构
-
Redis很多数据类型提供了很多小的优化
- hashtable它底层是dict的数据结构去实现的,它的扩容阈值就与普通的hash不一致,redis的dict它是扩容会更早,这样就可以减少dict结构中的链表结构的查询时间复杂度O(n)
- 包括dict在扩容的时候,它是渐进性的rehash的过程,以减少单次rehash的耗时过久
- 比如在ziplist与hashtable底层都记录了length长度,因此在调用scard、llen函数时,操作时间复杂度为O(1),而不需要全盘查询计数
-
二、Redis 持久化
Redis持久化主要有AOF与RDB两种方式
-
RDB
RDB更像是Redis备份的一种快照方式,RDB可以灵活的在配置文件中配置RDB的触发时机,当开始RDB时,Redis进程会fork出一个子进程,由子进程完成数据快照的持久化工作,当新RDB文件创建并写入完毕之后,会删除旧版的RDB文件,以新文件代替旧文件。
优点:文件较小、恢复时间快,适合主从同步的异地备份,包括向主从模式的首次同步也主要是依赖RDB文件(之后同步则会依赖主机发送命令给从机)。
缺点:
- 可能会丢失一段时间的数据,如果配置不得当的话
- 但是若配置的RDB触发时间过于紧凑,那么就会经常性的fock出子进程进行数据同步写入磁盘,是个非常大的消耗。而且数据集越大,fock子进程的的速度就会越慢,导致阻塞主线程,虽然AOF也需要fock出子进程,但是AOF可以控制fsync的调用频率,即控制了写磁盘的速率。
-
AOF
AOF不同于RDB,AOF会将每次操作的命令记下,然后以fync的频率将命令持久化至磁盘,AOF提供三种策略将命令持久化至磁盘:
- 每秒fsync一次,这是效率与备份达到平衡的状态,最多丢失1s的数据(默认)
- 从不sync,将数据交给了操作系统,由操作系统决定是否写入磁盘,容易丢数据
- 每次新命令都会进行fsync至磁盘,速度最慢,但是最安全
优点:AOF不太容易丢失数据,如果丢失数据可能也只是很短暂的时间,写磁盘以Append-Only的方式进行追加,这比随机写磁盘效率要高几倍不止。AOF命令更容易读懂。
缺点:
- AOF文件过大(Redis通过Rewrite方式去不断进行AOF文件重写)
三、Redis过期机制
一定要分清楚过期机制与内存淘汰机制的区别
Redis过期机制有两种方式:主动模式、被动模式
主动模式:在查询数据的时候判断当前key是否过期,若过期则进行删除
被动模式:redis尽量以1s10次的频率去抽查20个key,判断是否过期,过期则删除,若当次过期的key超过了25%,则重复进行。
算法:
Redis采用绝对时间进行过期,而未采用相对时间进行过期,这一点原因也非常容易想到,推荐看一个算法:时间轮算法【https://zhuanlan.zhihu.com/p/356647675】
四、Redis在主从模式下如何过期
因为Rediskey的过期并不是采用相对时间而是采用了绝对时间点进行过期,那么假设在主从redis所在的服务器时钟并不同步,那么就会导致主从的数据无法到达一致性,因此Redis的从机不会进行数据的删除,而是等待主机在删除之后,将DEL命令写入AOF并且发送给从机进行执行。
五、Redis内存淘汰机制
当Redis中的数据量达到了配置中的maxmemeory时(在64位操作系统中,该配置为0标识没有内存限制,在32位系统中,Redis默认为3GB大小),就会根据所配置的策略进行数据回收
常见的策略有:
- noeviction
- allkeys-lru(最近最少使用,LRU算法是通过)
- volatile-lru
- allkeys-random
- volatile-random
- volatile-ttl
- volatile-lfu(使用频率最少)
- allkeys-lfu
六、Redis事务
相关关键字:
watch、multi、exec、discard
Redis事务只支持编译错误的事务回滚,并不支持运行时错误的事务回滚。
编译错误:如命令拼写错误
运行时错误:如操作命令与操作数据类型不相符合
关于Redis为什么不支持回滚
官网回答:不应该将开发中出现的问题留到生产环境,包括编译、运行时所执行的命令。
个人理解:在MySQL中实现事务的回滚是通过undolog来进行事务回滚的,那么本身是一个用来做缓存,数据都存放在内存中的中间件速度势必会遭受到影响。
七、Redis主从复制原理
主从复制分为增量同步与全量同步,流程如下:
每个Slave与Master都会有一个Replication id:offset这样的一个偏移量。当某个slave因为短暂的掉线重连之后,会发送psync并且携带自身的offset消息偏移量,而master也会记录offset的偏移量,并且在master的命令缓冲区中会保存起始偏移量,缓存区中保存着起始偏移量之后的所有命令,如果slave传递过来的offset在偏移量所包含的区域中,那么就会进行增量同步,master会将slave offset之后的命令发送给slave执行。
但假设slave传递来的offset没有被master缓存区所包含,那么就会进行全量同步。在进行全量同步时,master主进程会fock出一个子进程进行RDB文件的生成,最终异步的将RDB文件发送给slave,而slave也有一项配置,在RDB加载进内存之前可否使用slave旧数据进行对外服务。
当然了在全量同步的时候,master会缓冲之后的命令,用做当slave保存了rdb文件之后的增量更新。
在新版本的Redis主从复制中,
psync与sync的区别:
sync是redis老版本的同步数据的协议,但是不能支持部分重同步,因此在新版中使用的都是psync协议。
八、缓存雪崩、缓存穿透、缓存击穿
-
缓存雪崩
原因:缓存雪崩主要是因为大量的key在同一时刻过期而导致的大量请求打到数据库
解决方案:
- 打底解决肯定是要通过分布式锁来进行解决
- 为每个key进行过期的时候随机增加一点时间,从而避免大量key同时过期
- key永不过期但是这种方式是最不成熟的,这会导致redis内存压力过大
-
缓存穿透
原因:由于一些不合法的请求,比如请求一些数据库中根本不会存在(redis中自然也就不会存在)的数据。
解决方案:
- 增加接口校验
- 对于一些非法的请求可以添加一些假数据缓存到redis