redis Server
数据库切换
默认会创建 16 个数据库,客户端通过 select 选取。但一般情况只用第 0 个数据库,切换容易导致误操作
1 | typedef struct redisDb { |
所有键空间存储在 redisDb 的 dict 中,称为 key space
每个键是字符串对象,值是各种对象
读写键操作
- 更新 keyspace_hits 和 keyspace_misses,用来输出统计数据
- 更新键的 LRU 时间
- 若发现该键已经过期,则删除键
- 若该键被 watch,标记键为 dirty,使得监听者发现后重新拉数据
- dirty 计数器++,用来触发持久化和复制操作
过期字典的键是键空间的键字符串对象的指针(不会新分配空间),值是 longlong 类型的过期时间(毫秒精度的 unix 时间戳)
判断是否过期:
先在过期字典里取 key 的过期时间,再与当前时间比较
删除策略
(redis 同时采用惰性删除和定期删除策略,其中定期删除是随机取出一定数量的键做检查):
- 定时删除:过期时立刻删除(问题:要创建大量定时器,占用太多 CPU,因此不合理)
- 惰性删除:获取键时若过期才删除 (问题:内存最不友好)
- 定期删除:定期对所有 key 进行检查并删除 (问题:如何确定定期时间,太快或太慢都不好)
持久化
解密 Redis 持久化 - justjavac - 博客园 (cnblogs.com)
rdb
记录键值
主服务器初始化加载时不会加载过期的键值,从服务器会加载过期的键值,但同步之后也会被清空掉
主节点统一管理过期删除,从节点只能被动接收 del 命令,保证了数据一致性,但从节点里可能会有过期键值
SAVE 阻塞保存,BGSAVE 用子进程保存
自动保存:自动保存规则设置在一个列表中,表示一段时间内进行了多少次改动就满足保存规则
每次写入会将 db 的 dirty 计数器加 1,且每次保存会保存的时间戳 lastsave。当距离 lastsave 的时间超过条件中设置的时间,比较 dirty 与规则中设置的改动次数,若满足则触发 BGSAVE
RDB 数据格式
REDIS | db_version | database 0 | database 3 | EOF | check_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 库)
事件分派器
对 select,epoll 的封装,当 socket 可读或可写时,执行事件处理器的处理函数
可读(sever 侧):AE_READABLE client 对 socket 执行 write,close,connect
可写(server 侧):AE_WRITABLE client 对 socket 执行 read
aeMain 中循环执行 aeProcessEvents,对 aeEventLoop 中的事件进行处理
注意为了不影响到定时器事件的执行,select,epoll 的超时需在最近一次定时器事件发生前退出
文件事件处理器
此处的注册比较分散,创建事件时会注册对应的事件处理器(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
尝试进行持久化操作
主服务器向从服务器数据同步
若处于集群模式,与其他机器定期同步和连接测试