My Personal Time

Desire is the starting point of all achievement

0%

Redis设计与实现笔记八

数据库

Redis服务器将所有的数据库都保存在服务器状态redisServer结构的db数组中,db数组的每项都是一个redisDb结构,每个redisDb结构都代表一个数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct redisDb {

// 数据库键空间,保存着数据库中的所有键值对
dict *dict; /* The keyspace for this DB */

// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
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 */

// 正在被 WATCH 命令监视的键
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */

struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */

// 数据库号码
int id; /* Database ID */

// 数据库的键的平均 TTL ,统计信息
long long avg_ttl; /* Average TTL, just for stats */

} redisDb;

初始化服务器时候,根据redisServer中的dbnum属性决定创建数据库个数,默认为16,默认情况使用0号数据库,通过SELECT命令可以切换目标数据库

客户端状态redisClient结构的db属性记录了客户端当前目标数据库。

数据库键空间

redisDb结构的dict属性保存了数据库中的所有键值对,

键空间的键也就是数据库的键,每个键都是一个字符串对象

键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种

313142

读写键空间时的维护操作

当使用Redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,比如

  1. 在读取一个键后(读操作和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits keyspace_misses属性查看
  2. 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间,使用OBJECT idletime 命令可以查看键的闲置时间
  3. 如果服务器在读取一个键时发现键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作
  4. 如果有客户端使用WATCH命令监视了某个键,那么服务器在对被监视键进行修改后,会将这个键标记为脏(dirty)从而让事务程序注意到这个键已经被修改过
  5. 服务器每次修改一个键后,都会对脏键计数器值增一,这个计数器会触发服务器的持久化以及复制操作
  6. 如果服务器开启了数据库通知功能,那么在对键进行修改后,服务器将按配置发送相应的数据库通知
设置键生存时间或过期时间

有四个不同的命令可以用于设置键的生存时间或过期时间

EXPIRE 用于将键key的生存时间设置为ttl秒

PEXPIRE 用于将生存时间设置为ttl毫秒

EXPIREAT 用于将过期时间设置为timestamp所指定的秒数时间戳

PEXPIREAT 用于将过期时间设置为timestamp所指定的毫秒数时间戳

实际上,EXPIRE、PEXPIRE、EXPIREAT都是通过PEXPIREAT实现

redisDb结构的expires字典保存了数据库中所有键的过期时间

移除过期时间

PERSIST命令可以移除一个键的过期时间

计算并返回过期时间

TTL命令和PTTL命令

过期键判断

检查给定键是否存在于过期字典,如果存在那么取得键的过期时间

检查当前UNIX时间戳是否大于键的过期时候:如果是的话,那么键已经过期

过期键删除策略

定时删除

在设置键的过期时间的同时,创建一个定时器,让定时器在键过期时机来临时,立即执行对键的删除操作

惰性删除

放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话就删除该键,如果没有过期就返回改建

定期删除

每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。

Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡

惰性删除策略由db.c中的expireIfNeeded函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
*
* 返回 0 表示键没有过期时间,或者键未过期。
*
* 返回 1 表示键已经因为过期而被删除了。
*/
int expireIfNeeded(redisDb *db, robj *key) {

// 取出键的过期时间
mstime_t when = getExpire(db,key);
mstime_t now;

// 没有过期时间
if (when < 0) return 0; /* No expire for this key */

/* Don't expire anything while loading. It will be done later. */
// 如果服务器正在进行载入,那么不进行任何过期检查
if (server.loading) return 0;

/* If we are in the context of a Lua script, we claim that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();

/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
// 当服务器运行在 replication 模式时
// 附属节点并不主动删除 key
// 它只返回一个逻辑上正确的返回值
// 真正的删除操作要等待主节点发来删除命令时才执行
// 从而保证数据的同步
if (server.masterhost != NULL) return now > when;

// 运行到这里,表示键带有过期时间,并且服务器为主节点

/* Return when this key has not expired */
// 如果未过期,返回 0
if (now <= when) return 0;

/* Delete the key */
server.stat_expiredkeys++;

// 向 AOF 文件和附属节点传播过期信息
propagateExpire(db,key);

// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
"expired",key,db->id);

// 将过期键从数据库中删除
return dbDelete(db,key);
}

31214

定期删除策略由redis.c中的activeExpireCycle实现,每当Redis服务器周期性操作redis.c/serverCron函数时,activeExpireCycle函数就会被调用,它在规定时间内,分多次遍历服务器中的各个数据库,从数据库的expire字典中随机检查一部分键的过期时间并删除其中过期键

AOF、RDB和复制功能对过期键处理

生成RDB文件

在执行SAVE命令或者BGSAVE命令创建一个新RDB文件,程序会对数据库中的键进行检查,已经过期键不会被保存到新创建的RDB文件中

载入RDB文件

在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入

如果服务器以主服务器模式运行,那么在载入RDB文件时,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,过期键被忽略

如果服务器以从服务器运行,所有键都会载入。

AOF文件写入

当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键产生任何影响

当过期键被惰性删除后,程序会向AOF文件追加一条DEL命令,来显示地记录该键已被删除

AOF重写

在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中

复制

主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键

从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期的键删除,而是继续像处理未过期的键一样来处理过期键

从服务器只有在接到主服务器发来的DEL命令后,才会删除过期键

数据库通知

可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变换,以及数据库中命令的执行情况\

发送数据库通知的功能是由notify.c/notifyKeyspaceEvent函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* The API provided to the rest of the Redis core is a simple function:
*
* notifyKeyspaceEvent(char *event, robj *key, int dbid);
*
* 'event' is a C string representing the event name.
*
* event 参数是一个字符串表示的事件名
*
* 'key' is a Redis object representing the key name.
*
* key 参数是一个 Redis 对象表示的键名
*
* 'dbid' is the database ID where the key lives.
*
* dbid 参数为键所在的数据库
*/
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
sds chan;
robj *chanobj, *eventobj;
int len = -1;
char buf[24];

/* If notifications for this class of events are off, return ASAP. */
// 如果服务器配置为不发送 type 类型的通知,那么直接返回
if (!(server.notify_keyspace_events & type)) return;

// 事件的名字
eventobj = createStringObject(event,strlen(event));

/* __keyspace@<db>__:<key> <event> notifications. */
// 发送键空间通知
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYSPACE) {

// 构建频道对象
chan = sdsnewlen("__keyspace@",11);
len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, key->ptr);

chanobj = createObject(REDIS_STRING, chan);

// 通过 publish 命令发送通知
pubsubPublishMessage(chanobj, eventobj);

// 释放频道对象
decrRefCount(chanobj);
}

/* __keyevente@<db>__:<event> <key> notifications. */
// 发送键事件通知
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYEVENT) {

// 构建频道对象
chan = sdsnewlen("__keyevent@",11);
// 如果在前面发送键空间通知的时候计算了 len ,那么它就不会是 -1
// 这可以避免计算两次 buf 的长度
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, eventobj->ptr);

chanobj = createObject(REDIS_STRING, chan);

// 通过 publish 命令发送通知
pubsubPublishMessage(chanobj, key);

// 释放频道对象
decrRefCount(chanobj);
}

// 释放事件对象
decrRefCount(eventobj);
}
redisServer结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
struct redisServer {

/* General */

// 配置文件的绝对路径
char *configfile; /* Absolute config file path, or NULL */

// serverCron() 每秒调用的次数
int hz; /* serverCron() calls frequency in hertz */

// 数据库
redisDb *db;

// 命令表(受到 rename 配置选项的作用)
dict *commands; /* Command table */
// 命令表(无 rename 配置选项的作用)
dict *orig_commands; /* Command table before command renaming. */

// 事件状态
aeEventLoop *el;

// 最近一次使用时钟
unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */

// 关闭服务器的标识
int shutdown_asap; /* SHUTDOWN needed ASAP */

// 在执行 serverCron() 时进行渐进式 rehash
int activerehashing; /* Incremental rehash in serverCron() */

// 是否设置了密码
char *requirepass; /* Pass for AUTH command, or NULL */

// PID 文件
char *pidfile; /* PID file path */

// 架构类型
int arch_bits; /* 32 or 64 depending on sizeof(long) */

// serverCron() 函数的运行次数计数器
int cronloops; /* Number of times the cron function run */

// 本服务器的 RUN ID
char runid[REDIS_RUN_ID_SIZE+1]; /* ID always different at every exec. */

// 服务器是否运行在 SENTINEL 模式
int sentinel_mode; /* True if this instance is a Sentinel. */


/* Networking */

// TCP 监听端口
int port; /* TCP listening port */

int tcp_backlog; /* TCP listen() backlog */

// 地址
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
// 地址数量
int bindaddr_count; /* Number of addresses in server.bindaddr[] */

// UNIX 套接字
char *unixsocket; /* UNIX socket path */
mode_t unixsocketperm; /* UNIX socket permission */

// 描述符
int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
// 描述符数量
int ipfd_count; /* Used slots in ipfd[] */

// UNIX 套接字文件描述符
int sofd; /* Unix socket file descriptor */

int cfd[REDIS_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */

// 一个链表,保存了所有客户端状态结构
list *clients; /* List of active clients */
// 链表,保存了所有待关闭的客户端
list *clients_to_close; /* Clients to close asynchronously */

// 链表,保存了所有从服务器,以及所有监视器
list *slaves, *monitors; /* List of slaves and MONITORs */

// 服务器的当前客户端,仅用于崩溃报告
redisClient *current_client; /* Current client, only used on crash report */

int clients_paused; /* True if clients are currently paused */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */

// 网络错误
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */

// MIGRATE 缓存
dict *migrate_cached_sockets;/* MIGRATE cached sockets */


/* RDB / AOF loading information */

// 这个值为真时,表示服务器正在进行载入
int loading; /* We are loading data from disk if true */

// 正在载入的数据的大小
off_t loading_total_bytes;

// 已载入数据的大小
off_t loading_loaded_bytes;

// 开始进行载入的时间
time_t loading_start_time;
off_t loading_process_events_interval_bytes;

/* Fast pointers to often looked up command */
// 常用命令的快捷连接
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
*rpopCommand;


/* Fields used only for stats */

// 服务器启动时间
time_t stat_starttime; /* Server start time */

// 已处理命令的数量
long long stat_numcommands; /* Number of processed commands */

// 服务器接到的连接请求数量
long long stat_numconnections; /* Number of connections received */

// 已过期的键数量
long long stat_expiredkeys; /* Number of expired keys */

// 因为回收内存而被释放的过期键的数量
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */

// 成功查找键的次数
long long stat_keyspace_hits; /* Number of successful lookups of keys */

// 查找键失败的次数
long long stat_keyspace_misses; /* Number of failed lookups of keys */

// 已使用内存峰值
size_t stat_peak_memory; /* Max used memory record */

// 最后一次执行 fork() 时消耗的时间
long long stat_fork_time; /* Time needed to perform latest fork() */

// 服务器因为客户端数量过多而拒绝客户端连接的次数
long long stat_rejected_conn; /* Clients rejected because of maxclients */

// 执行 full sync 的次数
long long stat_sync_full; /* Number of full resyncs with slaves. */

// PSYNC 成功执行的次数
long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */

// PSYNC 执行失败的次数
long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */


/* slowlog */

// 保存了所有慢查询日志的链表
list *slowlog; /* SLOWLOG list of commands */

// 下一条慢查询日志的 ID
long long slowlog_entry_id; /* SLOWLOG current entry ID */

// 服务器配置 slowlog-log-slower-than 选项的值
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */

// 服务器配置 slowlog-max-len 选项的值
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
size_t resident_set_size; /* RSS sampled in serverCron(). */
/* The following two are used to track instantaneous "load" in terms
* of operations per second. */
// 最后一次进行抽样的时间
long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
// 最后一次抽样时,服务器已执行命令的数量
long long ops_sec_last_sample_ops; /* numcommands in last sample */
// 抽样结果
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
// 数组索引,用于保存抽样结果,并在需要时回绕到 0
int ops_sec_idx;


/* Configuration */

// 日志可见性
int verbosity; /* Loglevel in redis.conf */

// 客户端最大空转时间
int maxidletime; /* Client timeout in seconds */

// 是否开启 SO_KEEPALIVE 选项
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_expire_enabled; /* Can be disabled for testing purposes. */
size_t client_max_querybuf_len; /* Limit for client query buffer length */
int dbnum; /* Total number of configured DBs */
int daemonize; /* True if running as a daemon */
// 客户端输出缓冲区大小限制
// 数组的元素有 REDIS_CLIENT_LIMIT_NUM_CLASSES 个
// 每个代表一类客户端:普通、从服务器、pubsub,诸如此类
clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES];


/* AOF persistence */

// AOF 状态(开启/关闭/可写)
int aof_state; /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */

// 所使用的 fsync 策略(每个写入/每秒/从不)
int aof_fsync; /* Kind of fsync() policy */
char *aof_filename; /* Name of the AOF file */
int aof_no_fsync_on_rewrite; /* Don't fsync if a rewrite is in prog. */
int aof_rewrite_perc; /* Rewrite AOF if % growth is > M and... */
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */

// 最后一次执行 BGREWRITEAOF 时, AOF 文件的大小
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */

// AOF 文件的当前字节大小
off_t aof_current_size; /* AOF current size. */
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */

// 负责进行 AOF 重写的子进程 ID
pid_t aof_child_pid; /* PID if rewriting process */

// AOF 重写缓存链表,链接着多个缓存块
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */

// AOF 缓冲区
sds aof_buf; /* AOF buffer, written before entering the event loop */

// AOF 文件的描述符
int aof_fd; /* File descriptor of currently selected AOF file */

// AOF 的当前目标数据库
int aof_selected_db; /* Currently selected DB in AOF */

// 推迟 write 操作的时间
time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */

// 最后一直执行 fsync 的时间
time_t aof_last_fsync; /* UNIX time of last fsync() */
time_t aof_rewrite_time_last; /* Time used by last AOF rewrite run. */

// AOF 重写的开始时间
time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */

// 最后一次执行 BGREWRITEAOF 的结果
int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */

// 记录 AOF 的 write 操作被推迟了多少次
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */

// 指示是否需要每写入一定量的数据,就主动执行一次 fsync()
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
/* RDB persistence */

// 自从上次 SAVE 执行以来,数据库被修改的次数
long long dirty; /* Changes to DB from the last save */

// BGSAVE 执行前的数据库被修改次数
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */

// 负责执行 BGSAVE 的子进程的 ID
// 没在执行 BGSAVE 时,设为 -1
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 的时间
time_t lastsave; /* Unix time of last successful save */

// 最后一次尝试执行 BGSAVE 的时间
time_t lastbgsave_try; /* Unix time of last attempted bgsave */

// 最近一次 BGSAVE 执行耗费的时间
time_t rdb_save_time_last; /* Time used by last RDB save run. */

// 数据库最近一次开始执行 BGSAVE 的时间
time_t rdb_save_time_start; /* Current RDB save start time. */

// 最后一次执行 SAVE 的状态
int lastbgsave_status; /* REDIS_OK or REDIS_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */


/* Propagation of commands in AOF / replication */
redisOpArray also_propagate; /* Additional command to propagate. */


/* Logging */
char *logfile; /* Path of log file */
int syslog_enabled; /* Is syslog enabled? */
char *syslog_ident; /* Syslog ident */
int syslog_facility; /* Syslog facility */


/* Replication (master) */
int slaveseldb; /* Last SELECTed DB in replication output */
// 全局复制偏移量(一个累计值)
long long master_repl_offset; /* Global replication offset */
// 主服务器发送 PING 的频率
int repl_ping_slave_period; /* Master pings the slave every N seconds */

// backlog 本身
char *repl_backlog; /* Replication backlog for partial syncs */
// backlog 的长度
long long repl_backlog_size; /* Backlog circular buffer size */
// backlog 中数据的长度
long long repl_backlog_histlen; /* Backlog actual data length */
// backlog 的当前索引
long long repl_backlog_idx; /* Backlog circular buffer current offset */
// backlog 中可以被还原的第一个字节的偏移量
long long repl_backlog_off; /* Replication offset of first byte in the
backlog buffer. */
// backlog 的过期时间
time_t repl_backlog_time_limit; /* Time without slaves after the backlog
gets released. */

// 距离上一次有从服务器的时间
time_t repl_no_slaves_since; /* We have no slaves since that time.
Only valid if server.slaves len is 0. */

// 是否开启最小数量从服务器写入功能
int repl_min_slaves_to_write; /* Min number of slaves to write. */
// 定义最小数量从服务器的最大延迟值
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
// 延迟良好的从服务器的数量
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */


/* Replication (slave) */
// 主服务器的验证密码
char *masterauth; /* AUTH with this password with master */
// 主服务器的地址
char *masterhost; /* Hostname of master */
// 主服务器的端口
int masterport; /* Port of master */
// 超时时间
int repl_timeout; /* Timeout after N seconds of master idle */
// 主服务器所对应的客户端
redisClient *master; /* Client that is master for this slave */
// 被缓存的主服务器,PSYNC 时使用
redisClient *cached_master; /* Cached master to be reused for PSYNC. */
int repl_syncio_timeout; /* Timeout for synchronous I/O calls */
// 复制的状态(服务器是从服务器时使用)
int repl_state; /* Replication status if the instance is a slave */
// RDB 文件的大小
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
// 已读 RDB 文件内容的字节数
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
// 最近一次执行 fsync 时的偏移量
// 用于 sync_file_range 函数
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
// 主服务器的套接字
int repl_transfer_s; /* Slave -> Master SYNC socket */
// 保存 RDB 文件的临时文件的描述符
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
// 保存 RDB 文件的临时文件名字
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
// 最近一次读入 RDB 内容的时间
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
int repl_serve_stale_data; /* Serve stale data when link is down? */
// 是否只读从服务器?
int repl_slave_ro; /* Slave is read only? */
// 连接断开的时长
time_t repl_down_since; /* Unix time at which link with master went down */
// 是否要在 SYNC 之后关闭 NODELAY ?
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
// 从服务器优先级
int slave_priority; /* Reported in INFO and used by Sentinel. */
// 本服务器(从服务器)当前主服务器的 RUN ID
char repl_master_runid[REDIS_RUN_ID_SIZE+1]; /* Master run id for PSYNC. */
// 初始化偏移量
long long repl_master_initial_offset; /* Master PSYNC offset. */


/* Replication script cache. */
// 复制脚本缓存
// 字典
dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */
// FIFO 队列
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
// 缓存的大小
int repl_scriptcache_size; /* Max number of elements. */

/* Synchronous replication. */
list *clients_waiting_acks; /* Clients waiting in WAIT command. */
int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */
/* Limits */
int maxclients; /* Max number of simultaneous clients */
unsigned long long maxmemory; /* Max number of memory bytes to use */
int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Pricision of random sampling */


/* Blocked clients */
unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
list *unblocked_clients; /* list of clients to unblock before next loop */
list *ready_keys; /* List of readyList structures for BLPOP & co */


/* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */
int sort_desc;
int sort_alpha;
int sort_bypattern;
int sort_store;


/* Zip structure config, see redis.conf for more information */
size_t hash_max_ziplist_entries;
size_t hash_max_ziplist_value;
size_t list_max_ziplist_entries;
size_t list_max_ziplist_value;
size_t set_max_intset_entries;
size_t zset_max_ziplist_entries;
size_t zset_max_ziplist_value;
size_t hll_sparse_max_bytes;
time_t unixtime; /* Unix time sampled every cron cycle. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */


/* Pubsub */
// 字典,键为频道,值为链表
// 链表中保存了所有订阅某个频道的客户端
// 新客户端总是被添加到链表的表尾
dict *pubsub_channels; /* Map channels to list of subscribed clients */

// 这个链表记录了客户端订阅的所有模式的名字
list *pubsub_patterns; /* A list of pubsub_patterns */

int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of REDIS_NOTIFY... flags. */


/* Cluster */

int cluster_enabled; /* Is cluster enabled? */
mstime_t cluster_node_timeout; /* Cluster node timeout. */
char *cluster_configfile; /* Cluster auto-generated config file name. */
struct clusterState *cluster; /* State of the cluster */

int cluster_migration_barrier; /* Cluster replicas migration barrier. */
/* Scripting */

// Lua 环境
lua_State *lua; /* The Lua interpreter. We use just one for all clients */

// 复制执行 Lua 脚本中的 Redis 命令的伪客户端
redisClient *lua_client; /* The "fake client" to query Redis from Lua */

// 当前正在执行 EVAL 命令的客户端,如果没有就是 NULL
redisClient *lua_caller; /* The client running EVAL right now, or NULL */

// 一个字典,值为 Lua 脚本,键为脚本的 SHA1 校验和
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
// Lua 脚本的执行时限
mstime_t lua_time_limit; /* Script timeout in milliseconds */
// 脚本开始执行的时间
mstime_t lua_time_start; /* Start time of script, milliseconds time */

// 脚本是否执行过写命令
int lua_write_dirty; /* True if a write command was called during the
execution of the current script. */

// 脚本是否执行过带有随机性质的命令
int lua_random_dirty; /* True if a random command was called during the
execution of the current script. */

// 脚本是否超时
int lua_timedout; /* True if we reached the time limit for script
execution. */

// 是否要杀死脚本
int lua_kill; /* Kill the script if true. */


/* Assert & bug reporting */

char *assert_failed;
char *assert_file;
int assert_line;
int bug_report_start; /* True if bug report header was already logged. */
int watchdog_period; /* Software watchdog period in ms. 0 = off */
};