共计 3227 个字符,预计需要花费 9 分钟才能阅读完成。
03 单线程
Redis 的单线程指的是网络 IO 和键值对读写是由一个线程来实现的;其余性能如长久化、异步删除、集群同步等是由额定的线程执行的。
注:Redis6.0 网络 IO 已改为多线程执行,以解决单线程的性能瓶颈。
多线程的开销
并发访问控制问题:
零碎中通常会存在被多线程同时拜访的共享资源,比方一个共享的数据结构。
当有多个线程要批改这个共享资源时,为了保障共享资源的正确性,就须要有额定的机制进行保障,而这个额定的机制,就会带来额定的开销。
Redis 高效起因
Redis 应用单线程模型能达到每秒十万级的解决能力,起因有:
- 大部分操作在内存上实现
- 高效的数据结构,如哈希表和跳表
-
多路复用机制,使其在网络 IO 操作中能并发解决大量的客户端申请,实现高吞吐
IO 模型和阻塞点
Redis 工作流程:
-
网络 IO 解决:
- 监听客户端申请:bind/listen
- 和客户端建设连贯:accept
- 从 socket 中读取申请:recv
- 解析客户端发送申请:parse
- 键值数据读写:get
- 网络 IO 解决:send
阻塞点:accept 和 recv - 当 Redis 监听到一个客户端有连贯申请,但始终未能胜利建设起连贯时,会阻塞在 accept() 函数这里,导致其余客户端无奈和 Redis 建设连贯。
-
当 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 提供了基于事件的回调机制,即针对不同事件的产生,调用相应的处理函数。事件回调机制
- select/epoll 一旦监测到 FD 上有申请达到时,就会触发相应的事件。
所有增加到 epoll 中的事件都会与设施 (网卡) 驱动程序建设回调关系,也就是说,一旦监测到 fd 上有申请达到时,就会触发相应的事件,当相应的事件产生时会调用回调办法。这个回调办法在内核中叫 ep_poll_callback, 它会将产生的事件增加到 rdlist 双链表中。 -
这些事件会被放进一个事件队列,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
- “*3”示意以后命令有三个局部,每局部都是由“$+ 数字”结尾,前面紧跟着具体的命令、键或值。
- 数字”示意这部分中的命令、键或值一共有多少字节。
留神:
为了防止额定的查看开销,Redis 在向 AOF 外面记录日志的时候,并不会先去对这些命令进行语法查看。
所以,如果先记日志再执行命令的话,日志中就有可能记录了谬误的命令,Redis 在应用日志复原数据时,就可能会出错。
写后日志这种形式,就是先让零碎执行命令,只有命令能执行胜利,才会被记录到日志中,否则,零碎就会间接向客户端报错。
AOF 的益处:
- 能够避免出现记录谬误命令的状况
- 它是在命令执行后才记录日志,所以不会阻塞以后的写操作。
危险:
- 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有失落的危险。
-
AOF 尽管防止了对以后命令的阻塞,但可能会给下一个操作带来阻塞危险。
这两个危险都是和 AOF 写回磁盘的机会相干的。三种写回策略
AOF 机制给咱们提供了三个抉择,也就是 AOF 配置项 appendfsync 的三个可选值。
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统管制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
策略剖析: - 同步写回:可靠性高,数据根本不失落;然而每个写命令都要落盘,性能影响较大
- 每秒写回:性能适中;宕机时失落 1 秒内的数据
-
操作系统管制写回:性能好;宕机时失落数据较多
文件过大问题
随着接管的写命令越来越多,AOF 文件会越来越大。这也就意味着,咱们肯定要小心 AOF 文件过大带来的性能问题。
- 文件系统限度,无奈保留过大文件
- 文件太大,追加命令记录效率变低
-
产生宕机故障复原时,AOF 记录要一个个从新执行,文件太大复原过程很慢
AOF 重写机制
AOF 重写机制指的是,对过大的 AOF 文件进行重写,以此来压缩 AOF 文件的大小。
实现办法
查看以后键值数据库中的键值对,记录键值对的最终状态,从而实现对某个键值对反复操作后产生的多条操作记录压缩成一条的成果。
也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。是否阻塞
重写过程是由主线程 fork 出后盾的子过程 bgrewriteaof 来实现的
重写过程
一个拷贝,两处日志:
- 每次执行重写时,fork 会把主线程的内存(页表:虚构映射关系)拷贝一份给 bgrewriteaof 子过程,这外面就蕴含了数据库的最新数据。
- 因为主线程未阻塞,依然能够解决新来的操作。此时,如果有写操作,第一处日志就是指正在应用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即便宕机了,这个 AOF 日志的操作依然是齐全的,能够用于复原。
-
这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会失落最新的操作。等到拷贝数据的所有操作记录重写实现后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保障数据库最新状态的记录。此时,咱们就能够用新的 AOF 文件代替旧文件了。
小结
- 零碎设计中的一个重要准则,即 trade-off,或者称为“取舍”,指的就是在性能和可靠性保障之间做取舍。
- 最新长久化计划:前置 RDB 快照 +AOF 文件
-
从新过程潜在阻塞危险:
- fork 会耗费大量 CPU 资源,拷贝实现之前整个过程是会阻塞的
- bigkey 从新申请大块内存耗时会变长,可能会产阻塞危险(所以须要敞开 Huge Page 机制)
参考
套接字百科