八股文|后端|Redis | 答案
3197
2023.02.18
2023.04.12
发布于 未知归属地

一.Redis 的五种数据结构 + 使用场景
1.string
1)使用动态字符串实现
2)val最大512MB
2.list
1)使用压缩列表 或 链表实现,3.2之后,使用快速列表(压缩列表+双向链表的混合体)lpush,rpop,llen:O(1)
2)可做消息队列(由于Redis单线程,所以不存在同步问题)
3.hash
1)使用压缩列表 或 哈希字典实现
4.set
1)使用整数数组(所有元素都是整数 且 元素个数不超过512) 或 哈希字典实现
2)去重的场景
5.sorted set
1)使用压缩列表 或 哈希字典(key:val, val: score)+跳表实现
2)需要有序集合的场景,如微博

二.Redis 为什么这么快?

  • 基于内存,非常快速;
  • 数据结构简单,Redis中的数据结构是专门进行设计的;
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • 使用I/O多路复用模型,非阻塞IO;
  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

三.Redis 内存淘汰策略
1.noeviction表示不淘汰数据,当缓存数据满了,有新的写请求进来,Redis不再提供服务,而是直接返回错误。(默认的淘汰策略)
2.根据过期时间的淘汰策略

  • volatile-random、volatile-ttl、volatile-lru、volatile-lfu 四种策略是针对已经设置了过期时间的键值对。到键值对的到期时间到了或者Redis内存使用量达到了maxmemory阈值,Redis会根据这些策略对键值对进行淘汰;
  • volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
  • volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。

对于设置的过期时间的数据,系统会定期删除 或 惰性删除
1)定期删除(贪婪策略):

  • 从过期字典中随机抽取20个键;
  • 从20个密钥中删除过期的密钥;
  • 如果过期密钥的比例超过1/4,请重复执行步骤1。

2)惰性删除:
当客户端访问数据时,redis检查数据的过期日期并删除它而不返回任何内容。

3.所有数据范围内的淘汰策略

  • allkeys-lru、allkeys-random、allkeys-lfu 这三种策略淘汰的数据范围扩大到所有的键值对,无论这些键值对是否设置了过期时间,筛选数据进行淘汰的规则是:
  • allkeys-random 策略,从所有键值对中随机选择并删除数据;
  • allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。
  • allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。

四.Redis 持久化有几种方式?
1.快照RDB
通过操作系统的多进程写时复制(COW)实现快照持久化:
1)持久化时调用fork()产生一个子进程,快照持久化交给子进程处理(子进程刚产生时,和父进程共享内存中的代码段和数据段,在进程分离的一瞬间,内存几乎无增长);
2)子进程做持久化不会修改内存中数据,但父进程会修改,这会导致数据段页面分离(当父进程修改某页数据时,该页面会被复制一份分离出来;子进程页面没有变化);同时由于冷数据占比高,所以被分离出来的页面很少。

  1. AOF
    先执行指令再写日志
    1)由于AOF文件会比较大,为了避免写入无效指令(错误指令),必须先做指令检查 (如何检查,只能先执行了。因为语法级别检查并不能保证指令的有效性,比如删除一 个不存在的key。而MySQL这种是因为它本身就维护了所有的表的信息,所以可以语法 检查后过滤掉大部分无效指令直接记录日志,然后再执行。)
    2)Redis本身定位内存数据库,速度是第一要素。AOF是个IO操作,会拖性能后腿。 Redis允许自定义AOF同步频率策略:Always(最安全,也存在丢失数据的情况,一个 事件循环的频率同步一次)、Everysec(常用,每秒同步一次)、No(最不安全,让操 作系统决定刷盘频率) 来满足不同的数据安全需求。

五.Redis 主从复制
最终一致性:异步同步(客户端在主节点修改了数据后,立即返回),不满足一致性要求,所以存在丢失数据的情况;从节点会努力追赶主节点,最终保持与主节点一致。
同步策略为:快照同步 + 增量同步
1)增量同步(主节点将修改指令存在内存buffer中,异步将指令同步到从节点,从节点一边执行同步指令,一边反馈同步到哪了——偏移量),如果buffer写满覆盖了未同步的指令,则通过快照同步;
2)√快照同步:非常消耗资源(首先主节点bgsave,再将快照全部传送到从节点,从节点加载快照完成后通知主节点继续增量同步。有可能陷入快照同步死循环,所以务必配置合适的buffer大小参数);
Redis2.8.18版本开始,支持无盘复制(解决大量io的问题,主节点直接通过套接字将快照发生到从节点:一边遍历内存一边发送数据;从节点和之前一样,先将全部内容存入磁盘,再一次性加载)
主从复制非必须,如果只做缓存,可以不需要。

六.Redis 常见性能问题和解决方案?
1.Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
2.Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。
3.Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
4.Redis主从复制的性能问题,第一次Slave向Master同步的实现是:Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发送给各个Slave。不管什么原因导致Slave和Master断开重连都会重复以上过程。Redis的主从复制是建立在内存快照的持久化基础上,只要有Slave就一定会有内存快照发生。虽然Redis宣称主从复制无阻塞,但由于Redis使用单线程服务,如果Master快照文件比较大,那么第一次全量传输会耗费比较长时间,且文件传输过程中Master可能无法提供服务,也就是说服务会中断,对于关键服务,这个后果也是很可怕的。
以上1.2.3.4根本问题的原因都离不开系统io瓶颈问题,也就是硬盘读写速度不够快,主进程 fsync()/write() 操作被阻塞(如AOF的fsync操作)。
解决方案:主节点不进行持久化操作,从节点进行。
5.单点故障问题,由于目前Redis的主从复制还不够成熟,所以存在明显的单点故障问题,这个目前只能自己做方案解决,如:主动复制,Proxy实现Slave对Master的替换等,这个也是Redis作者目前比较优先的任务之一

七.什么是缓存穿透?怎么解决?
我们使用Redis大部分情况都是通过Key查询对应的值,假如发送的请求传进来的key是不存在Redis中的,那么就查不到缓存,查不到缓存就会去数据库查询。假如有大量这样的请求,这些请求像“穿透”了缓存一样直接打在数据库上,这种现象就叫做缓存穿透。
解决方案:
1.把无效的Key存进Redis中。如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。这种处理方式肯定是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
2.使用布隆过滤器。布隆过滤器的作用是某个 key 不存在,那么就一定不存在,它说某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回。

八.什么是缓存雪崩?该如何解决?
当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。
解决方案:
1在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。
如果真的发生了缓存雪崩,有没有什么兜底的措施?
2使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
3提高数据库的容灾能力,可以使用分库分表,读写分离的策略。
4为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。

九.Redis主从切换?
1.通过VIP+心跳实现自动主从切换;
2.通过使用Sentinel实现自动主从切换(类似zookeeper集群):
客户端首先连接Sentinel,通过Sentinel获取主节点地址;当主节点更换时,客户端通过重新从Sentinel获取主节点地址。
具体实现为(如redis-py):
客户端在建立新连接时,会去查主节点地址,如果查主节点地址变更了,就断开所有之前的连接,重新用新地址建立新连接;如果旧主节点挂了,之前的连接都会被关闭,重连时就会使用新地址;如果旧主节点没有挂掉但主节点已经切换,在之前连接执行写操作时,会返回ReadOnlyError异常,所以可以在异常里将所有之前连接关闭,后续指令会进行重连。

对以上答案有任何疑问欢迎在评论区指出讨论!
评论 (8)