redis服务器

redis服务器负责与多个客户端建立网络连接,处理客户端请求,在数据库中保存客户端执行命令所产生的数据,并通过资源管理来维持服务器的自身运转。
本文将应用侧和源码侧两个角度进行描述。

  1. 服务器的功能:redis客户端发送命令后,服务器如何处理;
  2. 服务器源码结构中相关重要参数。

redis架构详解

单机模式

主从模式

哨兵模式

集群模式

慢查询日志

redis的慢查询日志功能用于记录执行时间超过设定时长的命令请求,用户可以通过这个功能那个产生的日志来监控和优化查询。
服务器配置中包含如下两个和慢查询有关的参数:

  1. slowlog-log-slower-than: 执行时长超过多少微秒时,将命令记录到慢查询日志中;如果=0,则表示将存储所有执行的日志。
  2. slowlog-max-length: 指定服务器能保存多少条慢查询日志。

redisServer属性众多,现在来看下和慢查询相关的属性:

1
2
3
4
5
6
struct redisServer {
list *slowlog; /* SLOWLOG list of commands */
long long slowlog_entry_id; /* SLOWLOG current entry ID */
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
}

其中,
slowlog_entry_id表示当前慢查询的日志id。其初始值为0,当创建一条新的慢查询日志时,这个属性就会用作新日志的id,而后自增+1用作下一条日志的id。
slowlog则保存了所有慢查询日志的链表,每次新增一个慢查询日志时,都会将新的放在链表头部,并且移除最早加入慢查询日志的entry。
slowlog_log_slower_than即上述命令执行的标定时间,
slowlog-max-len用以限制慢查询日志链表长度。

链表中的节点为slowlogEntry,其定义如下:

1
2
3
4
5
6
7
8
9
typedef struct slowlogEntry {
robj **argv; // 节点命令的详细内容
int argc; // 参数个数
long long id; // 链表节点的唯一标识符
long long duration; // 节点命令的执行时长,单位微秒
time_t time; // 节点命令执行的unix时间
sds cname; // 节点命令的发起客户端
sds peerid; // 命令发起的客户端的网络地址
} slowlogEntry;

如下是慢查询日志链表节点的示意图:

在客户端可以使用slowlog get命令查看服务器保存的慢查询日志。

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
123.122.228.34:6379> slowlog get 
1) 1) (integer) 10
2) (integer) 1690086551
3) (integer) 66497
4) 1) "geodist"
2) "citys"
3) "beijing"
4) "shanghai"
5) "km"
5) "123.122.42.170:44244"
6) ""
2) 1) (integer) 9
2) (integer) 1690049869
3) (integer) 10520
4) 1) "INFO"
5) "123.122.42.134:54780"
6) "lettuce#ClusterTopologyRefresh"
3) 1) (integer) 8
2) (integer) 1690012734
3) (integer) 10706
4) 1) "PING"
5) "123.122.228.37:43154"
6) ""
4) 1) (integer) 7
2) (integer) 1689999931
3) (integer) 11551
4) 1) "PING"
5) "123.122.228.37:42252"
6) ""

初始化慢查询日志

服务器启动时,将会执行慢查询的初始化。

1
2
3
4
5
6
void slowlogInit(void) {
server.slowlog = listCreate();
server.slowlog_entry_id = 0;
// 释放慢查询列表中的元素,及相关内存空间
listSetFreeMethod(server.slowlog,slowlogFreeEntry);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void slowlogFreeEntry(void *septr) {
slowlogEntry *se = septr;
int j;

for (j = 0; j < se->argc; j++)
// 将slowentry中保存的所有查询参数的引用计数值-1
decrRefCount(se->argv[j]);
// 释放查询参数的指针数组空间
zfree(se->argv);
sdsfree(se->peerid);
sdsfree(se->cname);
// 释放slowentry自身的空间
zfree(se);
}

添加慢查询日志

前面讲述过慢查询日志产生的条件,下面来看一下其源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
// 判断是否开启了慢查询日志
if (server.slowlog_log_slower_than < 0) return;
// 如果 该命令耗时大于设定值,则創建一个slowlogentry,并将其加入慢查询日志链表头部
if (duration >= server.slowlog_log_slower_than)
listAddNodeHead(server.slowlog,
slowlogCreateEntry(c,argv,argc,duration));

// 判断加入后的链表长度,如果超出链表长度的最大值,则删除最新入链的元素
while (listLength(server.slowlog) > server.slowlog_max_len)
listDelNode(server.slowlog,listLast(server.slowlog));
}
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
slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {
slowlogEntry *se = zmalloc(sizeof(*se));
int j, slargc = argc;

if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;
se->argc = slargc;
se->argv = zmalloc(sizeof(robj*)*slargc);
for (j = 0; j < slargc; j++) {
// 判断命令参数是否超出额定值,因为命令参数过多对于服务器而言是无效的内存开销
if (slargc != argc && j == slargc-1) {
se->argv[j] = createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"... (%d more arguments)",
argc-slargc+1));
} else {
// 如果 命令参数中 字符串参数过长,则new一个额定长度的字符串
if (argv[j]->type == OBJ_STRING &&
sdsEncodedObject(argv[j]) &&
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
{
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);

s = sdscatprintf(s,"... (%lu more bytes)",
(unsigned long)
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
se->argv[j] = createObject(OBJ_STRING,s);
} else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
se->argv[j] = argv[j];
} else {
/* Here we need to duplicate the string objects composing the
* argument vector of the command, because those may otherwise
* end shared with string objects stored into keys. Having
* shared objects between any part of Redis, and the data
* structure holding the data, is a problem: FLUSHALL ASYNC
* may release the shared string object and create a race. */
se->argv[j] = dupStringObject(argv[j]);
}
}
}
// 设置命令的执行时间
se->time = time(NULL);
// 设置发生时间
se->duration = duration;
se->id = server.slowlog_entry_id++;
se->peerid = sdsnew(getClientPeerId(c));
se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();
return se;
}

删除慢查询日志

遍历服务器的慢查询日志,如果存在,则逐条删除。

1
2
3
void slowlogReset(void) {
while (listLength(server.slowlog) > 0)
listDelNode(server.slowlog,listLast(server.slowlog));

listDelNode方法定义在src/adlist.c中。

持久化

redis提供了两种持久化方式,对应的服务器配置如下:

1

引用

1. redis slowlog.c
2. redis架构