八股文之redis

miloyang
0 评论
/ /
295 阅读
/
7324 字
09 2024-01

RDB 和AOF 的实现原理以及优缺点

RDB 和AOF 都是Redis 里面提供的持久化机制,RDB 是通过快照方式实现持久化、AOF 是通过命令追加的方式实现持久化。

RDB 持久化机制会根据快照触发条件,fork一个子进程,把内存里面的数据快照写入到磁盘,以二进制的压缩文件进行存储。 tqM0LHgKyCu2s3BPFI.png

RDB 快照的触发方式有很多,比如

  • 执行bgsave 命令触发异步快照,执行save 命令触发同步快照,同步快照会阻塞 客户端的执行指令。
  • 根据redis.conf 文件里面的配置,自动触发bgsave
  • 主从复制的时候触发

而AOF 持久化机制是近乎实时的方式来完成持久化的,就是客户端执行一个数据变更的操作,Redis Server 就会把这个命令追加到aof 缓冲区的末尾,然后再把缓冲区的数据写入到磁盘的AOF 文件里面,至于最终什么时候真正持久化到磁盘,是根据刷盘的策略来决定的。

为了避免追加的方式导致AOF 文件过大的问题,Redis 提供了AOF 重写机制,也就是说当AOF 文件的大小达到某个阈值的时候,就会把这个文件里面相同的指令进行压缩。

AOF 和RDB 的优缺点分析 我认为RDB 和AOF 的优缺点有两个。

  • RDB 是每隔一段时间触发持久化,因此数据安全性低,AOF 可以做到实时持久化,数据安全性较高。
  • RDB 文件默认采用压缩的方式持久化,AOF 存储的是执行指令,所以RDB 在数据恢复的时候性能比AOF 要好

具体得看业务用哪种,如果是分布式,建议都开启,主节点使用AOF,从节点使用RDB,因为RDB在fork子子进程的时候,可能会出现短时间的阻塞。

Redis 为什么这么快?

主要是内存:

  • 采用内存,内存IO速度本身很快,可以避免频繁的进行写盘操作,大大降低响应时间。所以内存的瓶颈只是内存大小。
  • 网络,采用了多路复用机制,提高了并发处理的连接数,解析客户端命令和写响应结果是多进程的。
  • redis的核心业务部分(命令处理)是单线程的,但是整个redis架构,有些架构是多线程的。
  • Redis 本身的数据结构也做了很多的优化,比如压缩表、跳跃表等方式降低了时间复杂读,同时还提供了不同时间复杂度的数据类型。使得开发人员能够有更多合适的选择。

Redis 的内存淘汰算法和原理?

redis提供了两种策略:过期策略和淘汰策略。

  • 过期策略

    相信这个大部分业务都有涉及过,就是通过expire命令给redis的key设置TTL。原理就是redis object结构体中,由两个Dict,一个是来记录key-value的,一个是记录key-TTL的。如何知道是否过期,那就拿key去key-ttl中查,然后判断即可。
    这个有涉及到删除原理,惰性删除和周期删除。

    • 惰性删除,就是在访问一个key的时候,检查该key的存活事件,如果已经过期了才执行删除。 访问一个key,就是增删改查。
    • 周期删除,定期抽样部分key,判断是否过期,如果过期则删除。
  • 淘汰策略

    redis中,支持8种不同的策略来选择要删除的key,最常见的就是LFU的访问次数,叫做逻辑访问次数,是因为并不是每次key被访问都计数,而是通过运算.使用了两个双向链表形成一个二维双向链表,一个用来保存访问频率,另一个用来保存访问频率相同的所有元素。当添加元素的时候,访问次数默认为1,于是找到相同访问频次的节点,然后添加到相同频率节点对应的双向链表头部。当元素被访问的时候,就会增加对应key 的访问频次,并且把当前访问的节点移动到下一个频次节点。有可能出现某个数据前期访问次数很多,然后后续就一直不用了,如果单纯按照访问频率,这个key 就很难被淘汰,所以在LFU 中通过使用频率和上次访问时间来标记数据的热度,如果有有读写,就增加访问频率,如果一段时间内没有读写,就减少访问频率。

Redis 和Mysql 如何保证数据一致性。

Redis 用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库IO,还可以提升数据的IO 性能。这是它的整体架构。

当应用程序需要去读取某个数据的时候,首先会先尝试去Redis 里面加载,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到Redis里面。

所以有一份数据同时保持在数据库和redis,当时数据发生变化的时候,需要同时更新redis和mysql,更新是有先后顺序的,所以可能会造成数据不一致的情况。 一般来说有几种解决方案:

  • 先更新数据库,删除缓存,等到下一次查库的时候更新缓存。这样的问题就是在更新数据的时候,缓存还未删除,会导致数据不一致。
  • 先删除缓存,在更新数据库,这样的问题就是删除缓存后,还没有更新数据库,但是下一个线程过来更新了缓存,会造成数据不一致。
  • 采用mq的,实现最终一致性,就是把更新的数据丢到mq中,然后由mq去删除缓存。
  • 通过canal组件,来更新redis

这个需要根据业务来,一般情况下,做到先更数据库,再删除缓存是比较常见的,因为删除缓存是内存操作,操作很快,影响比较少,改动量小。

Redis 存在线程安全问题吗?为什么?

Redis Server 本身是一个线程安全的K-V 数据库,也就是说在Redis Server 上执行的指令,不需要任何同步机制,不会存在线程安全问题。

虽然Redis 6.0 里面,增加了多线程的模型,但是增加的多线程只是用来处理网络IO 事件,对于指令的执行过程,仍然是由主线程来处理,所以不会存在多个线程通知执行操作指令的情况。

至于为什么Redis 没有采用多线程来执行指令,我认为有几个方面的原因。

  • Redis Server 本身可能出现的性能瓶颈点无非就是网络IO、CPU、内存。但是CPU不是Redis 的瓶颈点,所以没必要使用多线程来执行指令。
  • 如果采用多线程,意味着对于redis 的所有指令操作,都必须要考虑到线程安全问题,也就是说需要加锁来解决,这种方式带来的性能影响反而更大。

虽然Redis Server 中的指令执行是原子的,但是如果有多个Redis 客户端同时执行多个指令的时候,就无法保证原子性。 假设两个redis client 同时获取Redis Server 上的key1, 同时进行修改和写入,因为多线程环境下的原子性无法被保障,以及多进程情况下的共享资源访问的竞争问题,使得数据的安全性无法得到保障。

对于客户端层面的线程安全性问题,解决方法有很多,比如尽可能的使用Redis里面的原子指令,或者对多个客户端的资源访问加锁,或者通过Lua 脚本来实现多个指令的操作等等。

什么是热Key 问题,如何解决热key 问题

关于热点key,简单理解,就是访问频率发很高的key,由于访问量较大,热点key 有 可能会导致服务器资源不足出现宕机的问题。 热点key 一般有几种产生的情况:

  • 在一些高并发的场景中,比如秒杀、热搜等。
  • 由于资源分配不平衡导致访问热点问题

一般情况下,我们可以通过监控工具、或者客户端程序上报的方式来识别热点key。由于热点Key 可能存在的影响, 所以我们一般需要去尽可能的规避,从而避免出现性能上和稳定性问题,通常的解决方案有以下几种:

  • 把热点key 进行拆分,实现并发流量的分流
  • 使用多级缓存的设计,通过增加本地缓存的方式减少目标节点的访问
  • 通过对访问频率较高的节点进行扩容,通过负载均衡的方式分散流量

Redis 遇到Hash 冲突怎么办?

hash 冲突,是指不同的key,计算出来的结果落到了同一个hash 桶中。 Redis 为了解决哈希冲突,采用了链式寻址法,也就是采用链表的方式来保存同一个hash 桶中的多个元素。

如果出现大量的key 的冲突导致链表过长的情况下,会导致数据的检索效率变慢,Redis 是怎么解决这个问题的呢?为了保持高效,Redis 会对哈希表做rehash 操作,也就通过增加哈希桶来减少冲突。为了rehash 更高效,Redis 还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表

Redis 中的哨兵选举算法是如何实现的?

哨兵是一个单独的进程,所以为了保证哨兵的可靠性,我们也会对哨兵做部署集群。

当Redis 集群中的Master 节点出现故障,哨兵节点检测到以后,会从Redis 集群中的其他Slave 节点选举出一个作为新的Master。具体的判断依据有两个部分:

第一部分: 筛选
第二部分: 综合评估

在筛选阶段,会过滤掉不健康的节点,比如(下线或者断线),或者没有回复Sentinel哨兵心跳响应的Slave 节点。同时,还会评估实例过往的网络连接情况,如果在一定时间内,Slave 和Master 经常性断链,而且超出了一定的阈值,也不会考虑。经过筛选后,留下的都是健康的节点了。接下来就对健康节点进行综合评估,具体有三个维度,按照顺序来判断。

  • 根据Slave 优先级来判断,通过slave-priority 配置项(redis.conf),可以给不同的从库设置不同优先级,优先级高的优先成为master。

  • 选择数据偏移量差距最小的,即slave_repl_offset 与master_repl_offset 进度差距,其实就是比较slave 与原master 复制进度差距,避免丢失过多数据的问题。

  • slave runID,在优先级和复制进度都相同的情况下,选用runID 最好的,runID越小说明创建时间越早,优先选为master。

经过以上步骤,就可以选举出新的Master 节点了。

击穿、雪崩、穿透,如何避免?

  • 缓存穿透

    指的是客户端请求的数据,在缓存中和数据库中都不存在,这样的缓存永远不会生效,这些请求直接打到数据库来了。

    避免: 缓存一个TTL的空对象到缓存中,或者是使用布隆过滤器。

  • 缓存雪崩,

    指的是同一时段大量的缓存key同时失效,几万或者几十万的key或者redis服务器宕机了,导致大量请求都到达了数据库,带来数据库的巨大压力。

    避免: 造成这方面的原因,一个是同时失效,一个是服务可靠性。
    不同的key的TTL添加随机值,防止同时失效
    提高redis集群提高服务的可用性
    给缓存业务添加降级限流策略
    给业务添加多级缓存,不单单添加是redis缓存,可以nginx、cdn等等其他的缓存同时添加。可有效的避免雪崩了。

  • 缓存击穿问题,

    也被成为热点key问题,就是一个被高并发访问并且缓存重建业务复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。 避免:互斥锁,利用锁的方式,只让一个线程创建就好了,其余人搁那等着这个线程创建好数据吧。 逻辑过期,在缓存数据中的value再加一个expire,这并不是ttl,而是一个逻辑的过期时间。然后一个重建,其余都返回原有的数据。为什么其他数据知道有人在重建呢?加锁,你获取锁失败,说明有人在处理了。

Redis 哨兵机制和集群有什么区别?

Redis 集群有两种,一种是主从复制,一种是Redis Cluster。

Redis 哨兵集群是基于主从复制来实现的,所以它可以实现读写分离,分担Redis读操作的压力而Redis Cluster 集群的Slave 节点只是实现冷备机制,它只有在Master 宕机之后才会工作。

Redis 哨兵集群无法在线扩容,所以它的并发压力受限于单个服务器的资源配置。Redis Cluster 提供了基于Slot 槽的数据分片机制,可以实现在线扩容提升写数据的性能

从集群架构上来说,Redis 哨兵集群是一主多从, 而Redis Cluster 是多主多从

Redis 主从复制的原理

Redis 主从复制,是指在Redis 集群里面(如图),Master 节点和Slave 节点数据同步的一种机制。简单来说就是把一台Redis 服务器的数据,复制到其他Redis 服务器中。其中负责复制数据的来源称为master,被动接收数据并同步的节点称为slave

复制分为全量和增量复制
全量复制具体的工作原理是:

  1. Slave 向Master 发送SYNC 命令,Master 收到命令以后生成数据快照
  2. 把快照数据发送给Slave 节点,Salve 节点收到数据后丢弃旧的数据,并重新载入新的数据。

增量复制,就是指Master 收到数据变更之后,把变更的数据同步给所有Slave 节点。 增量复制的原理是,Master 和Slave 都会维护一个复制偏移量(offset),用来表示Master 向Slave 传递的字节数。每次传输数据,Master 和Slave 维护的Offset 都会增加对应的字节数量。Redis 只需要根据Offset 就可以实现增量数据同步了。

为什么Redis 集群的最大槽数是16384 个?

背景: Redis-Cluster 集群模式使用了哈希槽(hash slot)来实现数据的分片,hash slot 的默认长度是16384.应用程序去存储一个key 的时候,会通过CRC16 计算后取模路由到对应hash slot 所在的节点。

nDQjjWZPojiwS3xgmR29s.png

回答:
一般情况下,对于一个中间件中某些特定的阈值的设计,都是通过相关计算和测试得到的一个相对比较合适的值。
对于Redis 中的Hash Slot 为什么是16384 这个问题,我认为有几个考虑因素:

对于网络通信开销平衡:Redis 集群中每个节点会发送心跳消息,而心跳包中会携带节点的完整配置,它能够以幂等的方式来更新配置 。 如果采用16384 个插槽,而每个插槽信息占用的位数为1,因此每个节点需要维护的配置信息占用空间大小就是16384/节点数/8KB,假设是3 个节点的集群,则每个节点需要维护的配置信息占用空间大小为2KB

16384 个插槽可以确保每个master 节点都有足够的插槽,同时也可以保证插槽数目不会过多或过少,从而保证了Redis Cluster 的稳定性和高性能。总之,16384 个槽的数量是经过考虑和实践得出的,既可以满足Redis 集群的性能要求,又可以保证管理复杂度和通信开销的可控性。

当节点删除、新增的时候,都会做整个集群的数据再次散列。

人未眠
工作数十年
脚步未曾歇,学习未曾停
乍回首
路程虽丰富,知识未记录
   借此博客,与之共进步