網站首頁 編程語言 正文
本文基于社區版Redis 4.0.8
1、命令解析
Redis服務器接收到的命令請求首先存儲在客戶端對象的querybuf輸入緩沖區,然后解析命令請求的各個參數,并存儲在客戶端對象的argv和argc字段。
客戶端解析命令請求的入口函數為readQueryFromClient,會讀取socket數據存儲到客戶端對象的輸入緩沖區,并調用函數processInputBuffer解析命令請求。
注:內聯命令:使用telnet會話輸入命令的方式
void processInputBuffer(client *c) { ...... //循環遍歷輸入緩沖區,獲取命令參數,調用processMultibulkBuffer解析命令參數和長度 while(sdslen(c->querybuf)) { if (c->reqtype == PROTO_REQ_INLINE) { if (processInlineBuffer(c) != C_OK) break;//處理telnet方式的內聯命令 } else if (c->reqtype == PROTO_REQ_MULTIBULK) { if (processMultibulkBuffer(c) != C_OK) break; //解析命令參數和長度暫存到客戶端結構體中 } else { serverPanic("Unknown request type"); } } } //解析命令參數和長度暫存到客戶端結構體中 int processMultibulkBuffer(client *c) { //定位到行尾 newline = strchr(c->querybuf,'\r'); //解析命令請求參數數目,并存儲在客戶端對象的c->multibulklen字段 serverAssertWithInfo(c,NULL,c->querybuf[0] == '*'); ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll); c->multibulklen = ll; pos = (newline-c->querybuf)+2;//記錄已解析命令的請求長度resp的長度 /* Setup argv array on client structure */ //分配請求參數存儲空間 c->argv = zmalloc(sizeof(robj*)*c->multibulklen); // 開始循環解析每個請求參數 while(c->multibulklen) { ...... newline = strchr(c->querybuf+pos,'\r'); if (c->querybuf[pos] != '$') { return C_ERR; ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll); pos += newline-(c->querybuf+pos)+2; c->bulklen = ll;//字符串參數長度暫存在客戶端對象的bulklen字段 //讀取該長度的參數內容,并創建字符串對象,同時更新待解析參數multibulklen c->argv[c->argc++] =createStringObject(c->querybuf+pos,c->bulklen); pos += c->bulklen+2; c->multibulklen--; }
2、命令調用
當multibulklen的值更新為0時,表示參數解析完成,開始調用processCommand來處理命令,處理命令前有很多校驗邏輯,如下:
void processInputBuffer(client *c) { ...... //調用processCommand來處理命令 if (processCommand(c) == C_OK) { ...... } } //處理命令函數 int processCommand(client *c) { //校驗是否是quit命令 if (!strcasecmp(c->argv[0]->ptr,"quit")) { addReply(c,shared.ok); c->flags |= CLIENT_CLOSE_AFTER_REPLY; return C_ERR; } //調用lookupCommand,查看該命令是否存在 c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); if (!c->cmd) { flagTransaction(c); addReplyErrorFormat(c,"unknown command '%s'", (char*)c->argv[0]->ptr); return C_OK; //檢查用戶權限 if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) { addReply(c,shared.noautherr); //還有很多檢查,不一一列舉,比如集群/持久化/復制等 /* 真正執行命令 */ if (c->flags & CLIENT_MULTI && c->cmd->proc != execCommand && c->cmd->proc != discardCommand && c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) queueMultiCommand(c); //將結果寫入outbuffer addReply(c,shared.queued); } // 調用execCommand執行命令 void execCommand(client *c) { call(c,CMD_CALL_FULL);//調用call執行命令 //調用execCommand調用call執行命令 void call(client *c, int flags) { start = ustime(); c->cmd->proc(c);//執行命令 duration = ustime()-start; //如果是慢查詢,記錄慢查詢 if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) { char *latency_event = (c->cmd->flags & CMD_FAST) ? "fast-command" : "command"; latencyAddSampleIfNeeded(latency_event,duration/1000); //記錄到慢日志中 slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration); //更新統計信息:當前命令執行時間和調用次數 if (flags & CMD_CALL_STATS) { c->lastcmd->microseconds += duration; c->lastcmd->calls++;
3、返回結果
Redis返回結果并不是直接返回給客戶端,而是先寫入到輸出緩沖區(buf字段)或者輸出鏈表(reply字段)
int processCommand(client *c) { ...... //將結果寫入outbuffer addReply(c,shared.queued); ...... } //將結果寫入outbuffer void addReply(client *c, robj *obj) { //調用listAddNodeHead將客戶端添加到服務端結構體的client_pending_write鏈表,以便后續能快速查找出哪些客戶端有數據需要發送 if (prepareClientToWrite(c) != C_OK) return; //然后添加字符串到輸出緩沖區 if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK) //如果添加失敗,則添加到輸出鏈表中 _addReplyObjectToList(c,obj); }
addReply函數只是將待發送給客戶端的數據暫存在輸出鏈表或者輸出緩沖區,那么什么時候將這些數據發送給客戶端呢?答案是開啟事件循環時,調用的beforesleep函數,該函數專門執行一些不是很費時的操作,如過期鍵刪除,向客戶端返回命令回復等
void beforeSleep(struct aeEventLoop *eventLoop) { ...... /* Handle writes with pending output buffers. */ handleClientsWithPendingWrites(); } //回復客戶端命令函數 int handleClientsWithPendingWrites(void) { listIter li; listNode *ln; int processed = listLength(server.clients_pending_write); listRewind(server.clients_pending_write,&li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); c->flags &= ~CLIENT_PENDING_WRITE; listDelNode(server.clients_pending_write,ln); /* 發送客戶端數據 */ if (writeToClient(c->fd,c,0) == C_ERR) continue; /* If there is nothing left, do nothing. Otherwise install * the write handler. */ //如果數據量很大,一次性沒有發送完成,則進行添加文件事件,監聽當前客戶端socket文件描述符的可寫事件即可 if (clientHasPendingReplies(c) && aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c) == AE_ERR) { freeClientAsync(c); } } return processed;
到這里,命令請求才算真正處理完成了。
原文鏈接:https://www.cnblogs.com/mysql-dba/p/15878581.html
相關推薦
- 2022-07-24 docker容器使用GPU方法實現_docker
- 2021-12-01 Android?NDK開發(C語言--聯合體與枚舉)_Android
- 2022-04-25 turtle的基礎使用之python?turtle遞歸繪圖_python
- 2022-06-14 Pycharm安裝第三方庫的超詳細步驟_python
- 2022-10-22 C#?設置Chart的X軸為時間軸???????詳情_C#教程
- 2022-11-08 Go讀取文件與寫入文件的三種方法操作指南_Golang
- 2023-02-07 C#實現加密bat文件的示例詳解_C#教程
- 2022-09-06 React封裝CustomSelect組件思路詳解_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支