学习《Redis设计与实现》的一些关键点笔记。
点击下载:《Redis设计与实现》提取码: 2hur
数据类型底层数据结构
数据结构
- 动态字符串(sds )
- 链表(list)
- 字典(dict)
- 跳跃表(skipList)
- 整数集合(intSet)
- 压缩列表(zipList)
String
String ->sds
List
List -> list->listNode
List -> zipList
如果List中元素数量比较小,并且值都是小的整数,或者比较短的字符串,会使用zipList
Hash
Hash -> dict->dictht->dictEntry
Hash -> zipList
如果Hash表中,元素数量比较小,并且每个键值对都是小的整数,或者比较短的字符串,就会使用zipList
ZSet
ZSet -> skipList
有序集合包含元素数量比较多,或者有序集合中有比较长的字符串,ZSet就是这时候使用skipList
Set
Set->intSet
如果一个集合只包含整数值,并且集合元素数量不多时,采用intSet结构。
数据对象
redisObject{
Type;
Encoding;// 编码类型
ptr;//数据指针
Refcount;// 引用计数,和内存回收有关
lru;// 记录空转时长,lru的淘汰机制有关
}
数据库
redisServer{
// 数据库数量(默认16个),也就是下面数组的长度
int dbNum;
// 一个数组,保存所有数据库
redisDb *db;
// 一个数组,记录RDB持久化条件
savaParam *saveParams;
// AOF缓冲区
Sds aof_buf;
}
redisClient{
// 客户端当前使用的数据库
redisDb *db;
}
redisDb{
// 数据库字典,保存着数据库中所有键值对
Dict *dict;
// 过期数据字典,保存数据库所有 设置了过期时间的键,过期时间是Unix时间戳
dict *expires;
}
过期键删除策略
redis实际使用的是惰性删除和定期删除两种策略。可以很好合理利用CPU和内存之前的平衡。
-
定时删除:为key设置过期时间的同时,创建一个定时器,在key过期时立即删除。
对内存友好,但是浪费CPU时间,影响响应时间和吞吐量,适用于内存紧张,CPU宽裕的场景。 -
定期删除:每隔一段时间,程序去过期字典里检查一遍,删除已经过期的键。
对CUP友好,但是浪费内存(如果在过期后一直不访问,就一直存在,类似内存泄漏)。 -
惰性删除:不主动删除,就算Key已经过期,也不管。但是每次从字典空间获取,都会检查一遍key是否过期,过期了就删除,未过期,就返回。
RDB持久化
RDB持久化可以手动执行命令(save、bgsave),或者根据配置定期执行,将当前时间的内存数据生成RDB文件,保存到磁盘中,在服务器启动的时候再从磁盘加载RDB文件,来还原内存数据。
SAVE:该命令直接阻塞服务器进程,直到文件创建完毕,在这期间,服务器不能处理任何命令。
BGSAVE:该命令会生成一个子进程,由子进程后台去创建RDB文件,服务器进程继续处理请求。
配置:可以为save设置多个条件,只要有一个条件满足,就会执行BGSAVE命令。配置保存的redisServer的saveParams中。
检查条件:redis会使用serverCron默认每隔100毫秒执行一次,检查是否满足RDB保存条件。
od命令:od -cx dump.rdb 可以打印输出,RDB文件内容。
AOF持久化
AOF是通过记录Redis服务接收的命令,完成持久化操作,在服务器启动时,会加载AOF文件,执行文件中的内容,还原数据库数据。
命令追加:开启AOF命令,有写命令过来会追加到aof_buf缓冲区末尾。
redisServer{
// AOF缓冲区
Sds aof_buf;
}
文件写入:写入函数 flushAppendOnlyFIle()
函数,可以根据appendfsync
配置的值,决定写入AOF文件的时机。
- always: 立即写入
- everysec :一个线程专门负责执行写入AOF文件,时机 距离上次超过一秒钟。
- no: 所有内容写入到AOF文件,但是不主动同步到磁盘,由操作系统决定。
执行命令:BGReWriteAOF,Redis服务器会创建一个子进程,维护AOF重写缓冲区,记录所有的写命令。
事件
Redis服务是一个事件驱动程序,分文两种事件:
- 文件事件:Redis服务端和 客户端,利用套接字通信产生的文件事件。
- 时间事件:Redis服务端中的一些时间时间,比如 定时函数(serverCron函数)。
文件事件
Redis网络事件处理器是基于Reactor模式实现的。使用I/O多路服务程序同时监听多个套接字,并根据套接字 分发给 不同的事件处理器。
文件事件处理器的组成部分:套接字、I/O多路复用程序、文件事件分发器(dispatcher)、事件处理器组成。
一次完整的请求:
时间事件
Redis时间事件分为两种:
- 定时事件:让程序在指定的时间之后执行一次。
- 周期性事件:让程序 每隔一段时间执行一次。
事件属性:
- ID:全局唯一ID,并且按顺序递增。
- when:毫秒精度UNIX时间戳,记录事件的到达时间。
- timeProc:事件处理器,一个函数。当时间到达时,就调用这个处理器。
实现:
服务器维护了一个无序链表,将所有的时间事件都放进去,每次运行都遍历链表,执行事件到达的 时间处理器。
事件原理:
客户端
redisServer
redsiServer{
// 一个链表,保存了所有客户端状态
list *clients;
}
redisClient
/*
* 因为 I/O 复用的缘故,需要为每个客户端维持一个状态。
* 多个客户端状态被服务器用链表连接起来。
*/
typedef struct redisClient {
// 套接字描述符
int fd;
// 当前正在使用的数据库
redisDb *db;
// 当前正在使用的数据库的 id (号码)
int dictid;
// 客户端的名字
robj *name; /* As set by CLIENT SETNAME */
// 查询缓冲区
sds querybuf;
// 查询缓冲区长度峰值
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
// 参数数量
int argc;
// 参数对象数组
robj **argv;
// 记录被客户端执行的命令
struct redisCommand *cmd, *lastcmd;
// 请求的类型:内联命令还是多条命令
int reqtype;
// 剩余未读取的命令内容数量
int multibulklen; /* number of multi bulk arguments left to read */
// 命令内容的长度
long bulklen; /* length of bulk argument in multi bulk request */
// 回复链表
list *reply;
// 回复链表中对象的总大小
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
// 已发送字节,处理 short write 用
int sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
// 创建客户端的时间
time_t ctime; /* Client creation time */
// 客户端最后一次和服务器互动的时间
time_t lastinteraction; /* time of the last interaction, used for timeout */
// 客户端的输出缓冲区超过软性限制的时间
time_t obuf_soft_limit_reached_time;
// 客户端状态标志
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
// 当 server.requirepass 不为 NULL 时
// 代表认证的状态
// 0 代表未认证, 1 代表已认证
int authenticated; /* when requirepass is non-NULL */
// 复制状态
int replstate; /* replication state if this is a slave */
// 用于保存主服务器传来的 RDB 文件的文件描述符
int repldbfd; /* replication DB file descriptor */
// 读取主服务器传来的 RDB 文件的偏移量
off_t repldboff; /* replication DB file offset */
// 主服务器传来的 RDB 文件的大小
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
// 主服务器的复制偏移量
long long reploff; /* replication offset if this is our master */
// 从服务器最后一次发送 REPLCONF ACK 时的偏移量
long long repl_ack_off; /* replication ack offset, if this is a slave */
// 从服务器最后一次发送 REPLCONF ACK 的时间
long long repl_ack_time;/* replication ack time, if this is a slave */
// 主服务器的 master run ID
// 保存在客户端,用于执行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
// 从服务器的监听端口号
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
// 事务状态
multiState mstate; /* MULTI/EXEC state */
// 阻塞类型
int btype; /* Type of blocking op if REDIS_BLOCKED. */
// 阻塞状态
blockingState bpop; /* blocking state */
// 最后被写入的全局复制偏移量
long long woff; /* Last write global replication offset. */
// 被监视的键
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
// 这个字典记录了客户端所有订阅的频道
// 键为频道名字,值为 NULL
// 也即是,一个频道的集合
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
// 链表,包含多个 pubsubPattern 结构
// 记录了所有订阅频道的客户端的信息
// 新 pubsubPattern 结构总是被添加到表尾
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
/* Response buffer */
// 回复偏移量
int bufpos;
// 回复缓冲区
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
未完待续。。。。。。