运作
Redis 的内存模型¶
事实上,内存中有一块位置存储了 String 的字符内容,如上图中 dict 中其实存储的是指向 String 的指针,每个这个 String 指针会对应一个 timpstamp
Redis 线程¶
核心执行流程是单线程的
Redis 的 IO 多路复用¶
使用 IO 多路复用函数来监控多个文件描述符(比如与客户端的连接 socket)的状态变化
服务器进程运行在一个事件循环中:每次循环迭代时,会调用 IO 多路复用函数,阻塞等待一系列文件描述符的变化情况。当有事件发生时,redis 根据事件类型来执行处理逻辑;随后开启下一轮循环(这个事件循环的主体逻辑与 consul 客户端提供的长轮询监控 server 服务节点变化的 api 相似)
为什么使用单线程¶
- 上下文切换的时间成本
- 同步机制(加锁等)会有一定的开销
- 节省内存
redis 的性能瓶颈在于 IO 而不是 CPU
为什么 redis 快¶
redis 可以达到 8 ~ 10w/s
- 内存数据库,内存访问快
- 数据结构设计得很高效
- IO 多路复用,网络IO操作中可以提高吞吐量
哪里用到了多线程¶
工作流程如下:
在 IO 多路复用模型中,还是主线程去监听(遍历)所有的 fd 事件,随后再将具体的读取和写回操作交给多个线程执行
- 当客户端发送请求时,I/O 线程从 Socket 读取数据并将其存储在 queryBuf 中。
- I/O 线程从客户端的 Socket 中读取数据。
- 将读取的数据存储到客户端的输入缓冲区(
queryBuf
)中
- 主线程从 queryBuf 中获取请求并执行相应的命令。
- 执行结果存储在 reply 中,然后由 I/O 线程将响应发送回客户端。
- I/O 线程从客户端的输出缓冲区(
reply
)中获取数据。 - 将数据通过 Socket 发送给客户端。
- I/O 线程从客户端的输出缓冲区(
Redis 事务¶
事务允许用户原子性地执行多条命令,避免在这些命令的执行过程中被其他客户端的命令干扰
当使用 lua 脚本时,整个脚本会作为一个整体执行,同样具有原子性,且脚本中可以包含条件选择。但是 lua 脚本执行的过程中如果出现了错误,会立刻停止执行,但不会回滚,这一点和事务不同
性能方面,事务的网络往返次数要多于使用 lua 脚本(注意 queued 返回),lua 脚本只需要一次网络往返
使用事务¶
开启事务:使用 MULTI
命令
命令入队:输入要执行的命令,这些命令不会立即执行,而是放入队列
执行事务:使用 EXEC
命令执行所有队列中的命令
取消事务:使用 DISCARD
命令清空命令队列并退出事务
事务中的所有需要执行的语句之间相互不会影响,且所有语句作为一个原子性的整体执行。类似于 Verilog 中的 <= 。事务中的某条命令失败不会影响其他命令,且事务没有回滚功能
WATCH 乐观锁¶
客户端的概念¶
Client 即与 redis 服务器建立网络连接并发送命令的实体。
当多个客户端共用一个 redis 实例时,不同客户端到服务实例的顺序不完全确定。
使用事务时,可以一次性执行同一个客户端的一系列命令
WATCH 作用¶
如果在 WATCH
和 EXEC
之间,被监视的键被其他客户端修改,则整个事务不会执行,需要重新 WATCH
并开始新事务
Redis 持久化¶
为什么需要持久化¶
持久化就是将内存数据存入磁盘
- Redis 在很多场景下作为缓存使用,但如果碰到重启情况之后缓存都不在了,这样请求会一下都打到存储上
- Redis 本身也可以作为存储使用,这个时候对于数据保存的安全性要求较高
RDB 和 AOF 的本质区别是什么¶
RDB 是全量的二进制快照保存,而 AOF 则是追加日志文件进行记录
- 文件类型:RDB 生成的都是二进制文件(快照),AOF 生成的是文本文件(追加日志)
- 安全性:缓存宕机时,RDB 会损失较多的数据,AOF 则会根据配置的刷盘策略(比如1s刷一次)决定
- 文件恢复速度:由于 RDB 是二进制文件,所以其恢复速率比 AOF 更快
- 操作开销:如果以每一次的操作作为单位,RDB 每次的保存都是全量保存,而 AOF 刷盘是一次追加操作
RDB 和 AOF 选谁¶
- 如果能接受分钟级别的丢失,选 RDB(这也是官方的默认选择)
- 如果选择 AOF ,则开启每秒刷盘一次的 AOF
二者同时开启时,使用哪种载入策略¶
同时开启时,无论 AOF 日志是否丢失,都只会使用 AOF,因为既然选择一定要开启 AOF ,说明对于数据的可靠性要求较高,此时如果发生异常需要即时暴露出来进行处理
RDB 详解¶
RDB 开启¶
定时持久化¶
前一个参数表示每过多久检查一次;后一个参数表示这段时间内的更新数达到多少才能快照(这些命令不是需要手动执行的)
手动持久化¶
RDB 的写入方式¶
后台持久化时:
- Redis 首先调用 fork 开启一个子进程
- 子进程开始将所有数据写入到 临时 RDB 文件中
- 当子进程写入完成后,替换掉原有的 RDB 文件
这里的 fork 使用 copy on write ,并且注意到随后进行数据更新操作的必定是父进程(子进程不会接受命令)
- 在写入的过程中新来的数据是父进程新开了的内存接收的
- 复制的是快照一瞬间的数据
面试问题¶
RDB 的本质是什么¶
RDB 的本质是二进制形式的快照
如何开启 RDB¶
- 定时,使用后台持久化
- 主动命令 save / bgsave(实际生产中少)
RDB 对主流程有什么影响¶
- 阻塞式持久化时,由主进程进行快照保存,会阻塞主进程
- 后台持久化时,由 fork 出来的子进程来执行快照
- 特别地,当数据量比较大时,会导致 fork 子进程这个操作比较耗时,从而阻塞主进程
- 和阻塞相比,如果子进程执行拷贝时又有新的数据改写,会由主进程执行
AOF 详解¶
如何开启 AOF¶
找到 redis 配置文件 redis.conf
默认的配置为 appendonly no
,改为 yes 即可
刷盘策略¶
- always 每次请求都刷入 AOF
- everysec 每秒刷一次盘(崩溃时会丢失 1s 的数据)
- no 不主动刷盘,让操作系统自己刷(linux 一般是 30s 刷一次盘,这里是只从内核缓存中写入磁盘)
AOF 落盘¶
先写入缓冲,再根据策略刷盘
常见问题¶
AOF 重写是解决什么问题的¶
重写用于解决 AOF 不断膨胀的问题,随着命令越来越多,AOF 文件越来越大,但是记录的指令中存在指令的覆盖,此时前面的命令就没有记录的必要了。
AOF 重写的简要流程¶
- 主进程 fork() 出来一个子进程,然后这个子进程读取 RedisDB 中的数据,以字符串的形式写入到新的 AOF 文件中
-
当新的 AOF 文件重建完毕,主进程会把重写缓冲区的内容追加到新的 AOF 文件中
注意,重写缓冲区专门为重写服务,普通缓冲区使用相应的刷盘策略进行刷盘。在重写的过程中,主进程会同时向普通缓冲区的重写缓冲区追加数据
AOF 对主流程的影响¶
- always 需要阻塞主线程,每次主线程都需要等待从缓冲区写入磁盘
- everysec 一般不会阻塞主线程,主线程会使用 fork 创建子线程刷盘
- no 依赖操作系统将缓冲区的内容写入磁盘