• 《Redis 设计与实现》读书笔记(二)


    单机数据库实现

    九、数据库

    1.服务器中的数据库

    一个redis服务器保存多个数据库。

    struct redisServer {
    		//一个数组,多个数据库
    	redisDb *db;
    }
    

    当执行select 1,就是切换数据库到db[1],具体就是会修改redisClient.db指针到redisServer.db[1]

    2.数据库键空间

    typedef struct redisDb{

    dict *dict;//数据库键空间
    dict *expires;//过期时间
    

    }

    这里的dict就是上面说的字典数据结构。

    这个字典的key就是redis里面的key,每个key都是字符串对象
    值就是数据库的值,可以是字符串对象,列表对象,哈希对象,集合对象,游戏集合对象。

    3.键的过期时间

    如果我们对一个键设置过期时间
    redis就会在字典expires里面,加上key=过期的时间戳(精确到毫秒)。
    执行ttl,redis会比较expires里面的时间戳和当前时间的差值,然后返回差值。
    过期key的判定

    • 当访问key时,redis会检查key是否过期,如果是,删除key,并报错key不存在
    • 对于一直没有访问的key,redis会定期扫描expires里面的key,判定key是否过期,如果是,就删除key

    十、RDB持久化

    存在内存中的数据,称为数据库状态
    持久化就是把数据库状态,保存为RDB文件,RDB文件是存在硬盘的。

    1.客户端发起保存

    执行命令save,bgsave,可以立刻把数据库状态保存为RDB文件。

    • save命令是阻塞的,即执行过程中,服务器不能处理其他客户端的请求。
    • bgsave是异步的,redis会启动一个新进程,把内存的数据都复制到新进程,然后执行保存数据到RDB文件的工作,而原进程就继续处理客户端的请求

    2.服务端定期保存

    redis也会定期执行保存操作
    服务器的配置:

    save 900 1
    

    表示服务器在900秒内,对数据库执行了至少一次修改,服务器就会执行保存操作
    如果有多个svae配置,它们直接的关系是或的关系,即满足其中一个,就会执行保存

    struct redisServer{
    	long long dirty;
    	time_t lastsave
    }
    

    数据库对象中,有两个属性:

    • dirty,记录距离上一次保存操作后,数据库执行了多少次修改。
    • lastsave,上一次保存操作的时间戳

    redis通过这两个属性来实现定期保存的机制

    3.RDB文件结构

    RDB文件由

    REDIS db_version databases EOF cehcks_sum
    

    构成

    • redis是一个字符串,
    • db_version是一个4字节的int类型,表示数据库的版本号
    • databases 表示数据库数据
    • EOF 1字节表示文件的结束
    • check_sum表示前面的数据的md5

    1.数据库数据

    databases部分的构成:

    SELECTDB db_number PAIRS

    • SELECTDB是1字节,常量
    • db_number 是数据库编号
    • PAIRS是数据库数据里面的键值对

    2.键值对

    键值对构成
    EXPIRETIME_MS ms TYPE KEY VALUE

    • EXPIRETIME_MS 1字节常量,表示这个键有超时时间,可选
    • ms 超时时间的时间戳
    • TYPE 1字节常量,表示键的类型,redis会根据这个常量,来决定怎么解析后面的KEY和VALUE
    • KEY 是一个字符串对象,存储方法和VALUE的字符串对象一样
    • 值的对象

    3.VALUE编码

    redis会根据TYPE这个常量,来决定怎么读取VALUE的数据。
    KEY肯定就是字符串类型了。

    3.1字符串对象

    如果TYPE=REDIS_ENCODING_STRING,表示这个对象是数值字符串对象
    字符串对象有以下三种存储类型
    int类型

    构成:

    ENCODING  int
    
    • ENCODING 1字节常量,表示int类型,例如16位还是32位
    • int 数字

    无压缩字符串
    如果TYPE=REDIS_ENCODING_RAW,表示这个是普通字符串

    len string
    
    • len 字符串的长度
    • string 字符串的值

    压缩字符串
    如果字符串的长度大于20字节,就会压缩字符串。

    REDIS_RDB_ENC_LZF  compressed_len origin_len compressed_string
    
    • REDIS_RDB_ENC_LZF 1字节常量,压缩的算法
    • compressed_len 压缩后的长度
    • origin_len 原字符串长度
    • compressed_string 压缩后的内容

    问题:
    程序怎么知道这是int类型,还是无压缩字符串,还是压缩字符串的?

    3.2列表对象

    当TYPE=REDIS_RDB_TYPE_LIST 表示这是一个列表对象

    list_length item1 item2 itemN
    
    • list_length 列表的长度
    • item1-N 列表的元素,都是字符串对象

    3.3集合对象

    如果TYPE=REDIS_RDB_TYPE_SET 那么表示这是一个集合对象

    set_size elem1 ... elemN

    • set_size集合的长度
    • elem1 表示集合的元素,字符串对象

    3.4哈希表对象

    如果TYPE=REDIS_RDB_TYPE_HASH 那么表示这是一个哈希表对象

    hash_size key_value_pair1 。。。。。。key_value_pairN
    
    • hash_size 哈希表的键值对数量
    • key_value_pair1 键值对的值

    键值对的构成

    key1 value1 key2 value2
    
    • key1 键值对的键,字符串对象
    • value1 键值对的值,字符串对象

    3.5 有序集合对象

    如果TYPE=REDIS_RDB_TYPE_ZSET,表示这是一个有序集合对象

    sorted_set_size element1 。。。。  elementN
    
    • sorted_set_size元素的数量
    • element1 元素

    每个元素的构成

    member1 score1

    • member1 元素的内容,字符串对象
    • score1 元素的分值,redis会把int类型或者float类型转换为字符串类型保存

    4.RDB文件例子

    REDIS 0 0 0 6 376 003 MSG 005 HELLO 377 207z=304fTL
    343

    • REDIS
    • 0006是版本号
    • 376是SELECTDB常量
    • 是db 0
    • 是字符串类型
    • 003 MSG表示字符串MSG
    • 005 HELLO 表示字符串HELLO
    • 377是EOF
    • 后面是md5

    5.读入RDB文件

    在Redis启动的时候,会自动加载RDB文件,加载成功后,服务器才处理客户端的请求。

    十一、AOF持久化

    不同于RDB一次存储整个数据库状态
    AOF(Append Only File)是每次执行写命令,就append一条指令到文件。

    1.命令追加

    struct redisServer {
    
    	sds aof_buf;
    }
    

    在redis 服务器对象中,有一个aof_buf属性,用于存储AOF命令
    每次服务器执行写命令的时候,都会往这个缓冲区写AOF命令。
    在Redis的时间事件中,会调用flshAppendOnlyFile函数,决定是否吧缓冲区的数据flush到文件。

    例如如果执行命令 set key value
    就会产生下面的AOF命令:

    *34
    $3
    SET4
    $3
    KEY
    $5
    VALUE
    
    

    2.AOF文件载入

    在启动REDIS的时候,会加载AOF文件

    3.AOF重写

    当写命令很多的时候,甚至都是操作很少的几个键的话,例如不断地修改一个key的值,这样就会有很多命令。
    这时候就可以执行重写命令,来把AOF命令压缩。
    例如命令:

    set test a
    set test b
    set test c
    

    会被压缩为

    set test c
    

    因为上面的两条命令就没意义了。

    实际的重写进程,不会去扫描AOF文件,而是会扫描数据库的键值对,然后执行SET或者push等命令。

    4.AOF后台重写

    当执行BGREWRITEAOF命令,Redis就会执行后台重写

    • 启动一个新的进程,把数据库状态复制过去,执行重写操作,也就是扫描所有数据库,里面的所有key
    • 原进程继续处理客户端请求,写操作写入到缓冲区
    • 新进程执行完重写操作后,生成新的AOF文件,发送信号给原进程
    • 原进程收到信号后,把把缓冲区的命令append到新的AOF文件,然后当前的AOF文件切换为新的AOF文件

    十二、事件

    redis是两种事件

    • 文件事件,就是处理客户端的请求
    • 时间时间,处理redis自身的一些定时任务

    1.文件事件

    Redis使用IO多路复用的方式,当有客户端的请求(例如Socket有数据可以读取,有数据可以写),就会生成一个事件,塞到处理队列里面
    每当Redis执行文件事件的时候,就从中取一个事件来执行。
    事件包含

    • Socket号
    • 客户端实例

    2.时间事件

    redis里面会有一个时间事件队列

    一个时间事件包含

    • id 事件的id
    • when 什么时候执行,时间精确到毫秒
    • timeproc 执行函数

    具体实现就是,redis会每个100毫秒,就去这个队列里面遍历,看哪个时间可以执行(when小于当前时间),如果可以就执行。

    • 如果是定期事件,执行完,就会从队列里面删除
    • 如果是周期性事件,执行完,删除事件后,会创建一个新的事件插入到队列

    其实现在Redis只有一个事件事件,就是serverCron

    这个事件会执行:

    • 更新服务器的各类统计信息
    • 清理数据库中过期的键值对
    • 关闭和清理失效的客户端
    • 尝试进行RDB和AOF的持久化操作
    • 如果是主服务器,对从服务器进行定期同步
    • 如果是集群模式,对集群进行定期同步和连接测试

    serverCron每秒运行10次,也就是每100毫秒一次。

    3. 事件的调度

    当Redis服务器启动后,跑完了初始化的任务,就会死循环得跑下面这个流程:

    def aeProcessEvents():
    	time_event=aeSearchNearestTimer() #寻找最近的时间事件
    	remaind_ms=time_event.when-unix_ts_now() #计算事件要在多少毫秒后执行
    	if remaind_ms<0:
    		remaind_ms=0
    	timeval=create_timeval_with_ms(remaind_ms) #通过remaind_ms计算最长阻塞时间
    	aeApiPoll(timeval) #等待文件事件,超时时间为最长阻塞时间,如果remaind_ms=0,就马上返回,不阻塞
    	processFileEvents() #执行文件事件
    	processTimeEvents() #执行时间事件
    

    所以总的来说

    • 会阻塞进程来等待文件事件
    • 阻塞的时间不会超过下个时间事件的执行时间

    这样可以保证

    • 时间事件可以尽量准时(不是完全准时)地被执行
    • 文件事件也可以及时处理

    十三、客户端

    Redis的服务端对象有一个列表,保存所有的客户端对象,每个连接过来的客户端,都会创建一个客户端实例。

    struct redisServer {
    
    	list *clients;
    	
    }
    

    redis命令 client list 可以查看所有连接的客户端。

    客户端的属性有:

    1. flags,使用不同的标志来表示客户端的角色,例如是普通的客户端,还是主从同步的客户端等等
    2. 输入缓冲区,客户端发来的数据,首先放到这里,然后再进行处理
    3. argv,argc,命令的参数值,和数量
    4. 输出缓冲区,有两个,一个是固定大小的16KB,一个是可变大小的
    5. 套接字ID,
    6. 身份验证,记录客户端是否进行了身份验证,如果否,不能执行除auth外的其他命令
    7. 命令实现函数,命令名和实现函数的映射,这个应该是全局统一的,不是每个客户端都有一个的
    8. 名字,客户端可以自己设置名字,如果没有设置,为NULL
    9. 创建时间,客户端的创建时间戳
    10. 上一次执行命令的时间,用于计算空转时间

    客户端的生命周期:

    1. 创建,当客户端连接上服务端后,服务端就会创建一个客户端实例,添加到clients列表的后面
    2. 客户端发送命令给服务端
      1. 发送的命令首先存储到输入缓冲区,然后生成文件事件E1
    3. 服务端处理命令
      1. 当Redis主进程执行该文件事件E1时,就会解析输入缓冲区,解析里面的参数,和参数个数,存储到argv和argc。例如如果执行命令 set test aa,参数就是['set','test','aa'],个数是3
      2. 解析后,根据argv[0],在命令实现函数里面寻找set命令对应的执行函数,命令部分大小写
      3. 调用执行函数,传入client对象
      4. 执行函数里面,执行相应的操作,把返回结果,存储到输出缓冲区。根据返回结果的大小,决定存储到哪个缓冲区,如果大小超出服务器的限制,就直接关闭客户端。
      5. 生成套接字可写的文件事件E2
      6. 结束当前的文件事件
    4. 服务端返回命令的结果
      1. 当服务端执行文件事件E2时,把输出缓冲区的数据,传输给客户端
    5. 关闭客户端
      1. 当出现以下情况,会关闭客户端
        1. 客户端进程退出或者杀死,这样网络连接(Socket)就会被关闭,客户端也会被关闭
        2. 客户端发送不符合协议格式的请求
        3. 客户端成功CLIENT KILL命令的目标
        4. 如果服务器设置了timeout属性(客户端的超时时间),而客户端的空转时间超过timeout。如果客户端在执行BLPOP,订阅等命令,就不会被关闭。
        5. 发送的数据或者返回的数据超出缓冲区的限制

    十四、服务器

    1. 执行set命令的整个流程

    参考上面的客户端生命周期

    2.serverCron函数

    参考时间事件里面的serverCron说明。
    除此之外还有:

    1. 更新时间缓存,Redis里面有很多地方要用到服务器时间,对于时间精度要求不高的地方,Redis会使用时间缓存,而不是再去调用系统函数。
    2. 更新LRU时钟,
    3. 更新服务器每秒执行命令次数
    4. 更新服务器内存峰值
    5. 处理SIGTERM信号。启动服务器的时候,Redis会监听SIGTERM信号,当收到信息后,会把shutdown_asap属性置为1,在serverCront中,如果这个属性是1,就会关闭服务器
    6. 管理客户端资源
      1. 如果客户端连接超时,关闭客户端
    7. 管理数据库资源,例如删除过期的键,对字典进行收缩操作
    8. 执行被延迟的BGREWRITEAOF操作
    9. 检查持久化操作的状态
    10. 将AOF缓冲区的内容写入AOF文件
    11. 增加cronloops计数器,这个计数器记录了serverCron被执行的次数

    3.初始化服务器

    当启动服务器的时候,服务器会做以下操作

    1. 初始化状态结构
    2. 载入配置选项
    3. 初始化服务器数据结构,例如服务器实例,例如共享内存的数据
    4. 还原数据库状态,即载入RDB文件或者AOF文件
    5. 执行事件循环,就是事件章节中的死循环
  • 相关阅读:
    前端css常用class命名id命名
    javaScript获取url问号后面的参数
    ASP.NET MVC 基础知识整理(一)
    Java基础概念(二)
    Java基础概念(一)
    ionic隐藏头部导航栏
    ionic开发中页面跳转隐藏底部Ttab
    /Date(1354116249000)/ 这样的格式怎么转成时间格式 JS
    ionic ng-repeat 循环传值
    ionic页面跳转传值 ng-click
  • 原文地址:https://www.cnblogs.com/Xjng/p/12085109.html
Copyright © 2020-2023  润新知