怎么优化设置apache并发数的并发数量

今天给大家分享一个线上出现的问题,顺便普及一下关于Socket通信的一些常识。

上周在线上出现了一个很低级的问题,但是正是这个低级的问题引起了我的兴趣,其实所谓的低级是因为配置文件配置错了,原本线上是为每个客户端设置了一个席位,就说是客户端的配置内容是不同的,但是由于部署的人员将两个客户端席位设置的一样,这时候连接服务端的时候会出现问题,服务端的设置的策略是同一时刻只能有一个席位在线,接下来就开始了“一出好戏”----2个客户端开始抢占服务器,不断的进行“互踢”,因为客户端设置了断线重连。

下面我们先来看一下socket通信的基本流程

1、服务器创建监听socket
2、与对外服务的端口号绑定
3、开始listen监听上面设置的端口
4、客户端连接到服务器对应的port
5、服务器accept为新的客户端产生新的socket
6、基于这个新的socket与客户端交换数据。

这个流程是基础的网络交互过程,相信开发过网络应用的同学都清楚,这里采用的是短链接,每次通信后关闭连接,不会保持。那么如果同一个时刻有大量的客户端连接上来,服务端能处理吗?

带着这个问题,我们来学习一下操作系统关于Socket建立的相关处理,这里只是简单了解。重点是引出FD概念

从上图我们可以看出sys_socket是socket相关函数的总入口。

上面我们可以看到这个语句

其中函数sock_map_fd(sock),这个我们就不展开了,该方法体有一行代码

这个就是建立真正的socket的FD语句,从此我们可以看出来,其中每个socket都是有一个FD与之对应的,在Linux操作系统里一切皆文件,那么这个FD的数量是多少也就决定了socket能建立多少,FD会受到系统内存的影响,一般情况下1G的内存可以创建10万个socket,依次叠加2G就是20万。这里操作系统将网络连接与文件系统进行了连接,在进行数据发送读取就和操作文件一样,大家现在知道为啥socket数量创建的数量有限了吧,因为这个会消耗操作系统的资源,所以一般我们使用完FD都会进行释放,防止有资源泄露,无法回收的情况。

内核(kernel)利用文件描述符(File Descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

上面的内容可以说是一系统要做到"高并发"能够达到“高”的一个前提条件。

接下来我们引入另外一个概念,就是IO多路复用,这个关系到一个并发的问题,我们知道比如进行文件操作,都会进行IO流的读写,但是对文件的操作一般是阻塞的,在JDK1.4以后引入了NIO,增加了非阻塞的方式,前面我们说对网络数据的操作,也是类似与文件的操作,也可以进行阻塞和非阻塞的模式。这里我们后面在具体说明,这里还有一个关键因素,就是操作系统如何处理IO的多路复用的问题,如果底层处理不好,我们上层应用一样会达不到高效能。

所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。

所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。

IO多路复用允许应用在多个文件描述符上阻塞,并在某一个可以读写时通知, 一般遵循下面的设计原则:

  1. IO多路复用:任何FD准备好IO时进行通知

  2. 在FD就绪前进行睡眠。

  3. 在不阻塞的情况下处理所有IO就绪的FD

由于时间篇幅关系,下面我们简单介绍一下每一种多路复用方案的优缺点

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

       当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

上面的两种方式中,每次调用都需要所有被监听的文件描述符,内核必须遍历所有的文件描述符,当文件描述符变得很大,这里的遍历就会成为瓶颈。epoll将监听注册从实际监听中分离出来,完成了真正的事件等待。

epoll是linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level

2.使用mmap加速内核与用户空间的消息传递。

对于select和poll函数的系统与内核每次调用时的数据拷贝:epoll是通过内核与用户空间mmap同一块内存实现的,在epoll_ctl函数中:每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

3.调用后不需轮询判断描述符事件是否就绪。

对于select和poll函数每次调用后轮询检测事件是否发生:epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果)。

4.监视描述符没有个数上限。

epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,注:在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

5.IO效率不随FD数目增加而线性下降。

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是“活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对“活跃”的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。只有“活跃”的socket才会主动的去调用

拓展:系统维护一颗红黑树(平衡搜索二叉树:稳定)存储监视描述符,和一张链表存储就绪的描述符。当每次注册或修改,删除新的文件描述符到epoll句柄中时,就会增加一个描述符到这课红黑树中(增删改查简单),当返回时检测链表上是否有节点,有节点则拷贝到用户传给它的那个描述符数组中。

由此可知,我们如果要开发高并发的网络通信程序,使用selector这种模式是不行的,因为这个有数量的限制,但后面2中模式是可以的,除了底层的支撑外,我们程序还需要使用到NIO,或者是AIO,如果是阻塞IO,性能也不会达到最佳。

好了,到目前为止我们已经对IO复用和socket有了一定的了解。那么我们开始剖析前面的问题。

在实际项目中,我们引用了一个Apache开源的网络框架,名字MINA,它是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。

在实际使用过程中也很简单,我们举个例子

这样我们启动客户端和服务端就能进行数据交换了

我们线上的客户端和服务端是采用长连接异步的通讯方式,就是需要保持心跳的,当我们席位重复互踢时,后面的一个会替代前一个登录上的客户端,这个时候服务端会断开与前一个客户端的连接。

我们设置了每隔30s会自动检测系统是否在线,除了保持心跳额外开启了一个线程进行检测。因为检测到到Iosession已经关闭了会话,这个时候重新走了一次客户端连接服务端的流程,就是上面这段代码,这里只是举例子,真实代码类似,有一句代码出了重要问题,因为我们是长连接,每次建立通讯后,不会执行下面这行代码,

否在就是短链接了,只有在程序连接不上去的时候进行了Dispose操作。今天进行了一个简单的实验,如果你使用了连接但是没有dispose,后果还是很严重的。

下面我们看一下没有dispose的客户端不断和服务端进行连接的结果

客户端我们可以看到他是在不断的建立本地的socket连接,每次执行

NioSocketConnector connector = new NioSocketConnector();就会建立很多IP4的回环网络通信,自己和自己进行通讯,这些额外的端口建立耗费了巨大的系统资源,过一会我们看一下客户端,已经建立了相当大的连接,这一部分连接的用途有待进一步考察,它们是做什么用的?

因此,这里第一个问题,如果你采用的是短链接一定要及时释放着NioSocketConnector。

但我遇到的问题并不是释放这个这么简单,通过代码改写,每次session被close掉了以后,会去手动再次dispose()一下,因为想着是服务端主动断开连接,Mina应该会帮我们断开Connector才对的,但是随着互踢的次数增多,系统的FD文件数量,通过查看进程开启的的fd可以知道是在不断的增加的,如果是Linux可以通过lsof -p <processid> |wc -l 统计。一般在linux,为了防止进程过度的消耗系统资源,都会进行fd数量的限制,我们通过命令ulimit -HSn,H指定了硬性大小,S指定了软性大小,n表示设定单个进程最大的打开文件句柄数量。系统一般不建议超过4096。随着时间增加,我们程序的fd数量不一会就已经超过了4096,再次新建连接就会出现下面的异常

意味着操作系统已经不允许当前进程创建更多的文件句柄,所以我们就无法再次连接,为了解决这个问题,我们对源码进行了研究,发现每次新建连接都去

执行了这句代码,问题就是处在这里。因为用的nio,这段代码,其实每次都会去底层新建一个sockchannel,然后调用select.open(),将channel注册到select上,那么就会消耗系统资源。因此我们尝试将这段代码改成单例模式,每次只有一个connector,然后每次连接

这样,问题就解决,每次统计fd数量最多上升到200,就会自动回收,但数量维持在100以上,估计就是上面mina自身开了很多回环IP端口通信使用的。

因此,在实际开发项目的过程中,大家需要时刻注意,第一,需要释放的资源及时回收,如果等JAVA自身的垃圾回收机制,可能来不及,毕竟系统资源很宝贵;第二,对于某些对象,是否需要频繁的创建,如果能使用单例,尽量用单例,这个时候想起Spring强大的容器管理能力。 

下面是我的个人公众号,文章第一时间更新,欢迎关注

当Apache的安装目录/logs下的error-.log文件中,出现以上日志说明时,说明Apache需要进行优化了,修改Apache的最大并发连接数。

Apache是一个跨平台的web服务器,由于其简单高效、稳定安全的特性,被广泛应用于计算机技术的各个领域。现在,Apache凭借其庞大的用户数,已成为用户数排名第一的web服务器。

众所周知,Apache是一个跨平台的、采用模块化设计的服务器。为了应对不同的平台和不同的环境产生的各种不同的需求,也为了在具体的平台或环境下达到最佳的效果,Apache在web服务器的基础功能方面(端口绑定、接收请求等)也同样采用了模块化设计,这个Apache的核心模块就叫做多路处理模块(Multi-Processing

Apache针对不同的操作系统提供了多个不同的MPM模块,例如:mpm_beosmpm_eventmpm_netwarempmt_os2mpm_preforkmpm_winntmpm_worker。如果条件允许,我们可以根据实际需求将指定的MPM模块编译进我们自己的Apache中(Apache的源码是开放的,允许用户自行编译)。不过,如果在编译时我们没有选择,Apache将按照如下表格根据不同的操作系统自行选择对应的MPM模块,这也是Apache针对不同平台推荐使用的MPM模块。

不同操作系统上默认的MPM模块
由Be公司开发的一种多媒体操作系统,官方版已停止更新。
由NOVELL公司推出的一种网络操作系统
一种最初由微软和IBM共同开发的操作系统,现由IBM单独开发(微软放弃OS/2,转而开发Windows)

mpm_winnt模块是专门针对Windows操作系统而优化设计的MPM模块。它只创建一个单独的子进程,并在这个子进程中轮流产生多个线程来处理请求。

修改MPM模块配置(方法一)

在对Apache的MPM模块具备一定了解后,我们就可以针对不同的MPM模块来修改Apache最大并发连接数配置了。

1.启用MPM模块配置文件

Apace安装目录/conf/extra目录中有一个名为httpd-mpm.conf的配置文件。该文件主要用于进行MPM模块的相关配置。不过,在默认情况下,Apache的MPM模块配置文件并没有启用。因此,我们需要在httpd.conf文件中启用该配置文件,如下所示:

2.修改MPM模块配置文件中的相关配置

在启动MPM模块配置文件后,我们就可以使用文本编辑器打开该配置文件,我们可以看到,在该配置文件中有许多<IfModule>配置节点,如下图所示:

只有Apache使用对应MPM模块时,对应配置才会生效

此时,我们就需要根据当前Apache服务器所使用的MPM模块,来修改对应<IfModule>节点下的参数配置。首先,我们来看看mpm_winnt模块下的默认配置:

#由于mpm_winnt模块只会创建1个子进程,因此这里对单个子进程的参数设置就相当于对整个Apache的参数设置。
 
对应的配置参数作用如下:

每个子进程的最大并发线程数。
每个子进程允许处理的请求总数。如果累计处理的请求数超过该值,该子进程将会结束(然后根据需要确定是否创建新的子进程),该值设为0表示不限制请求总数(子进程永不结束)。

该参数建议设为非零的值,可以带来以下两个好处:

  1. 可以防止程序中可能存在的内存泄漏无限进行下去,从而耗尽内存。
  2. 给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。

修改MPM模块配置(方法二)

 
 
注意:在配置相关参数时,请先保证服务器具备足够的硬件性能(例如:CPU、内存等)。 如果发现自启动后,随着服务器的运行时间增加,服务器的内存占用也随之增加,可能是程序中出现内存泄露,请向下调整参数MaxRequestsPerChild的值以降低内存泄露带来的影响,然后尽快找出程序中的问题之所在。

我要回帖

更多关于 apache并发数 的文章

 

随机推荐