zoukankan      html  css  js  c++  java
  • Redis(四)——过期、持久化、事件

    iwehdio的博客园:https://www.cnblogs.com/iwehdio/

    学习自:

    1、redis数据库原理

    • redis默认有16个数据库。数据库与数据库之间的数据是隔离的。

    • Redis服务器用redisServer结构体来表示,其中redisDb是一个数组,用来保存所有的数据库,dbnum代表数据库的数量(这个可以配置,默认是16)

      struct redisServer{  
      
          //redisDb数组,表示服务器中所有的数据库
          redisDb *db;  
      
          //服务器中数据库的数量
          int dbnum;  
      
      }; 
      
    • Redis是C/S结构,Redis客户端通过redisClient结构体来表示:

      typedef struct redisClient{  
      
          //客户端当前所选数据库
          redisDb *db;  
      
      }redisClient;
      
    • Redis客户端连接Redis服务端的一号数据库时:

      image-20210205164732599

    • Redis中对每个数据库用redisDb结构体来表示:

      typedef struct redisDb { 
          int id;         // 数据库ID标识
          dict *dict;     // 键空间,存放着所有的键值对              
          dict *expires;  // 过期哈希表,保存着键的过期时间                          
          dict *watched_keys; // 被watch命令监控的key和相应client    
          long long avg_ttl;  // 数据库内所有键的平均TTL(生存时间)     
      } redisDb;
      

      从代码上我们可以发现最重要的应该是dict *dict,它用来存放着所有的键值对。一般我们将存储所有键值对的dict称为键空间

    • 键空间示意图:

      image-20210205164908456

    • Redis的数据库就是使用字典(哈希表)来作为底层实现的,对数据库的增删改查都是构建在字典(哈希表)的操作之上的。

    2、过期时间

    • 因为我们的内存是有限的。所以我们会干掉不常用的数据,保留常用的数据。这就需要我们设置一下键的过期(生存)时间了。

      • 设置键的生存时间可以通过EXPIRE或者PEXPIRE命令。
      • 设置键的过期时间可以通过EXPIREAT或者PEXPIREAT命令。
    • 其实EXPIREPEXPIREEXPIREAT这三个命令都是通过PEXPIREAT命令来实现的。

    • 具体的,是通过redisDb结构体中dict *expires;属性,存放所有键过期的时间。

    • 移除过期时间,查看剩余生存时间的命令:

      • PERSIST(移除过期时间)
      • TTL(Time To Live)返回剩余生存时间,以秒为单位
      • PTTL以毫秒为单位返回键的剩余生存时间
    • 过期策略:过期键保存在哈希表中了。那这些过期键到了过期的时间,什么时候被被删除掉呢?

    • 删除策略可分为三种

      • 定时删除(对内存友好,对CPU不友好)

        • 到时间点上就把所有过期的键删除了。
      • 惰性删除(对CPU极度友好,对内存极度不友好)

        • 每次从键空间取键的时候,判断一下该键是否过期了,如果过期了就删除。
      • 定期删除(折中)

        • 每隔一段时间去删除过期键,限制删除的执行时长和频率。
    • Redis采用的是惰性删除+定期删除两种策略,所以说,在Redis里边如果过期键到了过期的时间了,未必被立马删除的!

    • 如果定期删除漏掉了很多过期key,也没及时去查(没走惰性删除),大量过期key堆积在内存里,导致redis内存块耗尽了。

    • 这种情况下可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略

    • Redis的内存淘汰机制有以下几种:

      image-20210205165753803

    • 一般情况下,使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用allkeys-lru淘汰策略,将最近最少使用的数据淘汰。

    3、持久化

    • Redis是基于内存的,如果不想办法将数据保存在硬盘上,一旦Redis重启(退出/故障),内存的数据将会全部丢失。
      • 我们肯定不想Redis里头的数据由于某些故障全部丢失(导致所有请求都走MySQL),即便发生了故障也希望可以将Redis原有的数据恢复过来,这就是持久化的作用。
    • Redis提供了两种不同的持久化方法来将数据存储到硬盘里边:
      • RDB(基于快照),将某一时刻的所有数据保存到一个RDB文件中。
      • AOF(append-only-file),当Redis服务器执行写命令的时候,将执行的写命令保存到AOF文件中。

    RDB快照持久化

    • RDB持久化可以手动执行,也可以根据服务器配置定期执行。RDB持久化所生成的RDB文件是一个经过压缩的二进制文件,Redis可以通过这个文件还原数据库的数据。

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

      • SAVE阻塞Redis服务器进程,服务器不能接收任何请求,直到RDB文件创建完毕为止。
      • BGSAVE创建出一个子进程,由子进程来负责创建RDB文件,服务器进程可以继续接收请求。
    • 除了手动调用SAVE或者BGSAVE命令生成RDB文件之外,我们可以使用配置的方式来定期执行:

      • 在默认的配置下,如果以下的条件被触发,就会执行BGSAVE命令
      save 900 1              #在900秒(15分钟)之后,至少有1个key发生变化,
      save 300 10            #在300秒(5分钟)之后,至少有10个key发生变化
      save 60 10000        #在60秒(1分钟)之后,至少有10000个key发生变化
      
    • RDB持久化对过期键的策略:

      • 执行SAVE或者BGSAVE命令创建出的RDB文件,程序会对数据库中的过期键检查,已过期的键不会保存在RDB文件中
      • 载入RDB文件时,程序同样会对RDB文件中的键进行检查,过期的键会被忽略

    AOF文件追加

    • AOF是通过保存Redis服务器所执行的写命令来记录数据库的数据的。

    • AOF持久化功能的实现可以分为3个步骤:

      • 命令追加:命令写入aof_buf缓冲区
      • 文件写入:调用flushAppendOnlyFile函数,考虑是否要将aof_buf缓冲区写入AOF文件中
      • 文件同步:考虑是否将内存缓冲区的数据真正写入到硬盘
    • flushAppendOnlyFile函数的行为由服务器配置的appendfsyn选项来决定的:

      appendfsync always     # 每次有数据修改发生时都会写入AOF文件。
      appendfsync everysec   # 每秒钟同步一次,该策略为AOF的默认策略。
      appendfsync no         # 从不同步。高效但是数据不会被持久化。
      
    • AOF重写:

      • 在结果相同的情况下,多条命令可以进行合并成为1条命令,让AOF文件的体积变得更小。
      • AOF重写不需要对现有的AOF文件进行任何的读取、分析。AOF重写是通过读取服务器当前数据库的数据来实现的!
    • AOF后台重写:

      • Redis将AOF重写程序放到子进程里执行(BGREWRITEAOF命令),像BGSAVE命令一样fork出一个子进程来完成重写AOF的操作,从而不会影响到主进程。
      • AOF后台重写是不会阻塞主进程接收请求的,新的写命令请求可能会导致当前数据库和重写后的AOF文件的数据不一致
      • 为了解决数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓存区会在服务器创建出子进程之后使用
      • 子进程完成AOF重写后,让父进程将AOF重写缓冲区的数据写到新的AOF文件中。
    • AOF持久化对过期键的策略:

      • 如果数据库的键已过期,但还没被惰性/定期删除,AOF文件不会因为这个过期键产生任何影响(也就说会保留),当过期的键被删除了以后,会追加一条DEL命令来显示记录该键被删除了
      • 重写AOF文件时,程序会对RDB文件中的键进行检查,过期的键会被忽略
    • RDB和AOF并不互斥,它俩可以同时使用

      • RDB的优点:载入时恢复数据快、文件体积小。
      • RDB的缺点:会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。)
      • AOF的优点:丢失数据少(默认配置只丢失一秒的数据)。
      • AOF的缺点:恢复数据相对较慢,文件体积大
    • 如果Redis服务器同时开启了RDB和AOF持久化,服务器会优先使用AOF文件来还原数据(因为AOF更新频率比RDB更新频率要高,还原的数据更完善)

    4、Redis事件

    • Redis服务器是一个事件驱动程序,主要处理以下两类事件:

      • 文件事件:文件事件其实就是对Socket操作的抽象,Redis服务器与Redis客户端的通信会产生文件事件,服务器通过监听并处理这些事件来完成一系列的网络操作
      • 时间事件:时间事件其实就是对定时操作的抽象,前面我们已经讲了RDB、AOF、定时删除键这些操作都可以由服务端去定时或者周期去完成,底层就是通过触发时间事件来实现的!
    • Redis开发了自己的网络事件处理器,这个处理器被称为文件事件处理器

      • 文件事件处理器使用I/O多路复用程序来同时监听多个Socket。当被监听的Socket准备好执行连接应答(accept)、读取(read)等等操作时,与操作相对应的文件事件就会产生,根据文件事件来为Socket关联对应的事件处理器,从而实现功能。
      • Redis中的I/O多路复用程序会将所有产生事件的Socket放到一个队列里边,然后通过这个队列以有序、同步、每次一个Socket的方式向文件事件分派器传送套接字。也就是说:当上一个Socket处理完毕后,I/O多路复用程序才会向文件事件分派器传送下一个Socket。

      image-20210205172739277

    • Redis文件事件完成通信:

      • 首先,IO多路复用程序首先会监听着Socket的AE_READABLE事件,该事件对应着连接应答处理器
        • 可以理解简单成SocketServer.accpet()
      • 此时,一个名字叫做3y的Socket要连接服务器啦。服务器会用连接应答处理器处理。创建出客户端的Socket,并将客户端的Socket与命令请求处理器进行关联,使得客户端可以向服务器发送命令请求。
        • 相当于Socket s = ss.accept();,创建出客户端的Socket,然后将该Socket关联命令请求处理器
        • 此时客户端就可以向主服务器发送命令请求了

      image-20210205173125887

      • 假设现在客户端发送一个命令请求set Java3y "关注、点赞、评论",客户端Socket将产生AE_READABLE事件,引发命令请求处理器执行。处理器读取客户端的命令内容,然后传给对应的程序去执行。
      • 客户端发送完命令请求后,服务端总得给客户端回应的。此时服务端会将客户端的Scoket的AE_WRITABLE事件与命令回复处理器关联。
      • 最后客户端尝试读取命令回复时,客户端Socket产生AE_WRITABLE事件,触发命令回复处理器执行。当把所有的回复数据写入到Socket之后,服务器就会解除客户端Socket的AE_WRITABLE事件与命令回复处理器的关联。

      image-20210205173228613

    • 时间事件:

      • 持续运行的Redis服务器会定期对自身的资源和状态进行检查和调整,这些定期的操作由serverCron函数负责执行,它的主要工作包括:
        • 更新服务器的统计信息(时间、内存占用、数据库占用)
        • 清理数据库的过期键值对
        • AOF、RDB持久化
        • 如果是主从服务器,对从服务器进行定期同步
        • 如果是集群模式,对集群进行定期同步和连接
      • Redis服务器将时间事件放在一个链表中,当时间事件执行器运行时,会遍历整个链表。时间事件包括:
        • 周期性事件(Redis一般只执行serverCron时间事件,serverCron时间事件是周期性的)
        • 定时事件
    • 文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的过程中不会发生抢占。

    • 时间事件的实际处理事件通常会比设定的到达时间一些。


    iwehdio的博客园:https://www.cnblogs.com/iwehdio/
    来源与结束于否定之否定。
  • 相关阅读:
    lua面向对象(定义与调用)
    luastring(字符串)
    luatable(表)
    lua面向对象(创建与实例化)
    pandas安装方法(常规安装失败解决方法)
    lua循环
    windows常用命令schtasks
    ios UI自动化 appium参数配置
    ios UI自动化环境配置
    jmeter进行websocket 通信
  • 原文地址:https://www.cnblogs.com/iwehdio/p/14379455.html
Copyright © 2011-2022 走看看