0%

redis Server

redis Server

数据库切换

默认会创建 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 数据格式

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 库)

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

  • 尝试进行持久化操作

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

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