Redis代碼結構 一mem,bio
1. Redis代碼結構
事件庫 |
類型庫 |
網路程式庫 |
持久化 |
複製 |
訂閱 |
事務 |
main |
client |
其它 |
ae.c ae_epoll.c ae_kqueue.c ae_select.c syncio.c |
adlist.c intset.c object.c sds.c t_hash.c t_list.c t_set.c t_string.c t_zset.c ziplist.c zipmap.c |
anet.c networking.c |
aof.c rdb.c redis-check-aof.c redis-check-dump.c |
replication.c |
pubsub.c |
multi.c |
config.c redis.c dict.c db.c |
redis-cli.c |
debug.c lzf_c.c lzf_d.c pqsort.c redis-benchmark.c release.c sha1.c slowlog.c sort.c util.c vm.c zmalloc.c bio.c endian.c |
2. zmalloc.c
該檔案用於封裝記憶體 Clerk:redis提供的記憶體 Clerk有c庫的malloc、jemalloc、acmalloc。這幾個分配器的適用情境見()。提供該層封裝的主要目的是統計記憶體的使用量,因為redis本身是為全memory設計的,所以要時刻觀察記憶體的使用方式。
void *zmalloc(size_t size)用於分配記憶體,它首先判斷使用的分配器是使用有頭部資訊,即把大小儲存到內容的正上方。預設的三種分配器都是有頭部資訊的,所以PREFIX_SIZE=0,然後會進行大小的sizeof(long)位元組對齊,(ms malloc分配出來的預設應該是大小對齊的,其它的兩種方式不清楚),對於沒有對齊的則擴充分配的大小,並且累加used_memory。
update_zmalloc_stat_alloc(__n,__size)
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
used_memory += _n;
void *zcalloc(size_t size),其實跟zmalloc差不多,而不像c的calloc,因為它只有一個輸入參數。
void *zrealloc(void *ptr, size_t size)與c的realloc一樣,只是多了更新used_memory的大小。Zfree類似。
char *zstrdup(const char *s)與strdup類似,進行字串拷貝,該函數分配記憶體,要求調用它的函數在結束時釋放相應的記憶體,不然就會導致記憶體流失。
size_t zmalloc_used_memory(void)返回已使用的記憶體量。
void zmalloc_enable_thread_safeness(void)用於設計該分配器是否可支援多線程,redis的預設是不支援的。
size_t zmalloc_get_rss(void),該函數用於獲得redis-server的RSS即當前駐留在實體記憶體的頁數。該函數通過讀取/proc/pid/stat來擷取該資訊。上面的used_memory指是當前使用的虛擬記憶體量與RSS不一樣。
3. bio.c
該檔案用於提供後台線程的相關函數。Redis從 2.#引進了兩個後台線程用於主線程的aof檔案的close以及fsync操作。因為這兩個操作會導致主線程阻塞。詳細的應用情境見前面寫的“Redis Aof”的文章。這裡我們直接介紹該檔案裡的函數。
該檔案包含4個靜態全域變數以及一個bio_job的結構。
static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS]; //為訪問靜態全域變數bio_jobs,bio_pending提供互斥保障。static pthread_cond_t bio_condvar[REDIS_BIO_NUM_OPS];static list *bio_jobs[REDIS_BIO_NUM_OPS]; //儲存job的兩個adliststatic unsigned long long bio_pending[REDIS_BIO_NUM_OPS]; //當前等待處理的job數目struct bio_job { //job的結構體,現在用arg1來儲存fd time_t time; /* Time at which the job was created. */ void *arg1, *arg2, *arg3;};
void bioInit(void),該函數被initServer調用,完成上面的全域變數的初始化,以及建立close job處理線程以及fsync處理線程(bioProcessBackgroundJobs)。
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3),該函數用於建立bio_job並將該job加入到相應的bio_jobs[type] list裡。當前有三個地方會調用該函數:其一,建立REDIS_BIO_CLOSE_FILE job。當後台rewrite aof子進程結束時,主進程wait收到SIGCHLD訊號時,調用backgroundRewriteDoneHandler來進行相應的訊號處理:此時就需要close(oldaof),然後rename(tempfilename,oldaofname),如果直接按照這個過程操作則會導致阻塞,所以這邊採用的方法是如果oldaof是已經被關閉的則再開啟(非關閉則不需要),然後再rename此時oldaof因為有一個open的引用,所以不會unlink也就不會阻塞,最後再由剛才我們說的後台REDIS_BIO_CLOSE_FILE線程進行close,該線程會被阻塞,但這不會影響主線程;其二,建立REDIS_BIO_AOF_FSYNC
job,同樣是在backgroundRewriteDoneHandler裡,當把tempfile的fd重設為server.appendfd的fd時,if (server.appendfsync == APPENDFSYNC_EVERYSEC)則aof_background_fsync(newfd);該函數就會建立一個REDIS_BIO_AOF_FSYNC job。其三,flushAppendOnlyFile函數,我們在redis aof裡介紹過,在每次event loop之前的beforesleep函數裡都會調用該函數,顯然該函數是用於重新整理aof檔案(註:這裡有個write操作會導致主線程阻塞?為什麼不添加到file
event?),此時如果已經有REDIS_BIO_AOF_FSYNC job等待處理bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0,則不需要再aof_background_fsync增加fsync job(因為這裡只有這麼一個aof fd),否則如果是server.appendfsync == APPENDFSYNC_EVERYSEC則aof_background_fsync增加一個REDIS_BIO_AOF_FSYNC
job由後台fsync線程來進行sync操作。
上面就是目前的版本的使用到後台close及fsync線程的地方。
void *bioProcessBackgroundJobs(void *arg),該函數是兩個線程的處理函數,它們通過參數arg來辨別為不同的線程。該函數就是通過類型取得相應的bio_jobs list的job,然後調用close(job->arg1)或者aof_fsync(job->arg1)來完成相應的操作。
unsigned long long bioPendingJobsOfType(int type),返回當前等待type線程處理的jobs數目。對於fsync jobs顯然該pending數最多為1,上面我們已經講到了,因為它只對當前的aof fd有效,而任務時刻該檔案都是唯一的;對於close jobs的pending數可能會有多個,因為close 的是old aof file,顯然這個檔案每次都是不一樣的。
下面我們通過一張圖來描述一下當前的幾個進程及線程的關係。
圖1 redis中的進程與線程
註:關於bgsave,bgrewrite子進程的運行情況見前面文章的介紹。