目錄
一、高并發下傳統方式的弊端
1、常用的內存操作函數
2、弊端一
3、弊端二
4、弊端三
5、弊端四
二、弊端解決之道
1、內存管理維度分析
2、內存管理組件選型
三、高并發內存管理最佳實踐
1、內存池技術
2、內存池如何解決弊端
3、高并發內存池如何實現
四、高效內存池設計與實現
1、內存池的實現思路
2、Nginx內存池結構圖
3、關鍵數據結構
4、ngx_pool_t結構示意圖(大希奧未1024的池)
?5、Nginx內存池基本操作
一、高并發下傳統方式的弊端
1、常用的內存操作函數
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
malloc? 在內存的動態存儲區中分配一塊長度為size字節的連續區域返回該區域的首地址.
calloc? 與malloc相似,參數size為申請地址的單位元素長度,nmemb為元素個數,即在內存中申請nmemb*size字節大小的連續地址空間.內存會初始化0
realloc? 給一個已經分配了地址的指針重新分配空間,參數ptr為原有的空間地址,newsize是重新申請的地址長度.ptr 若為NULL,它就等同于 malloc.
2、弊端一
高并發時較小內存塊使用導致系統調用頻繁,降低了系統的執行效率。(系統調用不了解的,觀看我博客里系統調用的一文)。
3、弊端二
頻繁使用時增加了系統內存的碎片,降低內存使用效率
內部碎片 - 已經被分配出去(能明確指出屬于哪個進程)卻不能被利用的內存空間;
產生根源:1.內存分配必須起始于可被 4、8 或 16 整除(視處理器體系結構而定)的地址
????????????????? 2.MMU的分頁機制的限制
處理器 | 頁大小 | 分頁的級別 | 虛擬地址分級 |
x86 | 4KB | 2 | 10+10+12 |
x86(extended) | 4KB | 1 | 10+22 |
x86(PAE) | 4KB | 3 | 2+9+9+12 |
x86-64 | 4KB | 4 | 9+9+9+9+12 |
4、弊端三
沒有垃圾回收機制,容易造成內存泄漏,導致內存枯竭
情形一:
void log_error(char *reason)
{ char *p1; p1 = malloc(100); sprintf(p1,"The f1 error occurred because of '%s'.", reason); log(p1);
}情形二:
int getkey(char *filename)
{ FILE *fp; int key; fp = fopen(filename, "r");fscanf(fp, "%d", &key); //fclose(fp);return key;
}
5、弊端四
內存分配與釋放的邏輯在程序中相隔較遠時,降低程序的穩定性
例如:程序1調用程序2,誤認為傳參過來的是指針,用完后,進行了釋放
//程序1
ret get_stu_info(Student * _stu)
{ char * name= NULL; name = getName(_stu->no);//處理邏輯if(name) {free(name);name = NULL;}
}
//程序2
char stu_name[MAX];char * getName(int stu_no)
{//查找相應的學號并賦值給 stu_namesnprintf(stu_name,MAX,“%s”,name);return stu_name;
}
二、弊端解決之道
1、內存管理維度分析
2、內存管理組件選型
PtMalloc (glibc 自帶) | TcMalloc | JeMalloc | |
概念 | Glibc 自帶 | Google 開源 | Jason Evans (FreeBSD著名開發人員) |
性能 (一次malloc/free 操作) | 300ns | 50ns | <=50ns |
弊端 | 鎖機制降低性能,容易導致內存碎片 | 1%左右的額外內存開銷 | 2%左右的額外內存開銷 |
優點 | 傳統,穩定 | 線程本地緩存,多線程分配效率高 | 線程本地緩存,多核多線程分配效率相當高 |
使用方式 | Glibc 編譯 | 動態鏈接庫 | 動態鏈接庫 |
誰在用 | 較普遍 | safari、chrome等 | facebook、firefox 等 |
適用場景 | 除特別追求高效內存分配以外的 | 多線程下高效內存分配 | 多線程下高效內存分配 |
三、高并發內存管理最佳實踐
1、內存池技術
什么是內存池技術?
在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存,統一對程序所使用的內存進行統一的分配和回收。這樣做的一個顯著優點是,使得內存分配效率得到很大的提升。
2、內存池如何解決弊端
(1)、高并發時系統調用頻繁,降低了系統的執行效率
????????內存池提前預先分配大塊內存,統一釋放,極大的減少了malloc 和 free 等函數的調用。
(2)、頻繁使用時增加了系統內存的碎片,降低內存使用效率
????????內存池每次請求分配大小適度的內存塊,避免了碎片的產生
(3)、沒有垃圾回收機制,容易造成內存泄漏
????????在生命周期結束后統一釋放內存,完全避免了內存泄露的產生
(4)、內存分配與釋放的邏輯在程序中相隔較遠時,降低程序的穩定性
????????在生命周期結束后統一釋放內存,避免重復釋放指針或釋放空指針等情況
3、高并發內存池如何實現
高并發(High Concurrency)是互聯網分布式系統架構設計中必須考慮的因素之一,它通常是指,通過設計保證系統能夠同時并行處理很多請求。
高并發特點
(1)、響應時間短
(2)、吞吐量大
(4)、每秒響應請求數 QPS
(5)、并發用戶數高:
內存池設計考慮
設計邏輯應該盡量簡單,避免不同請求之間互相影響,盡量降低不同模塊之間的耦合
內存池生存時間應該盡可能短,與請求或者連接具有相同的周期,減少碎片堆積和內存泄漏
四、高效內存池設計與實現
1、內存池的實現思路
(1)、對于每個請求或者連接都會建立相應的內存池,建立好內存池之后,我們可以直接從內存池中申請所需要的內存,不用去管內存的釋放,當內存池使用完成之后一次性銷毀內存池。
(2)、區分大小內存塊的申請和釋放,大于池尺寸的定義為大內存塊,使用單獨的大內存塊鏈表保存,即時分配和釋放;小于等于池尺寸的定義為小內存塊,直接從預先分配的內存塊中提取,不夠就擴充池中的內存,在生命周期內對小塊內存不做釋放,直到最后統一銷毀。
2、Nginx內存池結構圖
3、關鍵數據結構
typedef struct
{u_char *last; // 保存當前數據塊中內存分配指針的當前位置u_char *end; // 保存內存塊的結束位置ngx_pool_t *next; // 內存池由多塊內存塊組成,指向下一個數據塊的位置ngx_uint_t failed; // 當前數據塊內存不足引起分配失敗的次數
} ngx_pool_data_t;struct ngx_pool_s
{ngx_pool_data_t d; // 內存池當前的數據區指針的結構體size_t max; // 當前數據塊最大可分配的內存大小(Bytes)ngx_pool_t *current; // 當前正在使用的數據塊的指針ngx_pool_large_t *large; // pool 中指向大數據塊的指針(大數據快是指 size > max 的數據塊)
};
4、ngx_pool_t結構示意圖(大希奧未1024的池)

5、Nginx內存池基本操作
內存池創建、銷毀和重置:
操作 | 函數 |
創建內存池 | ngx_pool_t *? ngx_create_pool(size_t size); |
銷毀內存池 | void ngx_destroy_pool(ngx_pool_t *pool); |
重置內存池 | void ngx_reset_pool(ngx_pool_t *pool); |
內存池申請、釋放和回收操作:
操作 | 函數 |
內存申請(對齊) | void *? ngx_palloc(ngx_pool_t *pool, size_t size); |
內存申請(不對齊) | void *? ngx_pnalloc(ngx_pool_t *pool, size_t size); |
內存申請(對齊并初始化) | void *? ngx_pcalloc(ngx_pool_t *pool, size_t size); |
內存清除 | ngx_int_t? ngx_pfree(ngx_pool_t *pool, void *p); |
內存池申請、釋放和回收操作:
操作 | 函數 |
內存申請(對齊) | void *? ngx_palloc(ngx_pool_t *pool, size_t size); |
內存申請(不對齊) | void *? ngx_pnalloc(ngx_pool_t *pool, size_t size); |
內存申請(對齊并初始化) | void *? ngx_pcalloc(ngx_pool_t *pool, size_t size); |
內存清除 | ngx_int_t? ngx_pfree(ngx_pool_t *pool, void *p); |
源碼代碼詳細見上傳資源