1. 定义
- Redis数据库结构
1 | typedef struct redisDb { |
Redis服务器
Redis服务器将所有数据库都保存在db数组中,通过dbnum来决定Redis数据库实例的个数。
1 | struct redisServer { |
- Redis客户端
1 | typedef struct client { |
2. Redis数据库
2.1 dict 键空间
redisDb结构中dict属性保存了该数据库所有的键值对,被称之为键空间。
- 每个键都是一个字符串,客户端所有设置key都存储在该字典中
- 每个值可以是字符串对象、列表对象、哈希对象、集合对象和有序集合对象中任意一种。
2.2 expires 过期字典
expires字典保存着当前数据库对象中键的过期时间
- 每个键都是一个指针,指向dict中过期的键
- 每个值都是一个long long类型的整数,保存了dict中key的过期时间(毫秒精度的时间戳)
2.3 blocking_keys 阻塞键字典
blocking_keys字典保存着造成客户端阻塞的键
- 每个键都是一个指针,指向造成客户端阻塞的dict中的键
- 每个值都是一个双端链表,存储着被这个键阻塞的客户端指针
2.4 ready_keys
ready_keys字典保存着阻塞键的等待队列,用于避免重复的阻塞命令
- 每个键都是一个指针,指向一个阻塞键
- 每个值都是NULL
2.5 watched_keys
watched_keys字典保存着被客户端WATCH命令监控的键
- 每个键都是一个指针,指向被监控的键
- 每个值都是一个双向链表,存储着监控这个键的客户端
2.6 读写键的维护
- 读取一个键后,更新其LRU或者LFU,通过OBJECT idletime/freq 可以查询
- 读取一个键后,更新hits或者miss,通过INFO stats 可以查询统计
- 读取一个键时,发现其已经过期,会先删除这个键
- 修改一个键后,会对服务器dirty加1,这个计数器用于持久化和复制操作
3. Redis键过期策略
3.1 设置键的生存时间或过期时间
通过EXPIRE或PEXPIRE命令可以设置键的过期,redis底层过期存储的是毫秒数。
通过TTL命令或PTTL命令可以查询键还有多久过期。
3.2 过期键删除
惰性删除
过期键的惰性删除由expireIfNeed函数实现,所以读写键值对的命令都会调用该函数检查键是否过期。在集群模式中,master节点会对过期键直接删除,但在slave节点中,只是逻辑上的删除,实际仍然存在,只有在master通知slave节点删除时,过期键才会真正的删除。这样由master节点统一管理过期键,在一定程度上保持了一致性。
定时删除
定时过期删除由activeExpireCycle函数实现,当redis执行周期性操作时,该函数会被调用,在规定的时间内,分多次遍历多个数据库,从数据库expires中随机获取,检查过期并删除。
惰性删除中有dbAsyncDelete和dbSyncDelete两种策略,异步操作会将删除操作记录下来,积累到一定程度统一删除。
4. AOF、RDB和复制对过期键处理
生成RDB文件
在执行SAVE或BGSAVE命令创建一个RDB文件时,会对数据库中过期的键进行检查,已过期的不会保存。
载入RDB文件
启动服务器时,若开启RDB功能,那么会载入RDB文件,在集群模式中,master节点会对RDB中已经过期的键进行过滤,不加载,而slave节点则不会考虑该情况。
AOF文件写入
当过期键被删除时,回向AOF文件追加一条DEL指令,来表示键被删除
5. 源码剖析
- 检查键的过期
1 | int expireIfNeeded(redisDb *db, robj *key) { |
- keys命令
1 | void keysCommand(client *c) { |
- scan命令底层实现,利用字典的反向二进制扫描
1 | void scanGenericCommand(client *c, robj *o, unsigned long cursor) { |
- 过期信息传递
1 | /** |