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

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

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

简单的回答就是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 );  

在每个连接开始的时候,创建连接唯一的内存池,存放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;  
}  

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

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;  
};  

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

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-09-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏魏艾斯博客www.vpsss.net

更换 sitemap 插件为 Google XML Sitemaps 及相关设置过程

25320
来自专栏北京马哥教育

Linux 新手必会的21条命令合集

21770
来自专栏欧阳大哥的轮子

论MVVM伪框架结构和MVC中M的实现机制

一直都有人撰文吹捧MVVM应用开发框架,文章把MVVM说的天花乱坠并且批评包括iOS和android所用的MVC经典框架。这篇文章就是想给那些捧臭脚的人们泼泼冷...

11130
来自专栏北京马哥教育

性能调优攻略

关于性能优化这是一个比较大的话题,在《由12306.cn谈谈网站性能技术》中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈...

42540
来自专栏非著名程序员

看大神如何玩转微信小程序日历插件?

23030
来自专栏腾讯Bugly的专栏

Android 内存优化总结&实践

导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存。然而大部...

57770
来自专栏蓝天

Linux下可以替换运行中的程序么?

今天被朋友问及“Linux下可以替换运行中的程序么?”,以前依稀记得Linux下是可以的(而Windows就不让),于是随口答道“OK”。结果朋友发来一个执行结...

20720
来自专栏Coding迪斯尼

使用java自造TCP/IP协议栈:使用JPCAP实现数据发包

从本节开始,我们打算使用java把tcp/ip网络协议栈重新实现一遍。这是一个不小的野心,自然也是一个不小的工程,好在前面顺利完成了操作系统,编译器两门课程的实...

34040
来自专栏工科狗和生物喵

Python爬虫小白入门(一)

开篇语 本篇文章适用人群 >有一点点语法基础,至少知道Python这个东西,如果有其他方面语言的基础那也凑合 >会一点点Linux系统的操作,最好是ubunt...

521110
来自专栏鹅厂网事

高性能网关设备及服务实践

针对海量的网络流量,转发性能是我们最关键的一个方面,那构建高性能的后台服务器有哪些关键的技术和需要注意的地方。

85980

扫码关注云+社区

领取腾讯云代金券