不同的业务,设计也不尽相同,但至少都一些共同的追求,比如性能。
做服务器开发很多年了,有时候被人问到,服务器性能是什么呢?各种服务器间拼得是什么呢?
简单的回答就是QPS,并发数,但有时候想想也许也不对。
QPS与并发数是针对同样的业务而言的,业务不同,相同的服务器能承受的压力也会不同。
性能,也许可以打个俗点的比方:
服务器就是一艘船,性能就是船的容量,开的速度,行得是否稳当。
该用的用,该省的省。能用内存就别用IO,CPU则能少用就少用,相同的QPS,CPU和内存用的少点的性能就要比用的多点好,同样,QPS跑得多点的就比
跑得小点的性能要好,哪怕多用了点CPU和内存。
什么是性能的保障呢?
高效的事件模型,简单明了的业务架构,统一稳定的资源管理,外加纯熟的人员。
咱就从资源说起吧。
资源多半与IO有关,如果你看过我前面的文章,一定不会对连接池陌生,没错,连接是系统的一种IO资源,下面看看另一种IO资源:内存。
如果你看过apache, nginx之类服务器的代码,或者想入手,那么多半应该从内存管理开始。
与服务器性能息息相关,内存池的设计也追求快速与稳定,生命周期一般有下面三种:
global: 全局的内存,存放整个进程的全局信息。
conn: 每个连接的信息,从连接产生到关闭。
busi:业务相关的信息,伴随每个业务的产生到结束
下面定义一个简单的内存池:
- typedef struct yumei_mem_buf_s yumei_mem_buf_t;
- typedef struct yumei_mem_pool_s yumei_mem_pool_t;
- struct yumei_mem_buf_s
- {
- int size;
- char *pos;
- char *data;
- yumei_mem_pool_t *pool;
- };
- struct yumei_mem_pool_s
- {
- int size;
- char *data;
- char *last;
- yumei_mem_pool_t *next;
- yumei_mem_pool_t *current;
- };
- yumei_mem_pool_t* yumei_mem_pool_create( int block_size, int block_num );
- int yumei_mem_pool_free( yumei_mem_pool_t *pool );
- yumei_mem_buf_t* yumei_mem_malloc( yumei_mem_pool_t *pool, int size );
- int yumei_mem_buf_free( yumei_mem_buf_t *buf );
typedef struct yumei_mem_buf_s yumei_mem_buf_t;
typedef struct yumei_mem_pool_s yumei_mem_pool_t;
struct yumei_mem_buf_s
{
int size;
char *pos;
char *data;
yumei_mem_pool_t *pool;
};
struct yumei_mem_pool_s
{
int size;
char *data;
char *last;
yumei_mem_pool_t *next;
yumei_mem_pool_t *current;
};
yumei_mem_pool_t* yumei_mem_pool_create( int block_size, int block_num );
int yumei_mem_pool_free( yumei_mem_pool_t *pool );
yumei_mem_buf_t* yumei_mem_malloc( yumei_mem_pool_t *pool, int size );
int yumei_mem_buf_free( yumei_mem_buf_t *buf );
在每个连接开始的时候,创建连接唯一的内存池,存放IO数据,当要创建新业务时,创建业务内存池,业务处理完毕时释放内存池:
- typedef struct yumei_busi_s yumei_busi_t;
- struct yumei_busi_s
- {
- yumei_mem_pool_t *pool;
- ...
- ...
- }
- #define yumei_BUSI_MEM_BLOCL_SIZE 512
- #define yumei_BUSI_MEM_BLOCK_NUM 32
- yumei_busi_t* yumei_busi_create()
- {
- yumei_busi_t* busi;
- yumei_pool_t* pool;
- yumei_mem_buf_t* buf;
- int size;
- pool = yumei_mem_pool_create( yumei_BUSI_MEM_BLOCL_SIZE, yumei_BUSI_MEM_BLOCK_NUM );
- if( !pool ){
- return 0;
- }
- size = sizeof( yumei_busi_t );
- buf = yumei_mem_buf_malloc( pool, size );
- if( !buf ){
- yumei_mem_pool_free( pool );
- return 0;
- }
- busi = buf->data;
- return busi;
- }
- #define YUMEI_BUSI_ERROR -1
- #define YUMEI_BUSI_OK 0
- int yumei_busi_free( yumei_busi_t* busi )
- {
- if( !busi ){
- return YUMEI_BUSI_ERROR;
- }
- yumei_mem_pool_free( busi->pool );
- return YUMEI_BUSI_OK;
- }
typedef struct yumei_busi_s yumei_busi_t;
struct yumei_busi_s
{
yumei_mem_pool_t *pool;
...
...
}
#define yumei_BUSI_MEM_BLOCL_SIZE 512
#define yumei_BUSI_MEM_BLOCK_NUM 32
yumei_busi_t* yumei_busi_create()
{
yumei_busi_t* busi;
yumei_pool_t* pool;
yumei_mem_buf_t* buf;
int size;
pool = yumei_mem_pool_create( yumei_BUSI_MEM_BLOCL_SIZE, yumei_BUSI_MEM_BLOCK_NUM );
if( !pool ){
return 0;
}
size = sizeof( yumei_busi_t );
buf = yumei_mem_buf_malloc( pool, size );
if( !buf ){
yumei_mem_pool_free( pool );
return 0;
}
busi = buf->data;
return busi;
}
#define YUMEI_BUSI_ERROR -1
#define YUMEI_BUSI_OK 0
int yumei_busi_free( yumei_busi_t* busi )
{
if( !busi ){
return YUMEI_BUSI_ERROR;
}
yumei_mem_pool_free( busi->pool );
return YUMEI_BUSI_OK;
}
有些时候业务比较简单,一个连接仅对应一个业务或多个业务不是并行执行,这样的情况下,就不再需要业务内存池了,可以直接用连接内存池:
- yumei_busi_t* yumei_busi_create( yumei_conn_t* conn )
- {
- yumei_busi_t* busi;
- yumei_pool_t* pool;
- yumei_mem_buf_t* buf;
- int size;
- pool = conn->pool;
- if( !pool ){
- retur 0;
- }
- size = sizeof( yumei_busi_t );
- buf = yumei_mem_buf_malloc( pool, size );
- if( !buf ){
- yumei_mem_pool_free( pool );
- return 0;
- }
- busi = buf->data;
- return busi;
- }
- #define YUMEI_CONN_ERROR -1
- #define YUMEI_CONN_OK 0
- int yumei_conn_close( yumei_conn_t* conn )
- {
- if( !conn ){
- return YUMEI_CONN_ERROR;
- }
- yumei_mem_pool_free( conn->pool );
- return YUMEI_CONN_OK;
- }
yumei_busi_t* yumei_busi_create( yumei_conn_t* conn )
{
yumei_busi_t* busi;
yumei_pool_t* pool;
yumei_mem_buf_t* buf;
int size;
pool = conn->pool;
if( !pool ){
retur 0;
}
size = sizeof( yumei_busi_t );
buf = yumei_mem_buf_malloc( pool, size );
if( !buf ){
yumei_mem_pool_free( pool );
return 0;
}
busi = buf->data;
return busi;
}
#define YUMEI_CONN_ERROR -1
#define YUMEI_CONN_OK 0
int yumei_conn_close( yumei_conn_t* conn )
{
if( !conn ){
return YUMEI_CONN_ERROR;
}
yumei_mem_pool_free( conn->pool );
return YUMEI_CONN_OK;
}
知道内存池怎么用了,再来看看内部设计吧,pool 的四个元素里 size 对应 block_size, data和last 分别对应块的起始地址和可分配地址,next和current分别对应下块内存池和当前可用内存池。
在一些通用的服务器上还会看到另一个元素:large。 这个是争对一些大内存的分配,当不清楚业务到底需要多大内存的时候,large往往是必须的,这样内存池结构就变成这样:
- typedef struct yumei_mem_large_s yumei_mem_large_t;
- struct yumei_mem_large_s
- {
- char *data;
- int size;
- yumei_mem_large_t *next;
- }
- struct yumei_mem_pool_s
- {
- int size;
- char *data;
- char *last;
- yumei_mem_pool_t *next;
- yumei_mem_pool_t *current;
- yumei_mem_large_t *large;
- };
typedef struct yumei_mem_large_s yumei_mem_large_t;
struct yumei_mem_large_s
{
char *data;
int size;
yumei_mem_large_t *next;
}
struct yumei_mem_pool_s
{
int size;
char *data;
char *last;
yumei_mem_pool_t *next;
yumei_mem_pool_t *current;
yumei_mem_large_t *large;
};
对于一些特殊的业务,比如业务使用的内存大小都固定,且相近的时候,内存池就缩化成了固定大小的内存管理,其实是很简单了,这样的内存池可以绑定在连接上,且用完不用释放,留待下条连接复用,进一步节省开销。