10 | 第1~9讲课后思考题答案及常见问题答疑

188 阅读8分钟

是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的1.问题:和跟 Redis 相比,SimpleKV 还缺少什么?


2.问题:整数数组和压缩列表作为底层数据结构的优势是什么?

  • 整数数组和压缩列表的设计,充分体现了 Redis“又快又省”特点中的“省”,也就是节省内存空间\

  • 因为是连续的内存空间,不会造成内存碎片

  • 同时由于不使用指针,避免了指针带来的额外空间开销**
    **

**
**

3.问题:Redis 基本 IO 模型中还有哪些潜在的性能瓶颈?

  • Redis 单线程性能的影响\

  • 在 Redis 基本 IO 模型中,主要是主线程在执行操作,任何耗时的操作,例如 bigkey、全量返回等操作,都是潜在的性能瓶颈\

  • 可以新的数据scan等,避免阻塞主线程

4.问题 :AOF 重写过程中有没有其他潜在的阻塞风险?

  • AOF:后写操作日志

  • 风险

    • 复制大页表带来的阻塞(复制页表)\

      • Redis 主线程 fork 创建 bgrewriteaof 子进程时,内核需要创建用于管理子进程的相关数据结构PCB,这个创建和拷贝过程由内核执行,是会阻塞主线程的。子进程要拷贝父进程的页表,这个过程的耗时和 Redis 实例的内存大小有关。如果 Redis 实例内存大,页表就会大,fork 执行时间就会长,这就会给主线程带来阻塞风险。\
    • 写big key带来的阻塞(分配新的页表)

      • bgrewriteaof 子进程会和主线程共享内存,当主线程收到新写修改操作,主线程会申请新的内存空间(复制线程只会读),如果操作的是 bigkey,主线程会因为申请大空间而面临阻塞风险(新写)

5.问题:AOF 重写为什么不共享使用 AOF 本身的日志?

  • 会造成阻塞,redis不可接收阻塞
  • 如果都用 AOF 日志的话,主线程要写,bgrewriteaof 子进程也要写,这两者会竞争文件系统的锁,这就会对 Redis 主线程的性能造成影响\

6.问题:为什么主从库间的复制不使用 AOF?

  • AOF:后写操作日志(需要翻译后执行)

  • RDB 文件是二进制文件,无论是要把 RDB 写入磁盘,还是要通过网络传输 RDB,IO 效率都比记录和传输 AOF 的高\

  • 在从库端进行恢复时,用 RDB 的恢复效率要高于用 AOF\

7.问题 :在主从切换过程中,客户端能否正常地进行请求操作呢?

  • 可以在从库上执行读请求
  • 不可以在主库上执行写请求

8.问题 :如果想要应用程序不感知服务的中断,还需要哨兵或客户端再做些什么吗?

  • 客户端需要能缓存应用发送的写请求(异步写数据)\

  • 主从切换完成后,客户端要能和新主库重新建立连接,哨兵需要提供订阅频道,让客户端能够订阅到新主库的信息\

    • 哨兵提供订阅频道,客户端订阅新主库的信息
    • 客户端主动和哨兵通信,询问新主库信息

9.问题:5 个哨兵实例的集群,quorum 值设为 2。在运行过程中,如果有 3 个哨兵实例都发生故障了,此时,Redis 主库如果有故障,还能正确地判断主库“客观下线”吗?如果可以的话,还能进行主从库自动切换吗?

  • 可以判定客观下线

    • 哨兵实例,个数正好等于 quorum 值,所以还能正常判断主库是否处于“客观下线”状态\
  • 不可以进行主从库自动切换

    • 不能获到半数以上的哨兵投票赞成

10.问题 :哨兵实例是不是越多越好呢?如果同时调大 down-after-milliseconds 值,对减少误判是不是也有好处?

  • 哨兵实例越多,误判率会越低\

  • 但是在判定主库下线和选举 Leader 时,实例需要拿到的赞成票数也越多,等待所有哨兵投完票的时间可能也会相应增加,主从库切换的时间也会变长(会不断同票)\

  • 客户端容易堆积较多的请求操作,可能会导致客户端请求溢出,从而造成请求丢失\

  • 调大 down-after-milliseconds 后,可能会导致这样的情况:主库实际已经发生故障了,但是哨兵过了很长时间才判断出来\

11.问题:为什么 Redis 不直接用一个表,把键值对和实例的对应关系记录下来?

  • 键值对-实例的关系表大,增加额外存储空间也会增加

  • 一旦改动,影响的key-value范围大,阻塞主线程

  • 但是哈希槽的个数要比键值对的个数少很多\

  • 无论是修改哈希槽和实例的对应关系,还是使用额外空间存储哈希槽和实例的对应关系,都比直接记录键值对和实例的关系的开销小得多\

12.问题 :rehash 的触发时机和渐进式执行机制

  • Redis 会使用装载因子(load factor)来判断是否需要做 rehash\

  • 装载因子的计算方式是,哈希表中所有 entry 的个数除以哈希表的哈希桶个数\

  • 触发条件

    • 装载因子≥1(如果是均匀分布,没有链式存储),同时,哈希表被允许进行 rehash\

      • 在进行 RDB 生成AOF 重写时,哈希表的 rehash 是被禁止的,这是为了避免对 RDB 和 AOF 重写造成影响\

      • 写AOF可以rehash

    • 装载因子≥5\

      • 当前保存的数据量已经远远大于哈希桶的个数,哈希桶里会有大量的链式哈希存在,性能会受到严重影响,此时,就立马开始做 rehash

13.问题 :采用渐进式 hash 时,如果实例暂时没有收到新请求,是不是就不做 rehash 了?

  • 会有定时任务进行rehash\

14.问题:主线程、子进程和后台线程的联系与区别

  • 进程一般是指资源分配单元\

  • 线程一般是指 CPU 进行调度和执行的实体\

  • 主线程,进程的第一个线程

  • redis主线程

    • 接收请求和处理请求操作
  • 子线程

    • 创建 RDB 的后台子进程,同时由它负责在主从同步时传输 RDB 给从库\

    • 通过无盘复制方式传输 RDB 的子进程\

    • bgrewriteaof 子进程\

    • 异步删除任务

    • bgsave  rdb写

    • bgrewriteaof 文件重写\

\

15.问题:写时复制的底层实现机制

  • 主线程 fork 出 bgsave 子进程后,bgsave 子进程实际是复制了主线程的页表\

  • bgsave 子进程生成 RDB 时,就可以根据页表读取这些数据,再写入磁盘中\

  • 主线程在有写操作时,才会把这个新写或修改后的数据写入到一个新的物理地址中,并修改自己的页表映射\

\

16.问题:replication buffer 和 repl_backlog_buffer 的区别

  • replication buffer(为什么不用偏移量?需要开辟一块全新的内存空间)

    • 主从库进行全量复制时(记录中间有一段空挡)

    • 主库用于和从库连接的客户端buffer\

    • Redis 通过 client_buffer 配置项来控制这个 buffer 的大小\

    • 内存空间不与其他从库共享

  • repl_backlog_buffer\

    • 是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的\

    • 所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset)发给主库,主库就可以和它独立同步\

\

17.使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB。当时 Redis 主要以修改操作为主,写读比例差不多在 8:2 左右,也就是说,如果有 100 个请求,80 个请求执行的是修改操作。在这个场景下,用 RDB 做持久化有什么风险吗?

  • RDB内存快照,将内存数据swap到磁盘中

  • 风险点

    • 内存不足的风险

      • 写的比例为 80%,会导致开辟2GB*0.8的新页表,大约是 1.6GB

      • 如果云主机开启了 Swap 机制,读写性能下降\

      • 如果云主机没有开启 Swap,会直接触发 OOM,导致redis挂掉\

    • 主线程和子进程竞争使用 CPU 的风险(可以扩大cpu处理核数)

      • 生成 RDB 的子进程需要 CPU 核运行\

      • 主线程本身也需要 CPU 核运行\

      • Redis 还启用了后台线程\

      • 两核cpu会产生资源竞争