標籤:redis 記憶體資料庫 源碼
這個檔案我在今天分析學習的時候,一直有種似懂非懂的感覺,代碼量700+的代碼,最後開放給系統的就是一個process()方法。這裡說的說的資料庫檢測,是針對key的檢測,會用到,下面提到的結構體:
/* Data type to hold opcode with optional key name an success status *//* 用於key的檢測時使用,後續檢測操作都用到了entry結構體 */typedef struct {//key的名字 char* key; //類型 int type; //是否是成功狀態 char success;} entry;
後續所涉及到的很多API都是與這個結構體相關,此代碼最終檢測的其實是一個叫dump.rdb的檔案,在檢測的後面還會加上迴圈冗餘校正CRC64。下面亮出API:
int checkType(unsigned char t) /* 每當添加一個新的obj類型時,都要檢測這個類型是否合理 */int readBytes(void *target, long num) /* 在當前檔案位移量位置往後讀取num個位元組位置 */int processHeader(void) /* 讀取快照檔案的頭部,檢測頭部名稱或版本號碼是否正確 */int loadType(entry *e) /* 為entry賦上obj的Type */int peekType() /* 彈出版本號碼 */int processTime(int type) /* 去除用來表示時間的位元組 */uint32_t loadLength(int *isencoded) /* 分type讀取長度 */char *loadIntegerObject(int enctype) /* 根據當前整型的編碼方式,擷取數值,以字元形式返回 */char* loadLzfStringObject() /* 獲得解壓後的字串 */char* loadStringObject() /* 擷取當前檔案資訊字串對象 */int processStringObject(char** store) /* 將字串對象賦給所傳入的參數 */double* loadDoubleValue() /* 檔案中讀取double類型值 */int processDoubleValue(double** store) /* 對double類型進行賦予給參數 */int loadPair(entry *e) /* 讀取索引值對 */entry loadEntry() /* 擷取entry的key結構體 */void printCentered(int indent, int width, char* body) /* 輸出介面對稱的資訊 */void printValid(uint64_t ops, uint64_t bytes) /* 輸出有效資訊 */void printSkipped(uint64_t bytes, uint64_t offset) /* 輸出Skipped跳過bytes位元組資訊 */void printErrorStack(entry *e) /* 輸出錯誤棧的資訊 */void process(void) /* process方法是執行檢測的主要方法 */
方法裡面好多loadXXX()方法,這幾個load方法的確比較有用,在這個檢測檔案中,編寫者又很人性化的構造了error的結構體,用於類比錯誤資訊棧的輸出。
/* Hold a stack of errors *//* 錯誤資訊結構體 */typedef struct {//具體的錯誤資訊字串 char error[16][1024]; //內部位移量 size_t offset[16]; //錯誤資訊等級 size_t level;} errors_t;static errors_t errors;
不同的level等級對應不同的出錯資訊。在API裡有個比較關鍵的方法,loadEntry,擷取key相關的結構體;
/* 擷取entry的key結構體 */entry loadEntry() { entry e = { NULL, -1, 0 }; uint32_t length, offset[4]; /* reset error container */ errors.level = 0; offset[0] = CURR_OFFSET; //此處賦值type if (!loadType(&e)) { return e; } offset[1] = CURR_OFFSET; if (e.type == REDIS_SELECTDB) { if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) { SHIFT_ERROR(offset[1], "Error reading database number"); return e; } if (length > 63) { SHIFT_ERROR(offset[1], "Database number out of range (%d)", length); return e; } } else if (e.type == REDIS_EOF) { if (positions[level].offset < positions[level].size) { SHIFT_ERROR(offset[0], "Unexpected EOF"); } else { e.success = 1; } return e; } else { /* optionally consume expire */ if (e.type == REDIS_EXPIRETIME || e.type == REDIS_EXPIRETIME_MS) { if (!processTime(e.type)) return e; if (!loadType(&e)) return e; } offset[1] = CURR_OFFSET; //調用loadPair為Entry賦值key if (!loadPair(&e)) { SHIFT_ERROR(offset[1], "Error for type %s", types[e.type]); return e; } } /* all entries are followed by a valid type: * e.g. a new entry, SELECTDB, EXPIRE, EOF */ offset[2] = CURR_OFFSET; if (peekType() == -1) { SHIFT_ERROR(offset[2], "Followed by invalid type"); SHIFT_ERROR(offset[0], "Error for type %s", types[e.type]); e.success = 0; } else { e.success = 1; } return e;}
其中裡面的關鍵的賦值key,value在loadPair()方法:
/* 讀取索引值對 */int loadPair(entry *e) { uint32_t offset = CURR_OFFSET; uint32_t i; /* read key first */ //首先從檔案中讀取key值 char *key; if (processStringObject(&key)) { e->key = key; } else { SHIFT_ERROR(offset, "Error reading entry key"); return 0; } uint32_t length = 0; if (e->type == REDIS_LIST || e->type == REDIS_SET || e->type == REDIS_ZSET || e->type == REDIS_HASH) { if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) { SHIFT_ERROR(offset, "Error reading %s length", types[e->type]); return 0; } }//讀取key值後面跟著的value值 switch(e->type) { case REDIS_STRING: case REDIS_HASH_ZIPMAP: case REDIS_LIST_ZIPLIST: case REDIS_SET_INTSET: case REDIS_ZSET_ZIPLIST: case REDIS_HASH_ZIPLIST: //因為類似ziplist,zipmap等結構體其實是一個個結點串連而成的超級字串,所以是直接讀取 if (!processStringObject(NULL)) { SHIFT_ERROR(offset, "Error reading entry value"); return 0; } break; case REDIS_LIST: case REDIS_SET: //而上面這2種是傳統的結構,要分結點讀取 for (i = 0; i < length; i++) { offset = CURR_OFFSET; if (!processStringObject(NULL)) { SHIFT_ERROR(offset, "Error reading element at index %d (length: %d)", i, length); return 0; } } break; case REDIS_ZSET: for (i = 0; i < length; i++) { offset = CURR_OFFSET; if (!processStringObject(NULL)) { SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length); return 0; } offset = CURR_OFFSET; if (!processDoubleValue(NULL)) { SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length); return 0; } } break; case REDIS_HASH: for (i = 0; i < length; i++) { offset = CURR_OFFSET; if (!processStringObject(NULL)) { SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length); return 0; } offset = CURR_OFFSET; if (!processStringObject(NULL)) { SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length); return 0; } } break; default: SHIFT_ERROR(offset, "Type not implemented"); return 0; } /* because we're done, we assume success */ //只要執行過了,我們就認定為成功 e->success = 1; return 1;}
如果e-success=1則說明這個key的檢測就過關了。為什麼這麼說呢,我們來看主檢測方法process()方法:
/* process方法是執行檢測的主要方法 */void process(void) { uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0; entry entry; //讀取檔案頭部擷取快照檔案版本號碼 int dump_version = processHeader(); /* Exclude the final checksum for RDB >= 5. Will be checked at the end. */ if (dump_version >= 5) { if (positions[0].size < 8) { printf("RDB version >= 5 but no room for checksum.\n"); exit(1); } positions[0].size -= 8; } level = 1; while(positions[0].offset < positions[0].size) { positions[1] = positions[0]; entry = loadEntry(); if (!entry.success) { //如果Entry不為成功狀態 printValid(num_valid_ops, num_valid_bytes); printErrorStack(&entry); num_errors++; num_valid_ops = 0; num_valid_bytes = 0; /* search for next valid entry */ uint64_t offset = positions[0].offset + 1; int i = 0; //接著尋找後面3個有效entries while (!entry.success && offset < positions[0].size) { positions[1].offset = offset; /* find 3 consecutive valid entries */ //尋找3個有效entries for (i = 0; i < 3; i++) { entry = loadEntry(); if (!entry.success) break; } /* check if we found 3 consecutive valid entries */ if (i < 3) { offset++; } } /* print how many bytes we have skipped to find a new valid opcode */ if (offset < positions[0].size) { printSkipped(offset - positions[0].offset, offset); } positions[0].offset = offset; } else { num_valid_ops++; num_valid_bytes += positions[1].offset - positions[0].offset; /* advance position */ positions[0] = positions[1]; } free(entry.key); } /* because there is another potential error, * print how many valid ops we have processed */ printValid(num_valid_ops, num_valid_bytes); /* expect an eof */ if (entry.type != REDIS_EOF) { /* last byte should be EOF, add error */ errors.level = 0; SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]); /* this is an EOF error so reset type */ entry.type = -1; printErrorStack(&entry); num_errors++; } /* Verify checksum */ //版本號碼>=5的時候,需要檢驗校正和 if (dump_version >= 5) { uint64_t crc = crc64(0,positions[0].data,positions[0].size); uint64_t crc2; unsigned char *p = (unsigned char*)positions[0].data+positions[0].size; crc2 = ((uint64_t)p[0] << 0) | ((uint64_t)p[1] << 8) | ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56); if (crc != crc2) { SHIFT_ERROR(positions[0].offset, "RDB CRC64 does not match."); } else { printf("CRC64 checksum is OK\n"); } } /* print summary on errors */ if (num_errors) { printf("\n"); printf("Total unprocessable opcodes: %llu\n", (unsigned long long) num_errors); }}
如果想瞭解檢測的詳細原理,事先瞭解dump.rdb的檔案內容結構也許會對我們又很大協助。
Redis源碼分析(十二)--- redis-check-dump本機資料庫檢測