redis服务器是典型的一对多服务器程序,一个服务器可以和多个客户端建立网络请求,每个客户端可以向服务器发送命令请求,服务器接收并处理客户端发送的命令请求,并向客户端返回命令回复。
通过使用由IO多路复用技术实现的文件事件处理器,redis服务器使用单线程单进程的方式处理命令请求,并与多个客户端进行网络通信。6.0之后,IO多路复用处使用多线程实现。
与服务器建立连接的客户端将以链表的形式被保存在redisServer中:

1
2
3
struct redisServer {
list *clients; // 链表保存各客户端状态
}

客户端属性

客户端状态包含的属性可以分为两类:

  1. 比较通用的属性,无论客户端执行什么工作,都需要使用这些属性
  2. 和特定功能相关的属性,比如操作数据库时需要用到的db属性和dictid属性,执行时需要用到mstate属性等。

id

1
2
3
typedef struct client {
uint64_t id; // 自增客户端唯一id
}

flags

1
2
3
typedef struct client {
uint64_t flags; // 客户端标志位
}

flags占8个字节,该属性记录了客户端角色以及当前所处的状态,标志值比较多,详见:server.h
主从服务器进行复制时,主服务器会成为从服务器的客户端;从服务器也会成为主服务器的客户端

  1. CLIENT_SLAVE (1<<0) 0000 0001,客户端代表的是一个从服务器。
  2. CLIENT_MASTER (1<<1) 0000 0010, 客户端代表的是一个主服务器。
  3. CLIENT_MONITOR (1<<2) 0000 0100,客户端正在执行monitor命令
  4. CLIENT_MULTI (1<<3) 0000 1000, 客户端正在执行事务。
  5. CLIENT_BLOCKED (1<<4) 0001 0000,客户端正在被BRPOP/BLPOP等命令阻塞。
  6. CLIENT_SCRIPT (1<<8),标识这个客户端是专门用于执行lua脚本的客户端,并没有真正的网络连接。
  7. CLIENT_UNIX_SOCKET (1<<11) ,标识该客户端通过unix域套接字进行连接。
  8. CLIENT_FORCE_AOF (1<<14), 标识该客户端,正在执行aof操作。
  9. CLIENT_READONLY (1<<17), 标识客户端当前处于只读状态。
  10. CLIENT_PUBSUB (1<<18),标识客户端正处于发布订阅模式下。

连接信息

1
2
3
4
5
typedef struct client {
connection *conn; // 保存此客户端的连接信息
user *user; // 与当前连接绑定的用户
int authenticated; // 默认用户是否需要身份认证
}

authenticated=0表示无需认证,authenticated=1则需要认证。

RESP 协议版本

1
2
3
typedef struct client {
int resp;
}

即RESP协议版本,基于TCP的应用层协议 RESP(REdis Serialization Protocol)。

数据库

1
2
3
typedef struct client {
redisDb *db;
}

客户端当前指向的数据库指针。

name

1
2
3
typedef struct client {
robj *name;
}

此客户端的名称,默认为空。可以通过client list命令查看:

1
2
182.168.168.226:6379> client list
id=764370 addr=182.168.106.129:34520 laddr=182.168.168.226:6379 fd=27 name= age=932860 idle=932860 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 obl=0 oll=0 omem=0 tot-mem=20504 events=r cmd=hgetall user=default redir=-1

输入缓冲区

1
2
3
4
5
typedef struct client {
sds querybuf; // 输入缓冲区
size_t qb_pos; // 用以标识输入缓冲区中已读位置
size_t querybuf_peak; // 最近100ms+输入缓冲区的峰值
}

客户端输入缓冲区用于保存客户端发送的命令请求。输入缓冲区大小会根据输入内容动态的缩小或者扩大,但最多不能超过1G,否则服务器将关闭此客户端。

输出缓冲区

1
2
3
4
typedef struct client {
time_t obuf_soft_limit_reached_time;
}

缓冲区大小由一个链表和任意多个字符串对象组成,但是为了避免客户端回复过大,占用过多的服务器资源,服务器会检查客户端的输出缓冲区大小,并在缓冲区大小超限时,执行相应的操作。服务器使用两种模式来限制客户端输出缓冲区的大小:

  1. 硬性限制: 如果输出缓冲区的大小超过了硬性限制所设置的大小,那么服务器立即关闭客户端;
  2. 软性限制: 如果输出缓冲区大小超过软性限制所设置的大小,但是没超过硬性限制,则服务器使用客户端结构体中obuf_soft_limit_reached_time属性记录客户端到达软性限制的起始时间,而后继续监视客户端,如果输出缓冲区大小一直超出软性限制,且持续时长超过服务器中设定的时长,则服务器关闭客户端。
    使用client-output-buffer-limit可以为普通客户端、从服务器客户端、执行发布订阅功能的客户端分别设置不同的软性限制和硬性限制:
    1
    2
    3
    4
    client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
    client-output-buffer-limit normal 0 0 0 // 将普通客户端的软性限制和硬性限制都设置为0,即不限制客户端输出缓冲区大小
    client-output-buffer-limit replica 256mb 64mb 60 // 将从服务器客户端的硬性限制为256m,软性限制为64m,软性限制时长为60s
    client-output-buffer-limit pubsub 32mb 8mb 60 // 将执行发布订阅功能客户端的硬性限制为32m, 软性限制为8m,软性限制时长为60s

命令与命令参数

1
2
3
4
5
6
7
8
9
10
typedef struct client {
int argc; // 当前命令的参数个数
robj **argv; // 当前命令 的参数
int argv_len; // argv数组的长度,可能会大于argc
int original_argc; // 在命令被重写时,记录命令原始参数个数
robj **original_argv; // 命令重写前,原始的参数
size_t argv_len_sum; // 命令参数长度之和
struct redisCommand *cmd, *lastcmd; // 上次执行的命令
struct redisCommand *realcmd;
}

如上,lastcmd使用redisCommand记录了上次执行的命令,redisCommand的定义如下:

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
struct redisCommand {
// 声明类数据
const char *declared_name; // 标识命令名,之所以定义为指针是为了兼容原生命令和sds命令
const char *summary; // 命令概要,可选
const char *complexity; // 复杂度描述, 可选
const char *since; // 命令的首次发布版本,可选
int doc_flags; /* Flags for documentation (see CMD_DOC_*). */
const char *replaced_by; // 如果命令被废弃,则填写其继任者
const char *deprecated_since; // 命令何时被废弃
redisCommandGroup group; // 命令组
commandHistory *history; // 命令历史
int num_history;
const char **tips; /* An array of strings that are meant to be tips for clients/proxies regarding this command */
int num_tips;
redisCommandProc *proc; // 命令实现
int arity; // 参数个数
uint64_t flags; /* Command flags, see CMD_*. */
uint64_t acl_categories; // ACL分类
keySpec *key_specs;
int key_specs_num;
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect (may be NULL) */
redisGetKeysProc *getkeys_proc; // 使用函数确定命令行中的键参数
int num_args; // 命令参数数组长度
struct redisCommand *subcommands; // 子命令数组
struct redisCommandArg *args; // 命令参数数组
#ifdef LOG_REQ_RES
/* Reply schema */
struct jsonObject *reply_schema;
#endif

// 运行时填充的数据
long long microseconds, calls, rejected_calls, failed_calls;
int id; // 命令id,从0开始,在运行时分配,用于acl检查;如果当前连接的用户具有此命令位,则此连接能执行给定的命令。[位图]
sds fullname; // sds描述的命令全名
struct hdr_histogram* latency_histogram; /*points to the command latency command histogram (unit of time nanosecond) */
keySpec legacy_range_key_spec; /* The legacy (first,last,step) key spec is
* still maintained (if applicable) so that
* we can still support the reply format of
* COMMAND INFO and COMMAND GETKEYS */
dict *subcommands_dict; // 保留子命令的字典,键是子命令的sds名称(非全名),值为redisCommand的指针。
struct redisCommand *parent;
struct RedisModuleCommand *module_cmd; /* A pointer to the module command data (NULL if native command) */
};

其中redisCommandTable的实现使用python实现的,详见redisCommand初始化
另外这个链接可以方便查看redis提供的命令的复杂度: commands

时间参数

1
2
3
4
5
typedef struct client {
time_t ctime; // 客户端创建时间
long duration; // 当前命令的执行时间
time_t lastinteraction; // 上次交互时间
}

client结构体详情

redis客户端的结构体定义在redis.h/client

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
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
uint64_t flags; /* Client flags: CLIENT_* macros. */
connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
robj *lib_name; /* The client library name as set by CLIENT SETINFO. */
robj *lib_ver; /* The client library version as set by CLIENT SETINFO. */
sds querybuf; /* Buffer we use to accumulate client queries. */
size_t qb_pos; /* The position we have read in querybuf. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */
int argv_len; /* Size of argv array (may be more than argc) */
int original_argc; /* Num of arguments of original command if arguments were rewritten. */
robj **original_argv; /* Arguments of original command if arguments were rewritten. */
size_t argv_len_sum; /* Sum of lengths of objects in argv list. */
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
struct redisCommand *realcmd; /* The original command that was executed by the client,
Used to update error stats in case the c->cmd was modified
during the command invocation (like on GEOADD for example). */
user *user; /* User associated with this connection. If the
user is set to NULL the connection can do
anything (admin). */
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. */
list *reply; /* List of reply objects to send to the client. */
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
list *deferred_reply_errors; /* Used for module thread safe contexts. */
size_t sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
time_t ctime; /* Client creation time. */
long duration; /* Current command duration. Used for measuring latency of blocking/non-blocking cmds */
int slot; /* The slot the client is executing against. Set to -1 if no slot is being used */
dictEntry *cur_script; /* Cached pointer to the dictEntry of the script being executed. */
time_t lastinteraction; /* Time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time;
int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a slave. */
int repl_start_cmd_stream_on_ack; /* Install slave write handler on first 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_applied; /* Applied replication data count in querybuf, if this is a replica. */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_aof_off; /* Replication AOF fsync ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */
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: REPLCONF listening-port */
char *slave_addr; /* Optionally given by REPLCONF ip-address */
int slave_capa; /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
int slave_req; /* Slave requirements: SLAVE_REQ_* */
multiState mstate; /* MULTI/EXEC state */
blockingState bstate; /* 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) */
dict *pubsub_patterns; /* patterns a client is interested in (PSUBSCRIBE) */
dict *pubsubshard_channels; /* shard level channels a client is interested in (SSUBSCRIBE) */
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
listNode *client_list_node; /* list node in client list */
listNode *postponed_list_node; /* list node within the postponed list */
listNode *pending_read_list_node; /* list node in clients pending read list */
void *module_blocked_client; /* Pointer to the RedisModuleBlockedClient associated with this
* client. This is set in case of module authentication before the
* unblocked client is reprocessed to handle reply callbacks. */
void *module_auth_ctx; /* Ongoing / attempted module based auth callback's ctx.
* This is only tracked within the context of the command attempting
* authentication. If not NULL, it means module auth is in progress. */
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
* when the authenticated user
* changes. */
void *auth_callback_privdata; /* Private data that is passed when the auth
* changed callback is executed. Opaque for
* Redis Core. */
void *auth_module; /* The module that owns the callback, which is used
* to disconnect the client if the module is
* unloaded for cleanup. Opaque for Redis Core.*/

/* If this client is in tracking mode and this field is non zero,
* invalidation messages for keys fetched by this client will be sent to
* the specified client ID. */
uint64_t client_tracking_redirection;
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
subscribed to in BCAST mode, in the
context of client side caching. */
/* In updateClientMemoryUsage() we track the memory usage of
* each client and add it to the sum of all the clients of a given type,
* however we need to remember what was the old contribution of each
* client, and in which category the client was, in order to remove it
* before adding it the new value. */
size_t last_memory_usage;
int last_memory_type;

listNode *mem_usage_bucket_node;
clientMemUsageBucket *mem_usage_bucket;

listNode *ref_repl_buf_node; /* Referenced node of replication buffer blocks,
* see the definition of replBufBlock. */
size_t ref_block_pos; /* Access position of referenced buffer block,
* i.e. the next offset to send. */

/* list node in clients_pending_write list */
listNode clients_pending_write_node;
/* Response buffer */
size_t buf_peak; /* Peak used size of buffer in last 5 sec interval. */
mstime_t buf_peak_last_reset_time; /* keeps the last time the buffer peak value was reset */
int bufpos;
size_t buf_usable_size; /* Usable size of buffer. */
char *buf;
#ifdef LOG_REQ_RES
clientReqResInfo reqres;
#endif
} client;

引用

1. server.h
2. Redis 源码剖析 3 – redisCommand
3. redisCommand初始化
4. commands