前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单机数据库的实现(上)

单机数据库的实现(上)

作者头像
用户7962184
发布2020-11-20 15:06:26
7410
发布2020-11-20 15:06:26
举报
文章被收录于专栏:没事多喝水没事多喝水

单机数据库的实现

数据库

在服务器内部,客户端状态redisClient结构的db属性记录了客户端当前的目标数据库。

谨慎处理多数据库,因为指定了特定的数据库之后,其它语言的客户端并无明显提醒和感知。

typedef struct redisDb {
       // ...
       // 数据库键空间,保存着数据库中的所有键值对
       dict *dict;
       //过期字典
       dict *expires;
       // ...
   } redisDb;

数据库的键空间是一个字典。针对数据库中的key去进行增删查改的时候,事实上方式和hash的增删查改一致。

在读写键空间时,会伴随着一些额外的操作:

  1. 修改键空间命中hit和miss次数,统计。
  2. 修改一个键的LRU时间。
  3. 如果这个过期键已过期,则删除数据再操作 。
  4. 如果WATCH命令监控某个键,那会在修改这个键的时候将这个键标记为dirty。
  5. 每次修改一个键之后,都会对dirty键加1,用来触发持久化和复制操作。
  6. 如果开启了数据库通知功能,那么对键进行修改后,服务器会按照配置发送相应的通知。

键空间保存了数据库中的所有键值对,而过期字典保存了数据库键的过期时间。

他们的键空间的键都是同一个对象。值不同,expires指向的是一个long类型的值,是过期时间。

  • 键过期删除是怎么实现的?

通常有三种策略:定时删除(键创建的时候起定时器去删除),惰性删除(查到才删),定期删除(每隔一段时间就检查那些key,如果有过期的就删除)

redis用的是惰性删除和定期删除两种策略。第一种很耗费cpu。

惰性删除中,如果遇到key过期的情况,就先删除key再执行命令。

定期删除中,从数据库中的expires字典中随机检查一部分的键的过期时间,并删除其中的过期键。

  • AOF,RDB,和复制功能对过期键的处理?

RDB的时候会检查key是否过期,如果过期就忽略该key,载入的时候,如果是主库,如果key过期,就会不载入该key,如果是从库,则会都载入。

AOF,如果有key被定期删除或者惰性删除之后,会向AOF追加一条DEL命令,来显示删除该键。载入的时候,跟RDB是一致的。

复制,主服务器删除一个过期键的时候,会显示地向从数据库发送一个DEL命令。从数据库收到读命令时,就算是过期key也不会删除,而是按照未过期的键处理。从服务器只有收到DEL命令才会删除key。

RDB持久化

RDB有两个命令可以持久化,SAVE是阻塞的,BGSAVE是非阻塞的。

redis没有显示读入RDB备份的命令,启动的时候会检测RDB的存在,自动载入RDB。

AOF的更新频率比RDB更新频率高,所以服务器会优先使用AOF来还原数据库状态。

BGSAVE会在满足特定的条件执行,比如save 900 1 那么就是900秒内有1次修改就执行BGSAVE。可以有多条。

redis有个dirty计数器在每次修改操作之后都会+1,lastsave是一个时间戳,保存上次成功执行save和bgsave的时间戳,这两个参数能实现上面的满足特定的条件执行。

RDB文件结构

总体结构

image-20200825101501566

RDB 文件的最开头是 REDIS 部分, 这个部分的长度为 5 字节, 保存着 "REDIS" 五个字符。

db_version 长度为 4 字节, 它的值是一个字符串表示的整数, 这个整数记录了 RDB 文件的版本号, 比如 "0006" 就代表 RDB 文件的版本为第六版。 本章只介绍第六版 RDB 文件的结构。

databases 部分包含着零个或任意多个数据库, 以及各个数据库中的键值对数据:

  • 如果服务器的数据库状态为空(所有数据库都是空的), 那么这个部分也为空, 长度为 0 字节。
  • 如果服务器的数据库状态为非空(有至少一个数据库非空), 那么这个部分也为非空, 根据数据库所保存键值对的数量、类型和内容不同, 这个部分的长度也会有所不同。

EOF 常量的长度为 1 字节, 这个常量标志着 RDB 文件正文内容的结束, 当读入程序遇到这个值的时候, 它知道所有数据库的所有键值对都已经载入完毕了。

check_sum 是一个 8 字节长的无符号整数, 保存着一个校验和, 这个校验和是程序通过对 REDISdb_versiondatabasesEOF 四个部分的内容进行计算得出的。 服务器在载入 RDB 文件时, 会将载入数据所计算出的校验和与 check_sum 所记录的校验和进行对比, 以此来检查 RDB 文件是否有出错或者损坏的情况出现。

databases部分

image-20200825101839735

SELECTDB 常量的长度为 1 字节, 当读入程序遇到这个值的时候, 它知道接下来要读入的将是一个数据库号码。

db_number 保存着一个数据库号码, 根据号码的大小不同, 这个部分的长度可以是 1 字节、 2 字节或者 5 字节。 当程序读入 db_number 部分之后, 服务器会调用 SELECT 命令, 根据读入的数据库号码进行数据库切换, 使得之后读入的键值对可以载入到正确的数据库中。

key_value_pairs 部分保存了数据库中的所有键值对数据, 如果键值对带有过期时间, 那么过期时间也会和键值对保存在一起。 根据键值对的数量、类型、内容、以及是否有过期时间等条件的不同, key_value_pairs 部分的长度也会有所不同。

key_value_pairs 部分

image-20200825103020828

image-20200825103137148

value编码部分

字符串对象:如果是整数,会以REDIS_RDB_ENC_INT8的类型记录,如果是短字符串会被原样保存,如果是大于20字节并且开启了rdb压缩选项,则会进行压缩之后存储。

image-20200825145243236

image-20200825145253813

image-20200825145301076

列表对象:

image-20200825145419148

eg:

image-20200825145432236

集合对象:与列表对象相似。

有序集合对象:

image-20200825145607627

多纪录分值。

如果是ZIPLIST的话,那处理方式类似于字符串,因为redis会根据TYPE去做相应的处理。

AOF持久化

AOF持久化的实现可以分为命令追加,文件写入,文件同步三个步骤。

命令在执行的时候,是会先写入aof_buf缓冲区。

然后有个死循环会不停地将缓冲中的数据写入文件中,然后根据appendfsync选项配置的值来决定什么时候同步aof文件。

always:每次都写并且同步,everysec:每次都写,每秒同步一次,no:只写不同步,同步靠系统。

同步是指调用系统函数fsync,让文件缓存中的数据真正写入磁盘中。

  • AOF恢复过程是怎么样的?

通过创建一个不带网络的伪客户端,去将AOF文件中的数据执行一遍来恢复数据。

从上面得知,AOF持久化会使AOF文件不断膨胀,为了避免这个问题,redis提供了文件重写的功能。

AOF文件重写的实现

AOF不是通过旧AOF文件去进行优化重写的,数据来源是通过redis服务器的。

AOF重写进程时通过子进程实现的,通过子进程遍历数据,忽略过期数据,多条数据合并,这样的方式可以让redis主进程继续服务客户端。

但是在主进程继续服务客户端的过程中,可能还会继续产生数据,导致重写后的AOF文件数据不一致。为了解决这个问题redis还设置了一个AOF重写缓冲区,来将重写过程中产生的数据保留起来。

重写结束后,再将重写缓冲区的数据写入aof文件中,最后将aof的重写文件改名覆盖原有的aof文件,进行覆盖。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单机数据库的实现
    • 数据库
      • RDB持久化
        • RDB文件结构
      • AOF持久化
        • AOF文件重写的实现
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档