上一篇文章介绍了redis基本的数据结构和对象《redis设计与实现》1-数据结构与对象篇
本文主要关于:
所在文件为server.h。数据库中所有针对键值对的增删改查,都是对dict做操作
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
读写键空间时,是针对dict做操作,但是除了完成基本的增改查找操作,还会执行一些额外的维护操作,包括:
过期时间保存在expires的字典中,值为long类型的毫秒时间戳
策略类型 | 描述 | 优点 | 缺点 | redis是否采用 |
---|---|---|---|---|
定时删除 | 通过定时器实现 | 保证过期键能尽快释放 | 对cpu不友好,影响相应时间和吞吐量 | 否 |
惰性删除 | 放任不管,查询时才去检查 | 对cpu友好 | 没有被访问的永远不会被释放,相当于内存泄露 | 是 |
定期删除 | 每隔一段时间检查 | 综合前面的优点 | 难于确定执行时长和频率 | 是 |
redis采用了惰性删除和定期删除策略
生成rdb文件的两个命令如下,实现函数为rdb.c文件的rdbSave函数:
RDB文件的载入是在服务器启动时自动执行的,实现函数为rdb.c文件的rdbload函数。载入期间服务器一直处于阻塞状态
redis允许用户通过设置服务器配置的server选项,让服务器每隔一段时间(100ms)自动执行BGSAVE命令(serverCron函数)
//server.c中main函数内部创建定时器,serverCron为定时任务回调函数
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
// 任意一个配置满足即执行
save 900 1 // 900s内,对服务器进行至少1次修改
save 300 10 // 300s内,对服务器至少修改10次
// 服务器全局变量,前面介绍过
struct redisServer {
...
/* RDB persistence */
// 上一次执行save或bgsave后,对数据库进行了多少次修改
long long dirty; /* Changes to DB from the last save */
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
pid_t rdb_child_pid; /* PID of RDB saving child */
struct saveparam *saveparams; /* Save points array for RDB */
int saveparamslen; /* Number of saving points */
char *rdb_filename; /* Name of RDB file */
int rdb_compression; /* Use compression in RDB? */
int rdb_checksum; /* Use RDB checksum? */
// 上一次成功执行save或bgsave的时间
time_t lastsave; /* Unix time of last successful save */
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
time_t rdb_save_time_last; /* Time used by last RDB save run. */
time_t rdb_save_time_start; /* Current RDB save start time. */
int rdb_bgsave_scheduled; /* BGSAVE when possible if true. */
int rdb_child_type; /* Type of save by active child. */
int lastbgsave_status; /* C_OK or C_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
...
};
// 具体每一个参数对应的变量
struct saveparam {
time_t seconds;
int changes;
};
复制代码
每个database的内容:
type为value的类型,1字节,代表对象类型或底层编码,根据type决定如何读取value
每个value保存一个值对象,与type对应。type不同,value的结构,长度也有所不同
- 如果字符串长度>20字节,压缩保存
- REDIS\_RDB\_ENC\_LZF:常量,标识字符串被lzf算法压缩过
- compressed\_len:被压缩后的长度
- origin\_len:字符串原始长度
- compressed\_string:压缩后的内容
使用linux自带的od命令可以查看rdb文件信息,比如od -c dump.rdb,以Ascii打印,下图显示docker创建的redis中,空的rdb文件输出的内容
除了RDB持久化外,redis还提供了AOF持久化功能。区别如下:
AOF持久化分为三步:
事件结束时调用flushAppendOnlyFile函数,考虑是否将aof_buf内容写到AOF文件里(参数决定)
服务器只需要读入并执行一遍AOF命令即可还原数据库状态,读取的步骤如下:
redis是一个事件驱动程序,事件包括两大类:
可选的io多路复用包括select,epoll,evport,kqueue实现。每种实现都放在单独的文件中。编译时根据不同的宏切换不同的实现
事件类型
#define AE_NONE 0 /* No events registered. */
#define AE_READABLE 1 /* Fire when descriptor is readable. */
#define AE_WRITABLE 2 /* Fire when descriptor is writable. */
复制代码
redis为文件事件编写了多个处理器,分别用于实现不同的网络需求,在networking.c文件中,包括:
时间事件分类以下两大类,取决于时间处理器的返回值:
时间事件包括三个属性:
int main() {
...
aeMain(server.el);
...
}
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
复制代码
redis服务器为每个连接的客户端建立了一个redisClient的结构,保存客户端状态信息。所有客户端的信息放在一个链表里。可通过client list命令查看
struct redisServer {
...
list *clients;
...
}
复制代码
客户端数据结构如下:
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
//客户端套接字描述符,伪客户端该值为-1(包括AOF还原和执行Lua脚本的命令)
int fd; /* Client socket. */
redisDb *db; /* Pointer to currently SELECTed DB. */
// 客户端名字,默认为空,可通过client setname设置
robj *name; /* As set by CLIENT SETNAME. */
// 输入缓冲区,保存客户端发送的命令请求,不能超过1G
sds querybuf; /* Buffer we use to accumulate client queries. */
size_t qb_pos; /* The position we have read in querybuf. */
sds pending_querybuf; /* If this client is flagged as master, this buffer
represents the yet not applied portion of the
replication stream that we are receiving from
the master. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
// 解析querybuf,得到参数个数
int argc; /* Num of arguments of current command. */
// 解析querybuf,得到参数值
robj **argv; /* Arguments of current command. */
// 根据前面的argv[0], 找到这个命令对应的处理函数
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
int reqtype; /* Request protocol type: PROTO_REQ_* */
int multibulklen; /* Number of multi bulk arguments left to read. */
long bulklen; /* Length of bulk argument in multi bulk request. */
// 服务器返回给客户端的可被空间,固定buff用完时才会使用
list *reply; /* List of reply objects to send to the client. */
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
size_t 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;
// 客户端角色和状态:REDIS_MASTER, REDIS_SLAVE, REDIS_LUA_CLIENT等
int flags; /* Client flags: CLIENT_* macros. */
// 客户端是否通过身份验证的标识
int authenticated; /* When requirepass is non-NULL. */
int replstate; /* Replication state if this is a slave. */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */
sds replpreamble; /* Replication DB preamble. */
long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
copying this slave output buffer
should use. */
char replid[CONFIG_RUN_ID_SIZE+1]; /* Master replication ID (if master). */
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
char slave_ip[NET_IP_STR_LEN]; /* Optionally given by REPLCONF ip-address */
int slave_capa; /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
multiState mstate; /* MULTI/EXEC state */
int btype; /* Type of blocking op if CLIENT_BLOCKED. */
blockingState bpop; /* blocking state */
long long woff; /* Last write global replication offset. */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
listNode *client_list_node; /* list node in client list */
/* Response buffer */
// 记录buf数组目前使用的字节数
int bufpos;
// (16*1024)=16k,服务器返回给客户端的内容缓冲区。固定大小,存储一下固定返回值(如‘ok’)
char buf[PROTO_REPLY_CHUNK_BYTES];
} client;
复制代码
服务器记录了redis服务器所有的信息,包括前面介绍的一些,罗列主要的如下:
struct redisServer {
...
// 所有数据信息
redisDb *db;
// 所有客户端信息
list *clients;
/* time cache */
// 系统当前unix时间戳,秒
time_t unixtime; /* Unix time sampled every cron cycle. */
time_t timezone; /* Cached timezone. As set by tzset(). */
int daylight_active; /* Currently in daylight saving time. */
// 系统当前unix时间戳,毫秒
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
// 默认没10s更新一次的时钟缓存,用于计算键idle时长
unsigned int lruclock; /* Clock for LRU eviction */
// 抽样相关的参数
struct {
// 上次抽样时间
long long last_sample_time; /* Timestamp of last sample in ms */
// 上次抽样时,服务器已经执行的命令数
long long last_sample_count;/* Count in last sample */
// 抽样结果
long long samples[STATS_METRIC_SAMPLES];
int idx;
} inst_metric[STATS_METRIC_COUNT];
// 内存峰值
size_t stat_peak_memory; /* Max used memory record */
// 关闭服务器的标识
int shutdown_asap; /* SHUTDOWN needed ASAP */
// bgsave命令子进程的id
pid_t rdb_child_pid; /* PID of RDB saving child */
// bgrewriteaof子进程id
pid_t aof_child_pid; /* PID if rewriting process */
// serverCron执行次数
int cronloops; /* Number of times the cron function run */
...
}
复制代码