Nginx的內存緩存是通過slab pool來實現的,但是目前Nginx代碼沒有對http響應進行內存緩存。比如作爲反向代理服務器時向後端獲取的文件也只是緩存在磁盤裏,而內存只是用來做索引。不過Nginx已經提供了內存緩存功能的函數,所以如果在其他地方有需要使用內存緩存的話,也可以通過修改代碼來實現(當然,也可以用memory disk來實現內存緩存)。在Nginx的內存緩存機制中,最重要的結構就是ngx_slab_pool_t,裏面存放了包括內存緩存的空間使用情況、位置映射以及緩存空間本身的幾乎所有信息。先來看一下ngx_slab_pool_t吧。
- typedefstruct {
- ngx_atomic_t lock; //mutex的鎖
- size_t min_size; //內存緩存obj最小的大小,一般是1個byte
- size_t min_shift; //slab pool以shift來比較和計算所需分配的obj大小、
- //每個緩存頁能夠容納obj個數以及所分配的頁在緩存空間的位置
- ngx_slab_page_t *pages; //slab page空間的開頭
- ngx_slab_page_t free; //如果要分配新的頁,就從free.next開始
- u_char *start; //實際緩存obj的空間的開頭
- u_char *end; //整個緩存空間的結尾
- ngx_shmtx_t mutex; //互斥鎖
- u_char *log_ctx;
- u_char zero;
- void *data;
- void *addr; //指向ngx_slab_pool_t的開頭
- } ngx_slab_pool_t;
- struct ngx_slab_page_s {
- uintptr_t slab; //多種情況,多個用途
- //當需要分配新的頁的時候,slab表示剩餘頁的數量
- //當分配某些大小的obj的時候(一個緩存頁存放多個obj),slab表
- //示被分配的緩存的佔用情況(是否空閒),以bit位來表示
- ngx_slab_page_t *next; //在分配較小obj的時候,next指向slab page在pool->pages的位置
- uintptr_t prev;
- };
注意,在ngx_slab_pool_t裏面有兩種類型的slab page,雖然都是ngx_slab_page_t定義的結構,但是功能不盡相同。一種是slots,用來表示存放較小obj的內存塊(如果頁大小是4096B,則是<2048B的obj,即小於1/2頁),另一種來表示所要分配的空間在緩存區的位置。Nginx把緩存obj分成大的(>=2048B)和小的(<2048B)。每次給大的obj分配一整個頁,而把多個小obj存放在一個頁中間,用bitmap等方法來表示其佔用情況。而小的obj又分爲3種:小於128B,等於128B,大於128B且小於2048B。其中小於128B的obj需要在實際緩衝區額外分配bitmap空間來表示內存使用情況(因爲slab成員只有4個byte即32bit,一個緩存頁4KB可以存放的obj超過32個,所以不能用slab來表示),這樣會造成一定的空間損失。等於或大於128B的obj因爲可以用一個32bit的整形來表示其狀態,所以就可以直接用slab成員。每次分配的空間是2^n,最小是8byte,8,16,32,64,128,256,512,1024,2048。小於2^i且大於2^(i-1)的obj會被分配一個2^i的空間,比如56byte的obj就會分配一個64byte的空間。
先看一下初始化slab pool的函數,在ngx_init_cycle()中調用的ngx_init_zone_pool()中被調用
- void ngx_slab_init(ngx_slab_pool_t *pool)
- {
- //假設每個page是4KB
- //設置ngx_slab_max_size = 2048B。如果一個頁要存放多個obj,則obj size要小於這個數值
- //設置ngx_slab_exact_size = 128B。分界是否要在緩存區分配額外空間給bitmap
- //ngx_slab_exact_shift = 7,即128的位表示
- //...
- //pool->min_shift = 3
- //最小分配的空間是8byte
- pool->min_size = 1 << pool->min_shift;
- //這些slab page是給大小爲8,16,32,64,128,256,512,1024,2048byte的內存塊
- //這些slab page的位置是在pool->pages的前面
- //初始化
- p = (u_char *) pool + sizeof(ngx_slab_pool_t);
- slots = (ngx_slab_page_t *) p;
- n = ngx_pagesize_shift - pool->min_shift;
- for (i = 0; i < n; i++) {
- slots[i].slab = 0;
- slots[i].next = &slots[i];
- slots[i].prev = 0;
- }
- //跳過上面那些slab page
- p += n * sizeof(ngx_slab_page_t);
- //**計算這個空間總共可以分配的緩存頁(4KB)的數量,每個頁的overhead是一個slab page的大小
- //**這兒的overhead還不包括之後給<128B物體分配的bitmap的損耗
- pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));
- //把每個緩存頁對應的slab page歸0
- ngx_memzero(p, pages * sizeof(ngx_slab_page_t));
- //pool->pages指向slab page的頭
- pool->pages = (ngx_slab_page_t *) p;
- //初始化free,free.next是下次分配頁時候的入口
- pool->free.prev = 0;
- pool->free.next = (ngx_slab_page_t *) p;
- //更新第一個slab page的狀態,這兒slab成員記錄了整個緩存區的頁數目
- pool->pages->slab = pages;
- pool->pages->next = &pool->free;
- pool->pages->prev = (uintptr_t) &pool->free;
- //實際緩存區(頁)的開頭,對齊
- pool->start = (u_char *)ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t), ngx_pagesize);
- //根據實際緩存區的開始和結尾再次更新內存頁的數目
- m = pages - (pool->end - pool->start) / ngx_pagesize;
- if (m > 0) {
- pages -= m;
- pool->pages->slab = pages;
- }
- //...
- }
下面來看一下需要分配緩存空間時調用的函數,由於是共享內存,所以在進程間需要用鎖來保持同步
- void * ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
- {
- //spinlock獲取鎖
- ngx_shmtx_lock(&pool->mutex);
- p = ngx_slab_alloc_locked(pool, size);
- //解鎖
- ngx_shmtx_unlock(&pool->mutex);
- return p;
- }
- //返回的值是所要分配的空間在內存緩存區的位置
- void * ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)
- {
- //這兒假設page_size是4KB
- //如果是large obj, size >= 2048B
- if(...){
- //分配1個或多個內存頁
- page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1) >> ngx_pagesize_shift);
- //返回指向內存緩存頁的位置,這兒slab page的位置與所要返回的緩存頁的位置是對應的
- p = (page - pool->pages) << ngx_pagesize_shift;
- p += (uintptr_t) pool->start;
- //done, return p
- //...
- }
- //較小的obj, size < 2048B
- //根據需要分配的size來確定在slots的位置,每個slot存放一種大小的obj的集合,如slots[0]表示8byte的空間,slots[3]表示64byte的空間
- //如果obj過小(<1B),slot的位置是1B空間的位置,即最小分配1B
- //...
- //如果之前已經有此類大小obj且那個已經分配的內存緩存頁還未滿
- if(...){
- //小obj,size < 128B,更新內存緩存頁中的bitmap,並返回待分配的空間在緩存的位置
- //...
- //size == 128B,因爲一個頁可以放32個,用slab page的slab成員來標註每塊內存的佔用情況,不需要另外在內存緩存區分配bitmap,並返回待分配的空間在緩存的位置
- //...
- //size > 128B,也是更新slab page的slab成員,但是需要預先設置slab的部分bit,因爲一個頁的obj數量小於32個,並返回待分配的空間在緩存的位置
- //...
- }
- //此前沒有此類大小的obj或者之前的頁已經滿了,分配一個新的頁,page是新的頁相應的slab page
- page = ngx_slab_alloc_pages(pool, 1);
- //小obj,size < 128B,更新內存緩存頁中的bitmap,並返回待分配的空間在緩存的位置(跳過bitmap的位置)
- //...
- //size == 128B,更新slab page的slab成員(即頁中的每個相同大小空間的佔用情況),並返回待分配的空間在緩存的位置
- //...
- //size > 128B,更新slab page的slab成員(即頁中的每個相同大小空間的佔用情況),並返回待分配的空間在緩存的位置
- //...
- }
- //返回一個slab page,這個slab page之後會被用來確定所需分配的空間在內存緩存的位置
- static ngx_slab_page_t * ngx_slab_alloc_pages(...)
- {
- //從pool->free.next開始,每次取(slab page) page = page->next
- for(;;){
- //本個slab page剩下的緩存頁數目>=需要分配的緩存頁數目N
- if(...){
- //更新從本個slab page開始往下第N個slab page的緩存頁數目爲本個slab page數目減去N
- //N爲需要分配的緩存頁數目
- //更新pool->free.next,下次從第N個slab page開始
- //...
- //更新被分配的page slab中的第一個的slab成員,即頁的個數和佔用情況
- page->slab = pages | NGX_SLAB_PAGE_START;
- //...
- //如果分配的頁數N>1,更新後面page slab的slab成員爲NGX_SLAB_PAGE_BUSY
- //...
- return page;
- }
- }
- //沒有找到空餘的頁
- return NULL;
- }
附圖
1. ngx_slab_alloc_pages圖例:
2. 小物體bitmap圖例:
3. slab page和緩存頁的映射: