likes
comments
collection
share

Redis核心技术笔记03-04

作者站长头像
站长
· 阅读数 13

03 单线程

Redis的单线程指的是网络IO和键值对读写是由一个线程来完成的;其他功能如持久化、异步删除、集群同步等是由额外的线程执行的。注:Redis6.0网络IO已改为多线程执行,以解决单线程的性能瓶颈。

多线程的开销

并发访问控制问题:系统中通常会存在被多线程同时访问的共享资源,比如一个共享的数据结构。当有多个线程要修改这个共享资源时,为了保证共享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,就会带来额外的开销。

Redis高效原因

Redis使用单线程模型能达到每秒十万级的处理能力,原因有:

  1. 大部分操作在内存上完成
  2. 高效的数据结构,如哈希表和跳表
  3. 多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐

    IO模型和阻塞点

    Redis工作流程:

  4. 网络IO处理:

    1. 监听客户端请求:bind/listen
    2. 和客户端建立连接:accept
    3. 从socket中读取请求:recv
    4. 解析客户端发送请求:parse
  5. 键值数据读写:get
  6. 网络IO处理:send阻塞点:accept和recv
  7. 当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞在 accept() 函数这里,导致其他客户端无法和 Redis 建立连接。
  8. 当 Redis 通过 recv() 从一个客户端读取数据时,如果数据一直没有到达,Redis 也会一直阻塞在 recv()。

    非阻塞模型

    套接字

    简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。

    socket模型

    在 socket 模型中,不同操作调用后会返回不同的套接字类型。socket() 方法会返回主动套接字,然后调用 listen() 方法,将主动套接字转化为监听套接字,此时,可以监听来自客户端的连接请求。最后,调用 accept() 方法接收到达的客户端连接,并返回已连接套接字。

    非阻塞模式

    当 Redis 调用 accept() 但一直未有连接请求到达时,Redis 线程可以返回处理其他操作,而不用一直等待。Redis 调用 recv() 后,如果已连接套接字上一直没有数据到达,Redis 线程同样可以返回处理其他操作。

    IO多路复用机制

    Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。
    事件回调机制
  9. select/epoll 一旦监测到 FD 上有请求到达时,就会触发相应的事件。所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,一旦监测到fd上有请求到达时,就会触发相应的事件,当相应的事件发生时会调用回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
  10. 这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。

    • Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。
    • Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。
    • 因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。

    04 AOF日志

    AOF(Append Only File)是写后日志,Redis先执行命令把数据写入内存,然后才记录日志。

    实现原理

    AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。示例:

    set testkey testvalue

    对应AOF文件

    *3
    $3
    set
    $7
    testkey
    $9
    testvalue
  11. “*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。
  12. 数字”表示这部分中的命令、键或值一共有多少字节。注意:为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。

AOF的好处:

  1. 可以避免出现记录错误命令的情况
  2. 它是在命令执行后才记录日志,所以不会阻塞当前的写操作。

风险:

  1. 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
  2. AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这两个风险都是和 AOF 写回磁盘的时机相关的。

    三种写回策略

    AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。

  3. Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
  4. Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
  5. No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。策略分析:
  6. 同步写回:可靠性高,数据基本不丢失;但是每个写命令都要落盘,性能影响较大
  7. 每秒写回:性能适中;宕机时丢失1秒内的数据
  8. 操作系统控制写回:性能好;宕机时丢失数据较多

    文件过大问题

    随着接收的写命令越来越多,AOF 文件会越来越大。这也就意味着,我们一定要小心 AOF 文件过大带来的性能问题。

  9. 文件系统限制,无法保存过大文件
  10. 文件太大,追加命令记录效率变低
  11. 发生宕机故障恢复时,AOF记录要一个个重新执行,文件太大恢复过程很慢

    AOF重写机制

    AOF重写机制指的是,对过大的AOF文件进行重写,以此来压缩AOF文件的大小。

    实现方法

    检查当前键值数据库中的键值对,记录键值对的最终状态,从而实现对某个键值对重复操作后产生的多条操作记录压缩成一条的效果。也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。

    是否阻塞

    重写过程是由主线程 fork 出后台的子进程 bgrewriteaof 来完成的

    重写过程

    一个拷贝,两处日志:

  12. 每次执行重写时,fork 会把主线程的内存(页表:虚拟映射关系)拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。
  13. 因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
  14. 这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。

    小结

  15. 系统设计中的一个重要原则 ,即 trade-off,或者称为“取舍”,指的就是在性能和可靠性保证之间做取舍。
  16. 最新持久化方案:前置RDB快照+AOF文件
  17. 重新过程潜在阻塞风险:

    1. fork会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的
    2. bigkey重新申请大块内存耗时会变长,可能会产阻塞风险(所以需要关闭Huge Page机制)

    参考

    套接字百科