Principio de persistencia y riesgos potenciales causados por el proceso fork child

Durante la entrevista, el entrevistador puede pedirle que hable sobre el mecanismo de persistencia de Redis y recitar directamente los ocho puntos: hay dos tipos de persistencia, uno es AOF, el otro es RDB, de los cuales AOF es. . .

Ya sabes, cuando se habla de persistencia, AOF y RDB se pueden relacionar con el sistema operativo y el IO subyacente, y RDB también se puede usar en el mecanismo centinela.

Para responder a cualquier pregunta de Redis, se debe tener en cuenta el alto rendimiento . La razón para crear tantas estructuras de datos es ser rápido, y la multiplexación de IO también es rápida. Entonces, algunas personas piensan que la persistencia es un contenido de alta disponibilidad. , ¿cómo se puede comparar con alto rendimiento? Debido a que Redis nació para un alto rendimiento, si no hay forma de mantener un alto rendimiento al agregar funciones, Redis preferiría no usar esta función. Por lo tanto, podemos encontrar que muchas de están diseñadas para mantener un alto rendimiento. rendimiento Bajo la premisa de alto rendimiento, para garantizar la persistencia , por lo que sugiero que pueda usar esto como un punto de entrada cuando aprenda la persistencia.

Respuesta formal : De hecho, AOF es un mecanismo que está estrechamente relacionado con las llamadas al sistema, como la bifurcación del sistema operativo. Hay muchos puntos de conocimiento muy detallados que vale la pena saborear. En el proceso de aprendizaje, también pensé en dos conmovedores. pregunta, ¿por qué hay búferes de reescritura AOF y búferes AOF? La solución de bifurcación mantiene el alto rendimiento de Redis, pero ¿realmente está bien? Si está interesado por un tiempo, puedo compartirlo con usted. Déjeme explicarle el principio primero. AOF es una estrategia de persistencia. Los comandos de escritura se agregan al búfer AOF en formato de protocolo de texto y luego se sincronizan con el disco en forma de archivos Después de reiniciar, pase Los comandos en el archivo se ejecutan para restaurar. Para hacer que Redis sea más rápido, a diferencia de MySQL, redis adopta el método log after write , que también evita registrar instrucciones incorrectas y no bloquea la operación de escritura actual. Pero esto traerá dos riesgos. El primer riesgo es que después de que redis acaba de ejecutar un comando, se bloquee antes de que tenga tiempo de registrar el registro. Si redis se usa como caché, es inofensivo volver a leer los datos del base de datos back-end para la recuperación.Si se utiliza redis como base de datos, no se puede recuperar. El segundo riesgo es que puede bloquear la siguiente operación, porque el AOF de Redis se realiza en el subproceso principal, por lo que una vez que se escriben datos de instancias grandes, el registro de AOF causará una presión de escritura de disco excesiva al escribir en el disco, lo que resultará en la escritura de The el disco es muy lento, lo que a su vez impide que se realicen operaciones posteriores. La causa principal para resolver los dos problemas anteriores es que necesitamos una instrucción para controlar el tiempo de escritura del archivo AOF en el disco , por lo que existen tres estrategias de sincronización que resuelven los problemas anteriores al controlar el tiempo de escritura del registro AOF. de vuelta al disco después de ejecutar el comando de escritura. Son cada segundo, siempre, y no. Sin embargo, dado que el archivo AOF se agregará todo el tiempo, una vez que el archivo AOF sea grande, la eficiencia de la adición se reducirá y puede exceder el límite del tamaño del archivo de almacenamiento dinámico propio del sistema de archivos. mecanismo de recuperación, después del tiempo de inactividad Después de reiniciar, ejecutará los comandos en AOF uno por uno.Si el archivo es demasiado grande, el tiempo de recuperación será demasiado largo, por lo que hay un mecanismo de reescritura.. El mecanismo de reescritura se puede resumir como una copia y dos registros . Una copia se refiere al proceso principal que bifurca el proceso secundario para la reescritura. Los dos registros son el registro AOF y el nuevo registro AOF, que se reemplazará después de que se complete el nuevo registro. completado registro antiguo.

escribir anexar

Cuando la función AOF está activada, después de que el servidor ejecuta un comando de escritura, agregará el comando de escritura ejecutado al final del búfer aof_buf del estado del servidor en un formato de protocolo de texto, similar a

*3 \ r \ n 3 \ r \ nset \ r \ n 5 \ r \ nhola \ r \ n $ 5 \ r \ nmundo \ r \ n

  1. ¿Por qué AOF adopta directamente el formato de protocolo de texto?

    1. Debido a que el protocolo de texto tiene mejor compatibilidad,
    2. Después de habilitar AOF, todos los comandos de escritura incluyen operaciones de adición, que están directamente en el formato del protocolo para evitar la sobrecarga de procesamiento secundario.
    3. El protocolo de texto tiene una mejor legibilidad y es conveniente para la modificación y el procesamiento directos.
  2. ¿Por qué AOF agrega comandos a aof_buf en lugar de al disco duro? Debido a que Redis usa un solo subproceso para responder a los comandos, AOF se ejecuta en el subproceso principal. Si cada comando para escribir un archivo AOF se agrega directamente al disco duro, el rendimiento depende completamente de la carga actual del disco duro. Si un archivo de registro es demasiado grande, hará que se escriba el archivo.Al ingresar al disco, la presión de escritura del disco es demasiado grande, lo que hace que la escritura en el disco sea muy lenta, lo que a su vez bloquea la siguiente instrucción de escritura. Si el búfer se escribe primero, puede evitar que la operación de escritura AOF en el disco provoque el riesgo de bloquear el siguiente comando de escritura. Por lo tanto, se proporcionan tres estrategias de archivo de sincronización de búfer AOF, que están controladas por el parámetro appendfsync para equilibrar el rendimiento y la seguridad. Se recomienda usar everysec.

sincronizar sincronizar

AOF no es como la base de datos WAL. AOF es un registro después de escribir. El comando se ejecuta primero, los datos se escriben en la memoria y luego se graba el registro.

Motivo :

  • 传统数据库的日志记录的是修改后的数据,AOF记录的是Redis收到的每一条指令,这些指令以文本形式保存。比方说一个简单的set语句,就分为三部分,每部分由$+数字开头,紧跟着具体的命令。先执行后写日志主要是为了避免 语法检查的开销,命令能执行成功,才被记录到日志,否则系统就会直接向客户端报错。
  • 不会阻塞当前的写操作。总而言之就是不需要进行额外的语法检查的情况下保证日志的语句都是对的,并且不会阻塞当前的写操作,尽可能提升Redis的效率。

但是AOF持久化有两个风险:

  1. 执行完一个命令,还没来得及记录日志就宕机了,但这对缓存数据库来说,影响不是很大,再从后端数据库重新读取数据就行。
  2. AOF虽然避免对当前命令的阻塞,但是可能会带来下一个操作的阻塞:因为AOF是在主线程中执行的,如果日志文件写入磁盘时,磁盘写压力过大,就会导致写磁盘很慢,进而导致后续操作无法进行。

以上两个风险都是和AOF写回磁盘的时机有关,这也就以为这如果我们能够控制一个写命令执行完后AOF日志写回磁盘的时机,这两个问题就解决了。

写回策略也就是同步策略有三种:

配置项 写回时机 优点 缺点
Always 同步写回 可靠性高,数据基本不丢失 每个写命令都要落盘,性能影响大
EverySec 每秒写回 性能适中 宕机时丢失1秒内的数据
No 操作系统控制写回 性能好 宕机时丢失数据较多

其中always写入aof_buf后调用系统调用fsync,他是针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。

而everysec和no采用系统调用write操作。会触发延迟写机制。Linux在内核提供页缓冲区来提高硬盘IO性能。write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于操作系统调度机制。例如:缓冲区页空间写满或达到特定时间周期。

数据还原

image.png

AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF如果关闭或者AOF文件不存在,加载RDB文件,加载AOF/RDB文件成功后,Redis启动成功。如果AOF/RDB文件存在错误时,Redis启动失败并打印错误信息

服务器只需要读取并重新执行一边AOF文件中保存的写命令,就可以还原服务器关闭之前的数据库状态。

  1. 创建一个不带网络的伪客户端(因为redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端执行AOF保存的写命令)
  2. 从AOF文件中读取一条写指令
  3. 使用伪客户端执行这条写指令

重复上述2、3过程,直到所有写命令都处理完毕

问题

随着AOF的增大,会带来性能问题,因为AOF是以文件形式接受所有的写命令,那么接收的文件越来越多后,AOF越来越大,恢复的时间就会越来越久。

重写Rewrite

重写就是为了解决AOF增加带来的性能问题。降低文件占用空间,存储更小,而且会使复原的时候更快。本质是以Redis进程内的数据为依据,将数据转化为写命令同步到新AOF文件的过程。并不需要对现有的AOF文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令。

为什么重写后的AOF文件可以变小?

  1. 进程中已经超时的数据不再写入文件
  2. 旧的AOF文件含有无效命令例如del key1...重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
  3. 因为以最终的数据为依据,所以针对同一个数据的多条命令合并成一条。

这里需要注意的是,为了避免在执行命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键锁包含的元素数量,如果元素数量超过了redis.h/redis_aof_rewrite_items_per_cmd常量的值,那么重写程序将使用多条命令来记录键的值,而不是单单使用一条命令。当前版本是64,也就是说一个集合键包含超过64个元素,那么重写程序就会用多条sadd命令来记录这个集合。每条命令设置的元素数量也为64个。

另一方面,如果一个列表键包含了超过64个项,那么会用多条rpush命令来保存这个列表,并且每条命令设置的项数量也为64个

重写可以手动触发(bgrewriteaof)也可以自动触发(auto-aof-rewrite-min-size、auto-aof-rewrite-percentage参数确定自动触发的时机)。

一个是重写时候最小体积,一个是当前AOF文件空间和上一次重写后AOF文件空间的比值。

image.png

  1. 在执行AOF重写请求时,如果当前进程正在执行AOF重写,就不执行;如果当前进程在进行bgsave,就等bgsave完成之后再执行。
  2. 父进程fork创建子进程,开销等同于bgsave
  3. 父进程依然写入AOF缓冲区并且根据appendfsync策略同步到硬盘,保证所有AOF机制正确性;AOF重写缓冲区用于保存新写入的数据
  4. 子进程写入新的AOF文件,每次批量写入硬盘的数据由配置aof-rewrite-incremental-fsync控制,默认32MB,防止单词刷盘数据过多造成硬盘阻塞。
  5. 子进程完成AOF重写工作,给父进程发送一个信号,父进程接收到该信号会调用一个信号处理函数,将AOF重写缓冲区的数据写入到新的AOF文件中,保证新AOF文件所保存的数据库状态和服务器的数据库状态一致,并且对新的AOF文件改名,原子地覆盖现有的AOF文件

整个AOF后重写过程中,只有信号处理函数执行时会对服务器进程(父进程)造成阻塞,其他时候AOF后台重写都不会阻塞父进程,这将AOF重写对服务器性能造成的影响降到最低。

原理:从数据库中读取键现在的值,用一条命令去记录键值对,代替之前记录这个键值对的多条命令。也就是调用aof_rewrite函数

但是这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞,因为Redis服务器使用单个线程来处理命令,所以服务器直接调用aof_rewrite函数的话,那么重写AOF期间,服务器无法处理客户端发来的命令请求,所以AOF重写是在子进程中进行的,主要有两个目的:

  1. 子进程进行AOF重写期间,父进程可以继续处理命令
  2. 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以避免使用锁的情况下保证数据的安全性。

但是还有个问题,可能会导致服务器当前数据库状态和重写后AOF文件所保存的数据库状态不一致。为了解决数据不一致问题,Redis设置了AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当redis执行完一个命令后,他会同时将这个写命令发送到AOF缓冲区和AOF重写缓冲区。

重写过程中的潜在风险

  1. Redis 主线程 fork 创建 bgrewriteaof 子进程时,内核需要创建用于管理子进程的相关数据结构,这些数据结构在操作系统中通常叫作进程控制块(Process Control Block,简称为 PCB)。内核要把主线程的 PCB 内容拷贝给子进程。这个创建和拷贝过程由内核执行,是会阻塞主线程的。而且,在拷贝过程中,子进程要拷贝父进程的页表,这个过程的耗时和 Redis 实例的内存大小有关。例如10G的Redis进程,需要复制大约20MB的内存页表,如果采用虚拟化技术,fork操作更加耗时,这就会给主线程带来阻塞风险。
  2. bgrewriteaof 子进程会和主线程共享内存。当主线程收到新写或修改的操作时,主线程会申请新的内存空间,用来保存新写或修改的数据,如果操作的是 bigkey,也就是数据量大的集合类型数据,那么,主线程会因为申请大空间而面临阻塞风险。因为操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。

改善措施有:

  1. 优先使用物理机或者高效支持fork操作的虚拟化技术
  2. 控制Redis实例的最大可用内存,每个Redis实例内存控制在10GB以内,关闭Huge Page机制
  3. 合理配置Linux内存分配策略,避免物理内存不足导致fork失败。降低fork操作的频率,放款AOF自动触发时机避免不必要的全量复制

RDB

首先,RDB就是给数据进行快照,是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时数据库的状态,它的还原速度非常

触发机制

手动触发

save命令:阻塞当前Redis服务器进程,服务器不能处理任何命令请求,直到RDB过程完成,因为内存较大的实例会造成长时间阻塞,所以线上环境不建议使用

bgsave命令是对前面save命令的优化,fork创建子进程,RDB过程由子进程负责,完成后自动结束,阻塞只会发生在fork阶段,一般时间很短

两个命令的联系与区别:创建RDB文件的实际工作是有 rdb.c/rdbSave 函数完成,savebgsave 命令会以不同方式调用这个函数。

  • save 直接调用函数生成RDB文件

  • bgsave 有以下步骤

    1. 父进程fork子进程
    2. 父进程继续响应其他命令,并且轮训等待子进程的信号,子进程调用rdbSave函数创建RDB文件
    3. 子进程完成RDB后发送信号通知父进程

bgsave执行时服务器的状态:服务器正常接受命令,但是处理 savebgsavebgrewriteaof 三个命令的方式不同

  • save: 会被拒绝,不能同时执行这俩命令,主要是为了避免父进程和子进程同时执行两个rdbSave 调用,产生竞争条件
  • bgsave:也会被拒绝,也是因为两个命令同时调用 rdbSave,会产生竞争条件
  • bgrewriteaof: 如果 bgsave 正在执行,那么客户端发送的 bgrewriteaof将会被延迟到bgsave执行完毕后执行;如果 bgrewriteaof 正在执行,那么 bgsave 会被拒绝,他们的实际工作都是由子进程来完成的,所以操作层面无冲突,只是在性能方面,两个子进程都要同时执行大量的磁盘写入操作,影响性能。

自动触发

有以下场景

  • 使用 save相关配置,例如 save m n表示m秒内数据存在n次修改的时候,自动出发basave
  • 如果从节点执行全量复制,主节点自动执行 bgsave 生成RDB文件并发送给从节点
  • 执行 debug reload 命令重新加载Redis的时候,会自动出发save操作
  • 默认情况下执行 shutdown 命令时,如果没有开启AOF持久化功能,则自动执行 bgsave

自动间隔性保存

通过 save选项设置多个保存条件,只要满足任意一个条件,服务器就会执行 Bgsave 命令。

格式:

save 900 1
save 300 1
save 60 10000
复制代码

意思就是服务器在900秒之内对数据库至少进行一次修改就会执行 bgsave

原理

  1. 参数

    • 服务器根据这个条件设置服务器状态 redisServer 结构的 saveparams 属性, saveparams 属性是一个数组。每个元素都保存了秒数以及修改数,类似如图
    • dirty 计数器:记录距离上一次成功执行 Save 命令或者 Bgsave命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除和更新操作)。当服务器成功执行一个数据库修改命令后,程序就会对该计数器进行更新,命令修改了多少次数据库,dirty 计数器的值就增加多少
    • lastsave 属性是一个UNIX 时间戳,记录了服务器上一次成功执行 save 命令或者 bgsave 命令的时间
  2. 周期性操作函数 serverCron 检查save选项所设置的保存条件是否已经满足,如果满足了就执行 Bgsave 命令他会遍历并检查 saveparams 数组中的所有保存条件,伪代码如下def serverCron();​

def serverCron();
#... 

#遍历所有保存条件
for saveparam in server.saveparams

    #计算距离上次执行保存操作有多少秒
    save_interval = unixtime_now() - server.lastsave

    #如果数据库状态的修改次数超过条件设置的次数
    #并且距离上次保存的时间超过条件所设置的时间
    #执行bgsave
    if server.dirty >= saveparam.changes and \
        save_interval > saveparam.seconds:

        BGSAVE()
复制代码

在这里的例子中,当执行到1378271101,也就是301秒后,服务器自动执行一次 bgsave,因为 saveparams数组保存的第二个条件已经被满足。

载入

RDB文件的载入是在服务器启动时自动启动的,实际工作是由rdb.c/rdbLoad函数完成的,服务器在载入期间会一直处于阻塞状态直到载入工作完后。但是同时有RDB和AOF的时候,是有先后顺序的

先后顺序:服务器启动的时候,会先查看是否开启了AOF持久化功能,如果开启了,会优先使用AOF还原数据库状态,只有AOF关闭的时候,才会使用RDB文件恢复

RDB文件处理

  • 保存:保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行,当下次运行时RDB文件会保存到新的目录中当磁盘写满或者坏掉了,可以通过config set dir{newDir}在线修改文件路径到可用的磁盘路径,之后执行bgsave进行磁盘切换,同样适用于AOF文件
  • 压缩:Redis默认采用LZF算法对生成的RDB文件进行压缩处理,压缩后的文件远远小于内存大小,默认打开,可以通过参数config set rdbcompression{yes/no}动态修改虽然压缩RDB消耗内存,但可以大幅度降低文件的体积,线上建议开启
  • 如果Redis加载损坏的RDB文件时拒绝启动,可以使用redis-check-dump工具检测RDB文件并且获取对应的错误报告

写时复制

在RDB快照过程中,数据不能被修改,也就是此时主线程不能执行写操作,只能执行读操作,这是不被允许的,也正是因此,Redis采用了写时复制的思想,如果某个数据正在被RDB快照,此时主线程要修改这个数据,那么这个数据就会被复制一份,生成该数据的副本,然后bgsave子进程会把这个副本数据写入RDB文件中,而这个过程中,主线程依然可以修改原来的数据。

优缺点

  • 优点

    1. RDB存储的是某个时间点上的数据快照,非常适合全量复制的场景,比如每6小时bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中,用于灾难恢复。
    2. RDB恢复速度远快于AOF。因为RDB是对数据直接进行快照,而AOF存储的是命令,恢复时候还多了一步执行
  • 缺点

    1. RDB无法做到实时持久化(秒级),因为bgsave每次运行都要执行一次fork创建子进程,频繁fork会阻塞主进程
    2. 老版本Redis无法兼容新版本RDB格式

潜在问题

假假设在写操作为主的场景下,RDB做持久化有潜在风险,因为RDB采用的是写时复制的方法,系统需要复制数据,给他们分配内存,所以就可能出现整个系统内存的使用量接近饱和的情况。

混合持久化

重启Redis时,我们很少使用RDB来恢复内存状态,因为会丢失大量数据,我们通常使用AOF,但是AOF相对于RDB慢很多,如果AOF文件较大的时候,启动Redis需要花费很长时间

因此,Redis 4.0 引入了混合持久化,也就是内存快照以一定频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。 这样子既可以避免RDB快照中频繁fork对主进程的影响,同时,AOF只需要记录两次快照之间的操作即可,也就是说不需要记录所有操作,因此不会出现文件过大的情况,也避免了重写。于是重启的时候,先加载RDB的内容,然后执行增量AOF文件,重启效率大大提升。

使用建议

  1. 如果数据不能丢,建议使用混合持久化
  2. 如果允许分钟级别的数据丢失,可以只使用RDB
  3. 如果只使用AOF,优先使用everysec

参考

《Redis设计与实现》

《Redis开发与运维》

《Redis 核心技术与实战》(极客时间专栏)

《Redis深度历险》

Supongo que te gusta

Origin juejin.im/post/7101875399432339492
Recomendado
Clasificación