redis 對象
redis 對數據的處理用對象進行管理,目前有5種類型。每種對象類型並不是用單一的編碼類型實現,根據應用場景,往往多種編碼類型結合使用。
對象
數據結構
typedef struct redisObject {
// 對象類型
unsigned type:4;
// 對象編碼類型
unsigned encoding:4;
// 對象操作時間
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
// 使用計數
int refcount;
// 數據內容
void *ptr;
} robj;
類型
/* The actual Redis Object */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
編碼
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
創建對象
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution), or
* alternatively the LFU counter. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
return o;
}
工作流程
對象實現可以通過調試,看看實現邏輯。剛看完整數集合,可以跟蹤下實現流程。
命令
sadd keytest 1 2 3
堆棧
下斷點看看命令工作流程
intsetNew() (/Users/xxx/src/other/redis/src/intset.c:100)
createIntsetObject() (/Users/xxx/src/other/redis/src/object.c:236)
setTypeCreate(sds value) (/Users/xxx/src/other/redis/src/t_set.c:44)
saddCommand(client * c) (/Users/xxx/src/other/redis/src/t_set.c:270)
call(client * c, int flags) (/Users/xxx/src/other/redis/src/server.c:3195)
processCommand(client * c) (/Users/xxx/src/other/redis/src/server.c:3552)
processCommandAndResetClient(client * c) (/Users/xxx/src/other/redis/src/networking.c:1651)
processInputBuffer(client * c) (/Users/xxx/src/other/redis/src/networking.c:1746)
processInputBufferAndReplicate(client * c) (/Users/xxx/src/other/redis/src/networking.c:1768)
readQueryFromClient(connection * conn) (/Users/xxx/src/other/redis/src/networking.c:1854)
callHandler(connection * conn, ConnectionCallbackFunc handler) (/Users/xxx/src/other/redis/src/connhelpers.h:76)
connSocketEventHandler(struct aeEventLoop * el, int fd, void * clientData, int mask) (/Users/xxx/src/other/redis/src/connection.c:275)
aeProcessEvents(aeEventLoop * eventLoop, int flags) (/Users/xxx/src/other/redis/src/ae.c:457)
aeMain(aeEventLoop * eventLoop) (/Users/xxx/src/other/redis/src/ae.c:515)
main(int argc, char ** argv) (/Users/xxx/src/other/redis/src/server.c:5054)
命令結構
struct redisCommand {
char *name;
redisCommandProc *proc;
int arity;
char *sflags; /* Flags as string representation, one char per flag. */
uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
redisGetKeysProc *getkeys_proc;
/* What keys should be loaded in background when calling this command? */
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */
long long microseconds, calls;
int id; /* Command ID. This is a progressive ID starting from 0 that
is assigned at runtime, and is used in order to check
ACLs. A connection is able to execute a given command if
the user associated to the connection has this command
bit set in the bitmap of allowed commands. */
};
- 命令綁定對應處理方法。
struct redisCommand redisCommandTable[] = {
...
{"sadd",saddCommand,-3,
"write use-memory fast @set",
0,NULL,1,1,1,0,0,0},
...
}
- 根據命令字符串,查找對應的
redisCommand
int processCommand(client *c) {
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
}
- 命令處理函數
void saddCommand(client *c) {
robj *set;
int j, added = 0;
// 查找 key 是否已經存在
set = lookupKeyWrite(c->db,c->argv[1]);
if (set == NULL) {
// 根據命令數值去確定實現編碼。
set = setTypeCreate(c->argv[2]->ptr);
dbAdd(c->db,c->argv[1],set);
} else {
if (set->type != OBJ_SET) {
addReply(c,shared.wrongtypeerr);
return;
}
}
for (j = 2; j < c->argc; j++) {
if (setTypeAdd(set,c->argv[j]->ptr)) added++;
}
if (added) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
server.dirty += added;
addReplyLongLong(c,added);
}
- 根據存儲的數據,底層決定用那種編碼進行保存。
/* Factory method to return a set that *can* hold "value". When the object has
* an integer-encodable value, an intset will be returned. Otherwise a regular
* hash table. */
robj *setTypeCreate(sds value) {
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject();
return createSetObject();
}
robj *createIntsetObject(void) {
intset *is = intsetNew();
robj *o = createObject(OBJ_SET,is);
o->encoding = OBJ_ENCODING_INTSET;
return o;
}
robj *createSetObject(void) {
dict *d = dictCreate(&setDictType,NULL);
robj *o = createObject(OBJ_SET,d);
o->encoding = OBJ_ENCODING_HT;
return o;
}
參考
《redis 設計與實現》
後記
很多時候,我們理解面向對象,會單純認爲 C++
,java
等語言。所謂面向對象,是對事物邏輯進行抽象,無關語言,例如 c
語言實現的 redis
對象,就很好闡述了這個問題。
[redis 源碼走讀] 系列,根據 《redis 設計與實現》書籍的目錄路線,結合相關內容進行源碼閱讀。計劃事無鉅細走讀源碼,發現時間不允許,很多細節在實際應用中可以不斷查閱鞏固,而且不少書籍博客也有很詳盡的解說。redis 在使用過程中,自己還有不少疑惑,帶着問題,在查閱過資料後,做一些總結,便於遺忘查閱。