《Redis深度历险》学习笔记:线程模型&持久化

线程模型

非阻塞IO

  • Redis、Node.js、Nginx 均是单线程服务,但他们都是服务器高性能的典范。
  • Redis 4.0 版本抛弃了单线程设计,转为多线程设计,但其主处理程序依然是单线程模型
  • Redis 使用单线程进行设计的原因:
    • 更好的可维护性,方便开发和调试:多线程模型存在竞争条件 (race condition)问题,多个进行的执行顺序会对共享内存的内容造成影响,需要额外设计锁逻辑。
    • 处理并发请求:将数据库的开、关、读、写都转换成了事件,使用 I/O 多路复用 机制轮询描述符处理并发连接,同时等待多个连接发送的请求。
    • Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU:一个 Redis 服务在 1s 内可处理 100w 个用户请求,如无法满足,应使用分片将不同的请求交给不同的 Redis 服务器来处理,而不是在同一个 Redis 服务中引入大量的多线程操作。
  • 每一个网络连接都会产生一个文件描述符,并由执行事件循环(死循环)的多路复用模块接收,然后关联指令队列,交给文件事件分配器按顺序处理。一旦期间有任何事件到来,就可以立即返回。时间过了之后还是没有任何事件到来,也会立即返回。 多路复用 (Source: Redis 和 I/O 多路复用)
  • 关于异步阻塞的概念,这篇文章有一个形象的举例:

    出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
    
    同步阻塞:老张把水壶放到火上,立等水开。
    
    同步非阻塞:老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。
    
    异步阻塞:老张把响水壶放到火上,立等水开。
    
    异步非阻塞:老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。
    

持久化

RDB 快照

  • RDB 快照是一次全量备份,是内存数据的二进制序列化形式,在存储上非常紧凑。
  • 持久化需要进行文件 IO,但该操作不能使用多路复用 API,在拖慢性能的同时,还存在数据一致性的问题(脏读、幻读等),为避免上述问题,Redis 使用多进程 COW(Copy On Write)策略进行持久化。
  • 创建快照(持久化)时:
    • fork(多进程)函数使进程分离,产生父、子进程,双方共享内存数据,内存里的数据在进程产生的一瞬间就凝固。
    • 父进程持续处理数据,复制需要修改的数据端后进行操作,不改变原有数据端。
    • 子进程持续读取数据端内数据进行快照操作,读写数据到一个临时文件 RDB 中。
    • 子进程完成读写后替换原 RDB 文件。

AOF 日志(Append-Only File)

  • AOF 日志是顺序指令序列,只记录对内存进行修改的指令记录。
  • Redis 通常先执行命令,再记录 AOF 日志

AOF 重写

  • 长期运行会使 AOF 日志文件过大,重放日志耗时过长,AOF 重写操作可为文件瘦身。
  • 运行机制:
    • fork(多进程)函数使进程分离,产生父、子进程
    • 子进程将日志写入到一个临时文件 AOF 中
    • 父进程将数据变更写入到临时内存缓冲区,同时持续写入日志到原 AOF 文件中(这样即使断电,也能保证有一个 AOF 文件是可用的)
    • 子进程完成读写后,父进程接收信号,将内存缓冲区日志追加到临时文件 AOF 中
    • 父进程完成写入后替换 AOF 文件,从而实现 AOF 重写。
  • AOF 日志使用 Linux 提供的 fsync 函数进行文件写入操作,但由于文件 IO 速度慢,需要设置适当的写入周期。
  • AOF 日志的 fsync 周期默认为 1s。(服务宕机最多丢失 1s 数据)。可关闭 fsync 牺牲安全性换取 Redis 性能。

Redis 4.0 混合持久化

  • RDB 来恢复内存会丢失大量数据,通常使用 AOF 日志重放,为解决重放太耗时的问题,Redis 4.0 使用混合持久化。
  • 将 RDB 文件和增量 AOF 日志文件存在一起,AOF 日志文件记录持久化过程中的增量变更。重放时,双管齐下甭提有多爽快了!

延伸阅读:

updatedupdated2023-09-272023-09-27