高并发高性能下提高SERVER性能的一种网络处理模式

 

给大家分享下我目前在项目中(需要开发一个monitor进程,用于监控大量逻辑SERVER,每个dms服务需要与后端做交互,性能较低)实现的异步网络IO架构,希望对大家解决相同场景的问题时能有所帮助。

 

适用场景

这个架构用来解决“高性能场景下,当我的SERVER还需要访问拉取其他SERVER的数据时,我的SERVER性能已经达到最大了,可是这台machine所有硬件(比如所有CPU都没有达到100%machine负载还是个位数)都没有达到瓶颈”这种场景。 

还有一点需要补充,如果第三方服务或者后端SERVER仅提供同步接口给我们,没有提供任何异步接口,并且不支持我们在协议级别上实现异步访问,这个方案就无能为力了。

 

问题描述

一般来说,对其他SERVER发出大量并发请求的耗时会存在两个问题:

1、  响应时间不稳定;

2、  平均响应时间可能过长。

举个例子,当逻辑SERVER需要从其他第三方服务里拉取数据,受网络和第三方服务的稳定性影响,性能会有波动,假定平均请求时间为1S,可能99%的请求都可以正常应答,但是可能会有1%的请求延时会达到5S 
 

 

某后端SERVER对大量请求的响应时间通常会有这种长尾效果,虽然这种长时间响应的请求数量很少,但是如果设计不当,会对SERVER的整体性能产生较大影响,正常响应用户的请求也会被延迟。

一般我们可能是通过设置网络套接字的超时时间来降低这种影响,但是超时时间过短又可能造成失败率上升。

所以,我们必须防止这1%的请求对其他正常请求的响应造成影响,又不能增加失败率。

 

2个问题跟后端SERVER的性能有关,比如说,dms拉取某file cache server,当cache server达到1万次每秒访问量时,平均单次响应需要100ms,那么如果我们设计不良好的系统会出现什么问题呢?首先把非网络因素都剔除,我们假定只有网络IO占了主要请求响应时间的消耗(一般情况下都是如此)。

 

假定下面这种较常见的网络设计模式:我们对每个网络套接字设定超时时间为1S,当从接入系统中(通常是一个队列)获取到一个完整的请求后,交给一组线程池去实现业务。每个线程分解业务后,向后端SERVER发请求等待响应,收到响应后分析结果,最后应答给client

 

首先只看第2个问题。如果平均一个请求100ms,那么每个线程可以处理10次每秒,如果我们开100个线程,可以处理1000个请求每秒。理论上似乎无限。但实际上,当一台PC机器上运行的线程数达到200时,进程间切换已经占到很大的比重,如果有共享资源用到锁,那么这种加解锁的系统调用也会占用系统资源。根据我的经验,通常情况下如果一台PC机线程达到200个左右后,继续增加线程数毫无意义,只能降低性能。

 

如果再把第1个问题算进去,那么那些少量的长时间响应请求,就会占用线程池的大量业务处理线程,造成SERVER的并发处理能力下降。

 

解决方案

我在设计dms_monitor时,假定我一台dms_monitor监控多台dms,比如很极端的配置下,每秒需要轮询一台dms,一共需要管理5000dms(仅举例),那么就需要每秒发送5000个请求,从不间断(因为需要灵敏的检测到不工作的DMS),并且每个请求都要等待响应。这些请求的响应时间肯定很不均匀,就遇到了上面所说的问题。

我的解决方案是这样的:

略去其他设计,仅谈这一块网络IO模式。我的原则就是软件上无等待,使进程最大化的运行,直到达到硬件资源的瓶颈(通常最直接的表现是处理网卡中断的CPU达到100%)。所以思路就是,仅保留核心的线程,除了没办法优化的资源锁外,去除所有的阻塞操作。

 

主要设计如下:1+N线程数,用于保存中间过程的共享容器(比如排序二叉树,存储所有请求未处理完的中间数据),下面简要解释下:

 

11个发送线程,负责创建(或者从连接池中获取)非阻塞网络套接字,向后端SERVER发送请求,将发送信息存入共享容器,将套接字放进多路复用实现对象中(例如epoll,select,poll等)。

 

2N个处理线程。N>=1,有些情形下实际1个线程应该是效率最高。并且这N个线程可以是几组线程池,例如N=30时,其中10个线程用于处理大数据量(中断较多)请求,20个线程用于处理小数据量请求。这N个处理线程被多路复用实现对象激活,比如:DMS收到DB回的响应包。激活后从网络IO取出数据,接下来从共享容器中取出正确的中间数据,判断业务完整性,不完整,则继续放入多路复用实现对象中,处理下一次激活。

 

3、保存中间过程的共享容器,应当是适合addget操作非常多的容易,一般用关系容器实现,不太可能用顺序容器实现。所有的中间过程都必须保存下来,举个例子,client端发来的必要信息必须保存,如果需要多次接收后端SERVER发来的回包(比如回包超大在2K以上大小),或者该业务需要多次向后端SERVER发送请求获取回包。

 

 

这种设计肯定增大了CODE复杂度,因为原本一次完成的业务请求,被分成数次甚至被几个线程分别处理了。对于小数据量的应用,该网络设计模式没意义,高并发高性能下则可以几倍的提高同等machine的性能。

 

实现难点

我用C++实现了这种结构的简单封装,复用时在解决下面几个问题后就可以正常使用了:

1、  业务分解。根据业务特性来实现,如果只需要对后端SERVER做一次交互,就会很简单。需要多次交互,比如DMS先访问PM去加锁,然后再访问PM获取数据,这就是两次交互,需要设计好中间过程的数据。

2、  后端SERVER必须支持异步接口,协议级别同样可以。

3、  实现可以分析后端SERVER回包的回调方法。比如,判断PM回包是否完整,当一个回包1M以上时,可能需要十几次的网络调用才能完整接收。

4、  如果占用内存大,并且每个请求占用的内存大小分布极不均匀,建议用内存池来存储一个请求处理时的中间数据。

 

该设计在client访问达到server所能容纳的峰值时,所有线程都不会有阻塞,基本瓶颈在网卡,建议使用千兆网卡多个,同时每颗CPU单独绑定网卡,避免业务操作抢占网卡CPU

 

你可能感兴趣的:(技术分享)