以往教程都是从 redis 基本数据结构开始,但是在这些数据结构实现前,redis 服务端如何处理客户端命令的流程,以及如何处理这些请求事件:
- 服务端启动监听
- 接受命令并解析
- 执行命令
- 返回命令请求数据
redis 服务端是典型的事件驱动程序,所以上述说的事件处理尤为重要。redis 将事件分为两大类:文件事件 和 时间时间。文件事件即 socket 读写事件,时间事件用于处理一些周期性执行的定时服务。
为了更好理解服务端和客户端的交互,先看看 redis 数据组织的结构。
robj
redis 是一个 kv 数据库,key 只能是 string,value 用结构体 robj 表示,它可以是字符串,列表,集合等,这个取决于 robj 的 type 字段。
typedef struct redisObject {
unsigned type:4; // 类型(type占4位) -> 5种,定义在<server.h>
unsigned encoding:4; // 编码 -> 11种
unsigned lru:REDIS_LRU_BITS; // 对象最后一次被访问的时间
int refcount; // 引用计数
void *ptr; // 指向实际值的指针
} robj;
ptr:为指针,指向实际存储的某一种数据结构refcount:对象的引用次数,实现对象的共享lru:用于实现缓存淘汰策略,可以在maxmemory_policy配置最大内存限制的缓存淘汰策略LFU:走updateLFU(val)。LRU:走LRU_CLOCK(),获取当前时间并更新。存储的是 上次访问时间 和 访问次数
同时针对一个类型的对象, redis 在不同情况下可能采取不同的数据结构存储,这个要看 encoding ,同时在对象的生命周期,encoding 不是一成不变的。
redisDb
robj 是对基础数据结构的封装,另外还需要一个结构体对数据库进行封装,用于管理数据库有关的相关数据以及相关操作,这就是 redisDb。
typedef struct redisDb {
dict *dict; // 数据库键空间,保存着数据库中的所有键值对
dict *expires; // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict *blocking_keys; // 正处于阻塞状态的键
dict *ready_keys; // 可以解除阻塞的键
dict *watched_keys; // 正在被 WATCH 命令监视的键
int id; // 数据库号码
long long avg_ttl; // 数据库的键的平均 TTL ,统计信息
list *defrag_later; // 尝试逐个碎片整理的key列表
} redisDb;
两个重要的属性:dict 和 expires
- 像
scan, move, sort等是对dict键空间的操作; expire, persist等是对expires过期散列表的操作
redisServer
struct redisServer {
...
redisDb *db; // redis数据库
int dbnum; // db数量,0-16
dict *commands // 所有命令都存储在这个字典中
...
aeEventLoop *el; // 事件循环【下篇着重讲这个】
list *clients; // 当前连接到服务器的所有客户端
...
}
【redisServer 的字段实在是太多了,大致就列举这几个】
这里就可以看到 redisServer, redisDb, dict 的关系了: