由于Redis的多线程IO代码较多,故分成了三篇博客讲解
Redis多线程IO源码分析-第一篇_YZF_Kevin的博客-CSDN博客
Redis多线程IO源码分析-第二篇_YZF_Kevin的博客-CSDN博客
Redis多线程IO源码分析-第三篇_YZF_Kevin的博客-CSDN博客
上一篇Redis多线程IO源码分析-第一篇_YZF_Kevin的博客-CSDN博客我们分析了redis的多线程IO是如何启动创建的,也分析了IO线程函数内部的处理逻辑,这篇博客我们开始分析redis是如何决定client的延迟读写
延迟读写操作
Redis在处理客户端读事件和写事件时会根据一定条件推迟客户端的读取操作或者往客户端写数据操作,它将待处理的读客户端和待处理的写客户端分别加入到全局变量server的clients_pending_read和clients_pending_write列表中,全局变量server对应的结构体为redisServer,定义在server.h中:
struct redisServer {
list *clients_pending_write; /* list类型,记录延迟写回数据的客户端 */
list *clients_pending_read; /* list类型,记录延迟读取数据的客户端*/
// 省略...
}
判断何时推迟客户端读操作
readQueryFromClient()函数
readQueryFromClient主要处理从客户端读取数据,在networking.c中实现,里面调用了postponeClientRead函数判断是否需要推迟客户端的读取操作 :
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, readlen;
size_t qblen;
/* 判断是否需要推迟客户端的读取操作 */
if (postponeClientRead(c)) return;
// 省略...
// 处理数据执行命令
processInputBuffer(c);
}
postponeClientRead() 定义如下
int postponeClientRead(client *c) {
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ|CLIENT_BLOCKED)))
{
c->flags |= CLIENT_PENDING_READ;
// 将客户端加入到clients_pending_read链表中
listAddNodeHead(server.clients_pending_read,c);
return 1;
} else {
return 0;
}
}
postponeClientRead函数用于判断是否延迟从客户端读取数据,包含四个条件:
- server.io_threads_active的值为1,表示激活了IO多线程
- server.io_threads_do_reads的值为1,表示IO多线程可以延迟执行客户端的读取操作,该变量在配置文件中定义,可以通过修改配置文件来开启延迟读取客户端数据
- ProcessingEventsWhileBlocked值为0,processEventsWhileBlokced函数在执行时会将ProcessingEventsWhileBlocked的值置为1,执行完毕后置为0。这是因为Redis在读取RDB或者AOF文件时会调用processEventsWhileBlokced函数,为了避免读取RDB或AOF文件时阻塞无法及时处理请求,所以processEventsWhileBlokced函数在执行时不能推迟客户端数据读取。
- 客户端的现有标识不能有CLIENT_MASTER、CLIENT_SLAVE、CLIENT_PENDING_READ、CLIENT_BLOCKED等标记
- CLIENT_MASTER、CLIENT_SLAVE表示是用于主从复制的客户端
- CLIENT_PENDING_READ表示客户端本身已经是推迟读取状态
- CLIENT_BLOCKED表示客户端是阻塞状态
满足以上四个条件时将推迟从客户端读取数据,会将该客户端标识置为CLIENT_PENDING_READ 即延迟读状态,并将待读取数据的客户端client加入到server.clients_pending_read列表中。
推迟客户端写操作
在往客户端写数据的addReply(networking.c),定义如下
void addReply(client *c, robj *obj) {
// 调用prepareClientToWrite往客户端写数据
if (prepareClientToWrite(c) != C_OK) return;
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
_addReplyProtoToList(c,obj->ptr,sdslen(obj->ptr));
} else if (obj->encoding == OBJ_ENCODING_INT) {
char buf[32];
size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
if (_addReplyToBuffer(c,buf,len) != C_OK)
_addReplyProtoToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}
可以看到函数中调用了prepareClientToWrite判断是否准备往客户端写数据:
prepareClientToWrite() 定义如下
int prepareClientToWrite(client *c) {
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
if (c->flags & CLIENT_CLOSE_ASAP) return C_ERR;
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
if (!c->conn) return C_ERR;
/*
* 如果缓冲区的数据都已写回到客户端并且客户端标识不是推迟读状态
*/
if (!clientHasPendingReplies(c) && !(c->flags & CLIENT_PENDING_READ))
clientInstallWriteHandler(c);// 调用clientInstallWriteHandler
return C_OK;
}
解释下,函数首先对客户端标识状态进行了一系列的判断,然后调用了clientHasPendingReplies函数判断输出缓冲区是否有还有数据等待写回到客户端,如果没有,判断客户端的标识是否是CLIENT_PENDING_READ已延迟读,如果不是CLIENT_PENDING_READ状态,调用clientInstallWriteHandler处理:
clientInstallWriteHandler 定义如下
void clientInstallWriteHandler(client *c) {
/* 如果客户端的标识不是推迟写状态,并且客户端未在进行主从复制或者客户端是主从复制的从节点并能接收请求 */
if (!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
(c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
{
/* 将客户端的标识置为延迟写 */
c->flags |= CLIENT_PENDING_WRITE;
// 将客户端加入到待写回的列表clients_pending_write中
listAddNodeHead(server.clients_pending_write,c);
}
}
函数对是否推迟客户端写操作进行了判断:
- 客户端标识不是CLIENT_PENDING_WRITE,对应条件为!(c->flags & CLIENT_PENDING_WRITE),表示客户端本身不是推迟写状态
- 客户端未在进行主从复制(对应条件为c->replstate == REPL_STATE_NONE) 或者 客户端是主从复制的从节点,但全量复制的 RDB 文件已经传输完成,客户端可以接收请求(对应条件 !c->repl_put_online_on_ack))
满足以上两个条件时将推迟客户端写操作,将客户端的标识置为延迟写CLIENT_PENDING_WRITE状态,并将客户端加入到server.clients_pending_write列表中。