高并发服务器的设计--内存池的设计

Posted Golang语言社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发服务器的设计--内存池的设计相关的知识,希望对你有一定的参考价值。

不同的业务,设计也不尽相同,但至少都一些共同的追求,比如性能。

做服务器开发很多年了,有时候被人问到,服务器性能是什么呢?各种服务器间拼得是什么呢?

简单的回答就是QPS,并发数,但有时候想想也许也不对。

QPS与并发数是针对同样的业务而言的,业务不同,相同的服务器能承受的压力也会不同。

性能,也许可以打个俗点的比方:

服务器就是一艘船,性能就是船的容量,开的速度,行得是否稳当。

该用的用,该省的省。能用内存就别用IO,CPU则能少用就少用,相同的QPS,CPU和内存用的少点的性能就要比用的多点好,同样,QPS跑得多点的就比

跑得小点的性能要好,哪怕多用了点CPU和内存。

什么是性能的保障呢?

高效的事件模型,简单明了的业务架构,统一稳定的资源管理,外加纯熟的人员。

咱就从资源说起吧。

资源多半与IO有关,如果你看过我前面的文章,一定不会对连接池陌生,没错,连接是系统的一种IO资源,下面看看另一种IO资源:内存。

如果你看过apache, nginx之类服务器的代码,或者想入手,那么多半应该从内存管理开始。

与服务器性能息息相关,内存池的设计也追求快速与稳定,生命周期一般有下面三种:

global: 全局的内存,存放整个进程的全局信息。

conn: 每个连接的信息,从连接产生到关闭。

busi:业务相关的信息,伴随每个业务的产生到结束

下面定义一个简单的内存池:

[cpp]   

  1. typedef struct yumei_mem_buf_s yumei_mem_buf_t;  

  2. typedef struct yumei_mem_pool_s yumei_mem_pool_t;  

  3.   

  4. struct yumei_mem_buf_s  

  5. {  

  6.     int                          size;  

  7.     char                        *pos;  

  8.     char                        *data;  

  9.     yumei_mem_pool_t            *pool;  

  10. };  

  11.   

  12. struct yumei_mem_pool_s  

  13. {  

  14.     int                          size;  

  15.     char                        *data;  

  16.     char                        *last;  

  17.     yumei_mem_pool_t            *next;  

  18.     yumei_mem_pool_t            *current;  

  19. };  

  20.   

  21. yumei_mem_pool_t* yumei_mem_pool_create( int block_size, int block_num );  

  22. int yumei_mem_pool_free( yumei_mem_pool_t  *pool );  

  23. yumei_mem_buf_t* yumei_mem_malloc( yumei_mem_pool_t   *pool, int size );  

  24. int yumei_mem_buf_free( yumei_mem_buf_t *buf );  


在每个连接开始的时候,创建连接唯一的内存池,存放IO数据,当要创建新业务时,创建业务内存池,业务处理完毕时释放内存池:


[cpp]   

  1. typedef struct yumei_busi_s yumei_busi_t;  

  2.   

  3. struct yumei_busi_s  

  4. {  

  5.     yumei_mem_pool_t      *pool;  

  6.     ...  

  7.     ...  

  8.   

  9. }  

  10.   

  11. #define yumei_BUSI_MEM_BLOCL_SIZE 512  

  12. #define yumei_BUSI_MEM_BLOCK_NUM  32  

  13.   

  14. yumei_busi_t* yumei_busi_create()  

  15. {  

  16.     yumei_busi_t* busi;  

  17.     yumei_pool_t* pool;  

  18.     yumei_mem_buf_t* buf;  

  19.     int size;  

  20.   

  21.     pool = yumei_mem_pool_create( yumei_BUSI_MEM_BLOCL_SIZE, yumei_BUSI_MEM_BLOCK_NUM );  

  22.     if( !pool ){  

  23.         return 0;  

  24.     }  

  25.   

  26.     size = sizeof( yumei_busi_t );  

  27.     buf = yumei_mem_buf_malloc( pool, size );  

  28.   

  29.     if( !buf ){  

  30.         yumei_mem_pool_free( pool );  

  31.         return 0;  

  32.     }  

  33.   

  34.     busi = buf->data;  

  35.   

  36.     return busi;  

  37.   

  38. }  

  39.   

  40. #define YUMEI_BUSI_ERROR -1  

  41. #define YUMEI_BUSI_OK     0  

  42.   

  43. int yumei_busi_free( yumei_busi_t* busi )  

  44. {  

  45.     if( !busi ){  

  46.         return YUMEI_BUSI_ERROR;  

  47.     }  

  48.   

  49.     yumei_mem_pool_free( busi->pool );  

  50.   

  51.     return YUMEI_BUSI_OK;  

  52. }  


有些时候业务比较简单,一个连接仅对应一个业务或多个业务不是并行执行,这样的情况下,就不再需要业务内存池了,可以直接用连接内存池:


[cpp]   

  1. yumei_busi_t* yumei_busi_create( yumei_conn_t* conn )  

  2. {  

  3.     yumei_busi_t* busi;  

  4.     yumei_pool_t* pool;  

  5.     yumei_mem_buf_t* buf;  

  6.     int size;  

  7.   

  8.     pool = conn->pool;  

  9.     if( !pool ){  

  10.         retur 0;  

  11.     }  

  12.   

  13.     size = sizeof( yumei_busi_t );  

  14.     buf = yumei_mem_buf_malloc( pool, size );  

  15.   

  16.     if( !buf ){  

  17.         yumei_mem_pool_free( pool );  

  18.         return 0;  

  19.     }  

  20.   

  21.     busi = buf->data;  

  22.   

  23.     return busi;  

  24.   

  25. }  

  26.   

  27. #define YUMEI_CONN_ERROR -1  

  28. #define YUMEI_CONN_OK     0  

  29.   

  30. int yumei_conn_close( yumei_conn_t* conn )  

  31. {  

  32.     if( !conn ){  

  33.         return YUMEI_CONN_ERROR;  

  34.     }  

  35.   

  36.     yumei_mem_pool_free( conn->pool );  

  37.   

  38.     return YUMEI_CONN_OK;  

  39. }  


在一些通用的服务器上还会看到另一个元素:large。 这个是争对一些大内存的分配,当不清楚业务到底需要多大内存的时候,large往往是必须的,这样内存池结构就变成这样:


[cpp]   

  1. typedef struct yumei_mem_large_s yumei_mem_large_t;  

  2.   

  3. struct yumei_mem_large_s  

  4. {  

  5.     char                      *data;  

  6.     int                        size;  

  7.     yumei_mem_large_t         *next;  

  8. }  

  9.   

  10. struct yumei_mem_pool_s  

  11. {  

  12.     int                          size;  

  13.     char                        *data;  

  14.     char                        *last;  

  15.     yumei_mem_pool_t            *next;  

  16.     yumei_mem_pool_t            *current;  

  17.     yumei_mem_large_t           *large;  

  18. };  


对于一些特殊的业务,比如业务使用的内存大小都固定,且相近的时候,内存池就缩化成了固定大小的内存管理,其实是很简单了,这样的内存池可以绑定在连接上,且用完不用释放,留待下条连接复用,进一步节省开销。


以上是关于高并发服务器的设计--内存池的设计的主要内容,如果未能解决你的问题,请参考以下文章

C++高并发内存池的设计和实现

C++高并发内存池的设计和实现

项目设计高并发内存池

Nginx 核心架构设计,揭秘其为何能支持高并发?

项目设计高并发内存池—tcmalloc核心框架学习

C++从零实现一个高并发内存池