redis

单线程 Redis 为什么快

redis 单线程是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是redis对外提供键值存储服务的主要流程。

但是redis的其它功能:持久化、异步删除、集群数据同步等都是由额外的线程执行的

多线程的开销

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

单线程redis快的原因

  • 大部分的操作在内存上完成
  • 高效的数据结构
  • 多路复用机制

多路复用的 I/O 模型

在redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核上会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给redis线程处理,这就实现了一个redis线程处理多个IO流的效果

为了在请求到达的时能通知到redis线程,select/epoll 提供了 基于事件的回调机制,即针对不同的事件发生,调用相应的处理函数。

select/epoll 一旦检测到FD(文件描述符file descriptor)上有请求到达时,就会触发相应的事件。

这些事件会被放进一个事件队列,redis单线程对该事件队列不断进行处理。这样一来,redis无需一直轮询是否有请求实际发生,这样就可以避免造成cpu资源的浪费。redis在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为redis一直在对事件队列进行处理,所以能及时响应客户端请求,提升了redis的响应性能。

RDB

RDB可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中

RDB 持久化 生成的 RDB 文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态

RDB 文件的创建与载入

两个命令可以生成RDB文件:

  • save:save命令会阻塞redis主线程,直到rdb创建完成为止,在主线程阻塞期间,服务器不能处理任何命令请求。
  • bgsave:bgsave命令会派生出一个子线程,然后由子线程负责创建 RDB 文件,主线程可以继续处理命令

RDB文件的载入工作是在服务器启动的时候自动执行的,所以 redis 并没有专门用于载入RDB文件的命令,只要redis服务在启动时检测到 RDB 文件存在,它就会自动载入RDB文件。

AOF文件的更新频率比RDB文件更新频率更高,所以:

  • 如果服务器开启了 AOF 持久化功能,那么服务器会优先使用 AOF 文件来还原数据库状态
  • 只有在 AOF 持久化功能处于关闭状态时,服务器才会使用 RDB 文件来还原数据库状态

在bgsave命令执行期间:

  • 客户端发送的 save 命令会被服务器拒绝,(避免父进程和子进程同时执行两个rdbSave调用)
  • 客户端发送的 BGREWRITEAOF 命令会被服务器拒绝。(相当于并发执行两个子线程,而且这两个子线程都是同时执行大量的磁盘写入操作)

自动间隔保存

redis允许用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令

比如配置如下:

save 900 1
save 300 10
save 60 10000
  • 在900秒之内,对数据库进行了至少一次修改
  • 在300秒内,对数据库至少进行了 10 次修改
  • 在60秒内,对数据库至少进行了 10000 次修改
dirty 计数器和 lastsave 属性
  • dity计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(所有的数据库)进行了多少次修改
  • lkastsave属性是一个时间戳,记录了上一次执行成 SAVE 或者 BGSAVE 命令的时间

当服务器执行成功一个数据库修改命令之后,程序就会对dirty计数器进行更新。

检查条件是否满足

redis服务器周期性操作函数serverCron默认每隔100ms执行一次,该函数用于对正在运行的服务进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足就执行BGSAVE命令

补充

  • 在持久化过程中,“写实复制” 会重新分配内存副本,如果此时的服务器内存使用接近饱和,同时父进程又有大量的新 key 写入,很快,机器内存就会被消耗完,如果机器开启了swap机制,那么redis会有一部分数据映射到磁盘上,当redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准。如果机器没有开swap,会直接出发oom,父子进程会面临被系统kill掉的风险。

  • 虽然是自己才能在做持久化,但是生成的RDB快照过程会消耗大量的CPU资源,虽然redis处理请求是单线程的,但redis服务还有其它线程在后台工作,例如AOF的每秒刷盘,异步关闭文件描述符等操作。当父进程占用CPU资源过多的时候进行RDB持久化,可能会产生CPU竞争,导致的结果是父进程处理请求延时增大,子进程生成RDB快照时间变长,导致整个redis服务性能下降。

  • 如果redis绑定了CPU,子进程会继承父进程的CPU亲和属性,子进程必然会与父进程争夺同一个CPU资源,进而影响redis服务的性能。所以如果redis需要开启定时RDB和AOF重写,进程一定不要绑定CPU

AOF

AOF日志是写后日志,先将数据写入内存,然后才记录日志

AOF中保存的是redis接收到的每一条命令,这些命令是以文本形式保存的

  • 为了避免额外的检查开销,redis在向AOF里面记录日志的时候,并不会先去对这些命令进行语法检查。所以如果先记录日志再执行命令的话,日志中就有可能会记录错误的命令,在恢复数据的时候可能会出错
  • 在命令执行之后记录日志,不会阻塞当前的写操作

潜在风险

  • 如果刚执行完一个命令,还没有来得及记录日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
  • AOF虽然避免了对当前命令的阻塞,但可能会给下一个命令带来阻塞风险。因为AOF日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。

三种回写策略

  • always,同步回写:每个写命令执行完,立马同步将日志写回磁盘
  • everysec,每秒回写,每个命令执行完,先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘。
  • no,操作系统控制的回写,每个命令执行完,先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容回写至磁盘

但是这三种方式都无法避免主线程阻塞和数据丢失问题

  • 同步回写:
    • 可靠性高,数据基本不会丢失
    • 对性能影响比较大
  • 操作系统控制的回写:
    • 性能好
    • 宕机时丢失的数据比较多
  • 每秒回写:
    • 性能适中
    • 宕机丢失1s内的数据

需要注意AOF文件过大带来的性能问题

  • 操作系统对文件大小有限制
  • 文件过大,追加命令记录效率会变低
  • 如果发生宕机,进行数据恢复的时候,AOF中的命令要一个个的执行,如果日志文件过大,整个恢复过程会非常缓慢

重写机制

重写
比如一个 key => value 是 “name” => “tom”
现在redis收到了如下命令

set name jim
set name lisi
set name wangwu

这时候AOF里面只会记录最后的命令 set name wangwu

和AOF日志由主线程回写不同,重写的过程是由后台线程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降

一个拷贝、两处日志

一个拷贝

每次重写的时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。

两处日志

因为主线程未阻塞,仍可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的AOF日志,redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个AOF日志仍然是齐全的。

第二处日志,就是指新的AOF重写日志。这个操作也会被写到重写日志的缓冲区。这样

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值