1.数据库的结构
Redis 中的每个数据库,都由一个 redis.h/redisDb 结构表示。
typedef struct redisDb {
// 保存着数据库以整数表示的号码
int id;
// 保存着数据库中的所有键值对数据
// 这个属性也被称为键空间(key space)
dict *dict;
// 保存着键的过期信息
dict *expires;
// 实现列表阻塞原语,如 BLPOP,可用于列表
dict *blocking_keys;
dict *ready_keys;
// 用于实现 WATCH 命令,可用于事务
dict *watched_keys;
} redisDb;
Redis有id 、dict 和 expires 三个重要属性:
- id 保存数据库的号码。Redis 服务器初始化时, 它会创建出 redis.h/REDIS_DEFAULT_DBNUM 个数据库, 并 将所有数据库保存到 redis.h/redisServer.db 数组中, 每个数据库的 id 为从 0 到 REDIS_DEFAULT_DBNUM - 1 的值。当执行 SELECT number 命令时,程序直接使用 redisServer.db[number] 来切换数据库。
- dict 保存着数据库的所有键值对数据。Redis的结构可以看成字典的嵌套,类似json的数据结构。dict的内部依然是字典结构,dict的key是字符串对象,表示name,dict的值则是从string到sort-set中的任意一种对象。而删除数据库的健,实际上就是删除dict中对应的健对象和值对象。
- expires也是一个字典,保存键的过期时间,注意只保存设置过的过期时间,如果没设置,则默认为永久。
1.1 字典的底层实现
hashtable
冲突解决:链表
扩容:渐进式hash,方法是复制出一个hash表,重算hash值(java8已不再重算)。重点是,扩容和收缩不是一次性完成,而是分多次完成。期间,字典的删改查操作可以在两个hashtable上进行,则增加操作只在新hashtable上进行。当字典中保存的数据很多事,可以避免扩容影响性能。
2.过期键的检查和清除
根据不同的清楚策略,通过expires 字典来检查键是否过期:
- 检查键是否存在于 expires 字典:如果存在,那么取出键的过期时间。
- 判断当前 UNIX 时间是否大于键的过期时间,如果是,那么键已经过期。
过期键的清除有三种方式:定时删除、惰性删除和定期删除。
2.1 定时删除
创建一个定时事件,由事件处理 器自动执行键的删除操作。
优点:对内存友好
缺点:可能占用大量cpu时间
2.2 惰性删除
每次从dict字典取出键值时,检查是否过期,如果过期则删除,并返回空。
优点:对cpu友好
缺点:过期键占用内存
核心是 db.c/expireIfNeeded 函数。在读取或写入数据库之前,调用 expireIfNeeded 对输入键进行检查。如果输入键已经过期的话,那么将键、键的值、键保存在 expires 字典中的过期时间都删除掉。
2.3 定期删除
是上面两个策略的结合。每隔一段时间,对 expires 字典进行检查,并执行惰性删除。
核心是redis.c/activeExpireCycle,每当 Redis 的例行处理程序 serverCron 执行时,activeExpireCycle 都会被调用。这个函数在规定的时间限制内,尽可能地遍历各个数据库的 expires 字典,随机地检查一部分键的过期时间,并删除过期键。
2.4 对RDB、AOF和复制的影响
RDB:在创建新的 RDB 文件时,程序会对键进行检查,过期的键不会被写入到更新后的 RDB 文件 中。
AOF:当过期键被惰性删除、或者定期删除后,程序会向 AOF 文件追加一条 DEL 命令,来显式地记录该键已被删除。
复制:当服务器带有附属节点时,过期键的删除由主节点统一控制。主节点再删除过期键后,会会显式地向所有附属节点发送一 个 DEL 命令。附属节点只按DEL命令行动,当它自己碰到过期键时,只向主节点返回键已过期。
3.持久化机制
把数据由内存同步到磁盘,会Fork一个子进程来异步的完成。有三种方式,RDB、AOF和混合方式。
3.1 RDB方式
即快照,定期一次全量备份,将所有缓存进行序列化存到磁盘。
优势:灾难恢复、性能好
劣势:1、归档前断线,则这个归档周期的数据无法恢复。2、子进程工作,如果数据量大,可能影响性能。
配置:
1、修改redis.conf中的save时间:
第一个save的表示每900秒,至少一个key发生变化,则归档一次。第二个save则表示每300秒,至少10个key变化,则归档。第三个同理,是为了应对短时间内的大量服务。
2、也可以修改rdb文件的命名和保存路径:
3.2 AOF方式
以redis网络协议的格式记录对数据库进行的写命令。
优势:append模式写日志,即使宕机,不会影响已记录的日志。
劣势:同数量的数据集,AOF体量比RDB大,效率低。
配置:
在redis.conf中允许打开AOF模式,改为yes:
配置AOF的同步方式,always表示每次修改都要追加日志:
AOF的原理,两个核心函数:
save():aof_buf -> aof文件
write():aof文件 -> 磁盘
共三种模式,第二种综合性性能较好。
3.3 混合方式
增大定期归档的时间跨度,归档间隔期,用AOF记录修改命令。
4.事件
文件事件和时间事件
4.1 文件事件
Redis使用socket进行client和server的通信,来完成实现高效的命令请求处理。采用非阻塞、多路复用IO模式。
在多个客户端中实现多路复用,接受它们发来的命令请求,并将命令的执 行结果返回给客户端。
Redis 将这类因为对套接字进行多路复用而产生的事件称为文件事件。文件事件分为读事件和写事件。
读事件实现了命令请求的接收,生命周期与该客户端和服务器的连接状态相同。
写事件实现了命令结果的返回。
4.2 时间事件
时间事件完成服务器的常规操作,分为单次执行事件和循环执行事件,服务器常规操作 serverCron 就是循环事件。
其实现结构是无序链表,所以查询的时间复杂度为O(N)。
文件事件和时间事件之间是合作关系:一种事件会等待另一种事件完成之后再执行,不会出现抢占情况。由于优先级的问题,时间事件的实际执行时间通常会比预定时间晚一些。
5.参考
《Redis设计和实现》黄健宏
《Redis实战》