redis客戶端管理
寫在前面
這一章節涉及的內容比較簡單,主要是為了講清楚redis server端如何維持和client相關的連線的,基本從以下幾個方面展開描述:
- redis server端採用何種資料結構維護連線資訊
- redis server端建立和client相關連線的過程
- redis client相關的資料結構定義
redis server核心資料結構
redis server作為redis最核心的資料結構,基本上redis所有相關的資訊都會在這裡有體現,這裡列出幾大塊。
- 服務本身執行資訊
- 網路監聽連線等
- AOF/RDB持久化資訊
- redis慢查詢日誌
- 主從同步資訊
- 叢集cluster資訊
redis客戶端管理相關的資料結構主要有
- list *clients; // 一個連結串列,儲存了所有客戶端狀態結構
- list *clients_to_close; // 連結串列,儲存了所有待關閉的客戶端
- list *slaves, *monitors; // 連結串列,儲存了所有從伺服器,以及所有監視器
redis server端使用列表來管理客戶端發起的連線,以list儲存所有客戶端發起的連線,以及待關閉的客戶端連線。
struct redisServer {
// serverCron() 每秒呼叫的次數
int hz; /* serverCron() calls frequency in hertz */
// 資料庫
redisDb *db;
// 事件狀態
aeEventLoop *el;
/* redis 網路相關的資料結構 */
// 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[] */
// 一個連結串列,儲存了所有客戶端狀態結構
list *clients; /* List of active clients */
// 連結串列,儲存了所有待關閉的客戶端
list *clients_to_close; /* Clients to close asynchronously */
// 連結串列,儲存了所有從伺服器,以及所有監視器
list *slaves, *monitors; /* List of slaves and MONITORs */
/* RDB / AOF 載入資訊相關資料結構 */
// 這個值為真時,表示伺服器正在進行載入
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;
/* server端相關的統計資訊 */
// 伺服器啟動時間
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 */
/* 慢查詢日誌 */
// 儲存了所有慢查詢日誌的連結串列
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;
size_t resident_set_size;
// 最後一次進行抽樣的時間
long long ops_sec_last_sample_time;
// 最後一次抽樣時,伺服器已執行命令的數量
long long ops_sec_last_sample_ops;
// 抽樣結果
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
// 陣列索引,用於儲存抽樣結果,並在需要時迴繞到 0
int ops_sec_idx;
/* AOF 持久化資訊*/
// 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. */
/* RDB 持久化資訊 */
// 自從上次 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? */
/* 日誌相關資訊 */
char *logfile; /* Path of log file */
int syslog_enabled; /* Is syslog enabled? */
char *syslog_ident; /* Syslog ident */
int syslog_facility; /* Syslog facility */
/* 主從複製 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 */
/* 主從賦值 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 */
/* 釋出訂閱資訊 */
// 字典,鍵為頻道,值為連結串列
// 連結串列中儲存了所有訂閱某個頻道的客戶端
// 新客戶端總是被新增到連結串列的表尾
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. */
/* 叢集資訊 */
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. */
/* 指令碼資訊 */
// 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 */
};
redis 客戶端建立過程
當客戶端第一次向server發起連線的時候,也就是在監聽socket的accept的處理函式當中acceptCommonHandler的內部通過createClient建立對應client連線的socket。
/*
* TCP 連線 accept 處理器
*/
#define MAX_ACCEPTS_PER_CALL 1000
static void acceptCommonHandler(int fd, int flags) {
// 建立客戶端
redisClient *c;
if ((c = createClient(fd)) == NULL) {
redisLog(REDIS_WARNING,
"Error registering fd event for the new client: %s (fd=%d)",
strerror(errno),fd);
close(fd); /* May be already closed, just ignore errors */
return;
}
// 如果新新增的客戶端令伺服器的最大客戶端數量達到了
// 那麼向新客戶端寫入錯誤資訊,並關閉新客戶端
// 先建立客戶端,再進行數量檢查是為了方便地進行錯誤資訊寫入
if (listLength(server.clients) > server.maxclients) {
char *err = "-ERR max number of clients reached
";
/* That`s a best effort error message, don`t check write errors */
if (write(c->fd,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */
}
// 更新拒絕連線數
server.stat_rejected_conn++;
freeClient(c);
return;
}
// 更新連線次數
server.stat_numconnections++;
// 設定 FLAG
c->flags |= flags;
}
createClient內部進行按照以下步驟建立了client的socket並新增list列表當中。
- 分配redisClient的記憶體大小
- 根據fd執行aeCreateFileEvent將socket新增到對應的eventloop當中實現事件監聽
- 初始化一堆client相關的屬性
- 執行listAddNodeTail方法將client新增到客戶端相關的列表當中
/*
* 建立一個新客戶端
*/
redisClient *createClient(int fd) {
// 分配空間
redisClient *c = zmalloc(sizeof(redisClient));
// 當 fd 不為 -1 時,建立帶網路連線的客戶端
// 如果 fd 為 -1 ,那麼建立無網路連線的偽客戶端
// 因為 Redis 的命令必須在客戶端的上下文中使用,所以在執行 Lua 環境中的命令時
// 需要用到這種偽終端
if (fd != -1) {
// 非阻塞
anetNonBlock(NULL,fd);
// 禁用 Nagle 演算法
anetEnableTcpNoDelay(NULL,fd);
// 設定 keep alive
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive);
// 繫結讀事件到事件 loop (開始接收命令請求)
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
}
// 初始化各個屬性
// 預設資料庫
selectDb(c,0);
// 套接字
c->fd = fd;
// 名字
c->name = NULL;
// 回覆緩衝區的偏移量
c->bufpos = 0;
// 查詢緩衝區
c->querybuf = sdsempty();
// 查詢緩衝區峰值
c->querybuf_peak = 0;
// 命令請求的型別
c->reqtype = 0;
// 命令引數數量
c->argc = 0;
// 命令引數
c->argv = NULL;
// 當前執行的命令和最近一次執行的命令
c->cmd = c->lastcmd = NULL;
// 查詢緩衝區中未讀入的命令內容數量
c->multibulklen = 0;
// 讀入的引數的長度
c->bulklen = -1;
// 已傳送位元組數
c->sentlen = 0;
// 狀態 FLAG
c->flags = 0;
// 建立時間和最後一次互動時間
c->ctime = c->lastinteraction = server.unixtime;
// 認證狀態
c->authenticated = 0;
// 複製狀態
c->replstate = REDIS_REPL_NONE;
// 複製偏移量
c->reploff = 0;
// 通過 ACK 命令接收到的偏移量
c->repl_ack_off = 0;
// 通過 AKC 命令接收到偏移量的時間
c->repl_ack_time = 0;
// 客戶端為從伺服器時使用,記錄了從伺服器所使用的埠號
c->slave_listening_port = 0;
// 回覆連結串列
c->reply = listCreate();
// 回覆連結串列的位元組量
c->reply_bytes = 0;
// 回覆緩衝區大小達到軟限制的時間
c->obuf_soft_limit_reached_time = 0;
// 回覆連結串列的釋放和複製函式
listSetFreeMethod(c->reply,decrRefCountVoid);
listSetDupMethod(c->reply,dupClientReplyValue);
// 阻塞型別
c->btype = REDIS_BLOCKED_NONE;
// 阻塞超時
c->bpop.timeout = 0;
// 造成客戶端阻塞的列表鍵
c->bpop.keys = dictCreate(&setDictType,NULL);
// 在解除阻塞時將元素推入到 target 指定的鍵中
// BRPOPLPUSH 命令時使用
c->bpop.target = NULL;
c->bpop.numreplicas = 0;
c->bpop.reploffset = 0;
c->woff = 0;
// 進行事務時監視的鍵
c->watched_keys = listCreate();
// 訂閱的頻道和模式
c->pubsub_channels = dictCreate(&setDictType,NULL);
c->pubsub_patterns = listCreate();
c->peerid = NULL;
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
// 如果不是偽客戶端,那麼新增到伺服器的客戶端連結串列中
if (fd != -1) listAddNodeTail(server.clients,c);
// 初始化客戶端的事務狀態
initClientMultiState(c);
// 返回客戶端
return c;
}
redisClient對應的資料結構定義,很多變數我們看著都很熟悉。
- 跟請求相關的變數包括:(redisDb *db)、(int argc)、( robj **argv)
- 跟訂閱相關的變數包括:(dict *pubsub_channels)、(list *pubsub_patterns)。
/*
*
* 因為 I/O 複用的緣故,需要為每個客戶端維持一個狀態。
*
* 多個客戶端狀態被伺服器用連結串列連線起來。
*/
typedef struct redisClient {
// 套接字描述符
int fd;
// 當前正在使用的資料庫
redisDb *db;
// 當前正在使用的資料庫的 id (號碼)
int dictid;
// 客戶端的名字
robj *name; /* As set by CLIENT SETNAME */
// 查詢緩衝區
sds querybuf;
// 查詢緩衝區長度峰值
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
// 引數數量
int argc;
// 引數物件陣列
robj **argv;
// 記錄被客戶端執行的命令
struct redisCommand *cmd, *lastcmd;
// 請求的型別:內聯命令還是多條命令
int reqtype;
// 剩餘未讀取的命令內容數量
int multibulklen; /* number of multi bulk arguments left to read */
// 命令內容的長度
long bulklen; /* length of bulk argument in multi bulk request */
// 回覆連結串列
list *reply;
// 回覆連結串列中物件的總大小
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
// 已傳送位元組,處理 short write 用
int sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
// 建立客戶端的時間
time_t ctime; /* Client creation time */
// 客戶端最後一次和伺服器互動的時間
time_t lastinteraction; /* time of the last interaction, used for timeout */
// 客戶端的輸出緩衝區超過軟性限制的時間
time_t obuf_soft_limit_reached_time;
// 客戶端狀態標誌
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
// 當 server.requirepass 不為 NULL 時
// 代表認證的狀態
// 0 代表未認證, 1 代表已認證
int authenticated; /* when requirepass is non-NULL */
// 複製狀態
int replstate; /* replication state if this is a slave */
// 用於儲存主伺服器傳來的 RDB 檔案的檔案描述符
int repldbfd; /* replication DB file descriptor */
// 讀取主伺服器傳來的 RDB 檔案的偏移量
off_t repldboff; /* replication DB file offset */
// 主伺服器傳來的 RDB 檔案的大小
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
// 主伺服器的複製偏移量
long long reploff; /* replication offset if this is our master */
// 從伺服器最後一次傳送 REPLCONF ACK 時的偏移量
long long repl_ack_off; /* replication ack offset, if this is a slave */
// 從伺服器最後一次傳送 REPLCONF ACK 的時間
long long repl_ack_time;/* replication ack time, if this is a slave */
// 主伺服器的 master run ID
// 儲存在客戶端,用於執行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
// 從伺服器的監聽埠號
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
// 事務狀態
multiState mstate; /* MULTI/EXEC state */
// 阻塞型別
int btype; /* Type of blocking op if REDIS_BLOCKED. */
// 阻塞狀態
blockingState bpop; /* blocking state */
// 最後被寫入的全域性複製偏移量
long long woff; /* Last write global replication offset. */
// 被監視的鍵
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
// 這個字典記錄了客戶端所有訂閱的頻道
// 鍵為頻道名字,值為 NULL
// 也即是,一個頻道的集合
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
// 連結串列,包含多個 pubsubPattern 結構
// 記錄了所有訂閱頻道的客戶端的資訊
// 新 pubsubPattern 結構總是被新增到表尾
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
/* Response buffer */
// 回覆偏移量
int bufpos;
// 回覆緩衝區
char buf[REDIS_REPLY_CHUNK_BYTES];
}
相關文章
- Redis客戶端管理神器RedisInsight 推薦Redis客戶端
- [Redis 客戶端整合] Java 中常用Redis客戶端比較Redis客戶端Java
- Redis-客戶端Redis客戶端
- redis:常用客戶端命令(redis-cli)Redis客戶端
- Redis客戶端管理軟體:Redis Desktop Client mac中文免費版Redis客戶端clientMac
- 初探 Redis 客戶端 Lettuce:真香!Redis客戶端
- [Redis 客戶端整合] SpringBoot 整合 LettuceRedis客戶端Spring Boot
- [Redis 客戶端整合] SpringBoot 整合 JedisRedis客戶端Spring Boot
- day03-Redis的客戶端Redis客戶端
- mysql、redis 客戶端連線池MySqlRedis客戶端
- Redis客戶端連線數DevOpsRedis客戶端dev
- Redis客戶端選型再分析Redis客戶端
- Windows下安裝redis客戶端WindowsRedis客戶端
- Redis學習筆記(十) 客戶端Redis筆記客戶端
- Redis的Pub/Sub客戶端實現Redis客戶端
- Redis 客戶端 Jedis、lettuce 和 Redisson 對比Redis客戶端
- [轉載] 使用Redis的Java客戶端JedisRedisJava客戶端
- Go 實現簡易的 Redis 客戶端GoRedis客戶端
- Golang 實現 Redis(6): 實現 pipeline 模式的 redis 客戶端GolangRedis模式客戶端
- 深入剖析Redis客戶端Jedis的特性和原理Redis客戶端
- Redis原始碼剖析——客戶端和伺服器Redis原始碼客戶端伺服器
- Redis 6.0 客戶端快取的伺服器端實現Redis客戶端快取伺服器
- flask框架圖書管理系統客戶端Flask框架客戶端
- Kubernetes客戶端和管理介面大集合客戶端
- Redis 非同步客戶端選型及落地實踐Redis非同步客戶端
- 測試平臺系列(80) 封裝Redis客戶端封裝Redis客戶端
- Redis客戶端基本操作以及檢視慢查詢Redis客戶端
- redis客戶端實現高可用讀寫分離Redis客戶端
- 走近原始碼:Redis命令執行過程(客戶端)原始碼Redis客戶端
- mqtt 客戶端MQQT客戶端
- dubbo客戶端客戶端
- Pulsar客戶端客戶端
- 服務端,客戶端服務端客戶端
- 客戶端,服務端客戶端服務端
- Nacos - 客戶端心跳續約及客戶端總結客戶端
- SQLPro Studio Mac資料庫管理客戶端工具SQLMac資料庫客戶端
- 4款.NET開源的Redis客戶端驅動庫Redis客戶端
- Redis的Java客戶端比較:絕地武士與生菜RedisJava客戶端