数据库切换

默认会创建16个数据库,客户端通过select选取。但一般情况只用第0个数据库,切换容易导致误操作

1
2
3
4
5
6
7
8
9
typedef struct redisDb {

  dict *dict; //键空间

  dict *expires; //过期字典

  int id;

} redisDb;

所有键空间存储在redisDb的dict中,称为key space

每个键是字符串对象,值是各种对象

读写键操作

  1. 更新keyspace_hits和keyspace_misses,用来输出统计数据
  2. 更新键的LRU时间
  3. 若发现该键已经过期,则删除键
  4. 若该键被watch,标记键为dirty,使得监听者发现后重新拉数据
  5. dirty计数器++,用来触发持久化和复制操作

过期字典的键是键空间的键字符串对象的指针(不会新分配空间),值是longlong类型的过期时间(毫秒精度的unix时间戳)

判断是否过期:

先在过期字典里取key的过期时间,再与当前时间比较

删除策略

(redis同时采用惰性删除和定期删除策略,其中定期删除是随机取出一定数量的键做检查):

  • 定时删除:过期时立刻删除(问题:要创建大量定时器,占用太多CPU,因此不合理)
  • 惰性删除:获取键时若过期才删除 (问题:内存最不友好)
  • 定期删除:定期对所有key进行检查并删除 (问题:如何确定定期时间,太快或太慢都不好)

持久化

解密Redis持久化 - justjavac - 博客园 (cnblogs.com)

rdb

记录键值

主服务器初始化加载时不会加载过期的键值,从服务器会加载过期的键值,但同步之后也会被清空掉

主节点统一管理过期删除,从节点只能被动接收del命令,保证了数据一致性,但从节点里可能会有过期键值

SAVE阻塞保存,BGSAVE用子进程保存

自动保存:自动保存规则设置在一个列表中,表示一段时间内进行了多少次改动就满足保存规则

每次写入会将db的dirty计数器加1,且每次保存会保存的时间戳lastsave。当距离lastsave的时间超过条件中设置的时间,比较dirty与规则中设置的改动次数,若满足则触发BGSAVE

RDB数据格式

REDISdb_versiondatabase 0database 3EOFcheck_sum

格式细节包括压缩算法略过

aof

记录写命令(启动时优先选择加载aof)

命令追加:按redis协议追加到aof_buf缓冲区中

文件写入和同步:redis server主线程每次循环结束前,将缓冲区写入aof文件,并调用fsync落盘

同步策略:always(每次都落盘),everysec(离上次落盘超过一秒触发落盘), no(靠操作系统自己落盘,一般是30s)

过期但还未被删除的键值不会追加到aof中,只有惰性删除或定期删除显示调用del后才会追加DEL命令

aof重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。

重写是子进程,进程的数据是父进程数据的副本(为了避免加锁),重写时会数据不同步,通过在父进程加一个aof重写缓冲区解决。父进程在重写过程中的写操作会同时写到aof缓冲区和aof重写缓冲区。子进程写完后通知父进程将aof重写缓冲区的数据追加到aof文件中,追加完毕后使用rename原子操作覆盖现有aof文件

事件(ae库)

img

img

事件分派器

对select,epoll的封装,当socket可读或可写时,执行事件处理器的处理函数

可读(sever侧):AE_READABLE client对socket执行write,close,connect

可写(server侧):AE_WRITABLE client对socket执行read

aeMain中循环执行aeProcessEvents,对aeEventLoop中的事件进行处理

注意为了不影响到定时器事件的执行,select,epoll的超时需在最近一次定时器事件发生前退出

img

文件事件处理器

此处的注册比较分散,创建事件时会注册对应的事件处理器(aeCreateFileEvent),注册顺序是先acceptTcpHandler,再在acceptTcpHandler根据连接来嵌套注册别的处理器

连接应答处理器 acceptTcpHandler

redis server在初始化时创建该事件,创建成功表示server已经起来了

使用accept新建一个client的cfd,并使用该cfd创建client

命令请求处理器 readQueryFromClient ,事件类型AE_READABLE

createClient时会同时创建该client的命令请求处理事件,当client往socket write时(发起请求),会触发事件的处理,将socket中数据存入client对象中并解析为argv和argc,之后调用对应的命令处理函数,生成返回值,最后注册命令回复事件sendReplyToClient

命令回复处理器 sendReplyToClient,事件类型AE_WRITABLE

调用write往cfd中写数据

数据同步处理器 sendBulkToSlave

时间事件

分为定时事件和周期性事件。定时事件只执行一次,aeProcessEvents执行完事件注册的处理器函数(te->timeProc)后返回AE_NOMORE,表示后续不再执行并在链表中删除该事件。周期性事件执行完后刷新事件内的when属性,并让事件保留

时间事件保存在一个无序链表(没有按照when来排序)中,每次需要整个遍历链表来获取要执行的事件,但是由于事件很少(低版本只有一个serverCron事件),该链表的遍历不会消耗太多的性能

serverCron

整个server定时cronjob的集合函数,负责:

  • 更新统计信息

  • 清理失效键值对

  • 关闭清理失效的client

  • 尝试进行持久化操作

  • 主服务器向从服务器数据同步

  • 若处于集群模式,与其他机器定期同步和连接测试