Apache->JBoss低负载状态下http请求阻塞问题分析

现说明一下系统的大致结构和配置:

运行环境: apache2/jdk6/upjas2.0.3(jboss7.2.0.Final-testsuite-fix)

系统结构:apache prefork模式作为负载均衡服务器,连接后面的jboss服务器。客户端发送请求给apache,apache转发给jboss,jboss把请求处理结果返回apache,apache再返回结果给客户端。

再描述一下我遇到的这个超级奇怪的问题:
当jboss服务器几乎没有负载时(大约5分钟只有1个请求),服务器正常工作。
当jboss服务器负载很小时(大约1分钟5个请求),服务器阻塞,拒绝处理请求。
当jboss服务器继续不断增加负载到一个量后(大约1秒钟一个请求),服务器突然又从阻塞状态复活,重新开始处理请求。

下面来分析问题。

先看一下apache和jboss的配置。

apache配置:

8080端口接受外部请求,转发到jboss服务器8000端口

<VirtualHost *:8080>
    DocumentRoot "/xxx/xxx"

    ProxyPass /gateway http://x.x.x.x:8000/gateway
    ProxyPassReverse /gateway http://x.x.x.x:8000/gateway
</VirtualHost>

线程配置,说明最多可以有500个connection连接后面的jboss

<IfModule prefork.c>
        StartServers         500
        MinSpareServers      500
        MaxSpareServers     500
        ServerLimit        500
        MaxClients         500
        MaxRequestsPerChild  10000
</IfModule>

jboss配置:

<subsystem xmlns="urn:jboss:domain:threads:1.1">
    <thread-factory name="web-container-thread-factory" group-name="web-container-thread-group" thread-name-pattern="web-container-thread-%t"/>
    <bounded-queue-thread-pool name="http-executor">
        <core-threads count="20"/>       
        <queue-length count="50"/>   
        <max-threads count="1000" />    
        <keepalive-time time="10" unit="minutes" />
        <thread-factory name="web-container-thread-factory"/>
    </bounded-queue-thread-pool>
</subsystem>

<subsystem xmlns="urn:jboss:domain:web:1.4" default-virtual-server="default-host" native="false">
	<connector name="http" protocol="HTTP/1.1" scheme="http" socket-binding="http" executor="http-executor" max-post-size="2097152" />
</subsystem>

 

配置显示jboss使用的是bounded-queue-thread-pool,其工作方式是先创建thread处理请求,直到thread数量达到core-threads,后续的请求进queue,queue满后继续创建thread处理请求直到thread数量达到max-threads,后续的请求将被拒绝。(This is the most complex executor type. When a task is accepted, if the number of running pool threads is less than the "core" size, a new thread is started to execute the task.  Otherwise, if space remains in the queue, the task is placed in the queue.  Otherwise, if the number of running pool threads is less than the "maximum" size, a new thread is started to execute the task.  Otherwise, if blocking is enabled on the executor, the calling thread will block until space becomes available in the queue.  Otherwise, the task is delegated to the handoff executor, if one is configured.  Otherwise, the task is rejected.)

./jboss-cli.sh
connect

[standalone@x.x.x.x:xxxx /] /subsystem=threads/bounded-queue-thread-pool=http-executor:read-attribute(name=
core-threads          largest-thread-count  thread-factory        handoff-executor      name                  queue-size
current-thread-count  keepalive-time        queue-length          max-threads           rejected-count        allow-core-timeout
[standalone@x.x.x.x:xxxx /] /subsystem=threads/bounded-queue-thread-pool=http-executor:read-attribute(name=current-thread-count
{
    "outcome" => "success",
    "result" => 20
}
[standalone@x.x.x.x:xxxx /] /subsystem=threads/bounded-queue-thread-pool=http-executor:read-attribute(name=queue-size)
{
    "outcome" => "success",
    "result" => 1

jboss-cli的监控结果:随着请求负载的增加,先是thread从0->20,然后queue从1->50(这期间一直拒绝服务),然后thread从20->1000(这期间服务恢复)。

这就奇怪了。。为什么在queue=1-50这段期间,服务器会拒绝服务呢?已经创建的20个thread为什么没有去处理queue中的请求呢?

我们来看一下在服务器拒绝处理请求期间jboss的线程都在干什么,使用jstack {pid}

"web-container-thread-10" prio=10 tid=0x000000004017a800 nid=0x7da5 runnable [0x00007f03516bc000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.read(SocketInputStream.java:129)
	at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:713)
	at org.apache.coyote.http11.InternalInputBuffer.parseRequestLine(InternalInputBuffer.java:351)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:821)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:653)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:511)
	at org.jboss.threads.SimpleDirectExecutor.execute(SimpleDirectExecutor.java:33)
	at org.jboss.threads.QueueExecutor.runTask(QueueExecutor.java:808)
	at org.jboss.threads.QueueExecutor.access$100(QueueExecutor.java:45)
	at org.jboss.threads.QueueExecutor$Worker.run(QueueExecutor.java:828)
	at java.lang.Thread.run(Thread.java:662)
	at org.jboss.threads.JBossThread.run(JBossThread.java:122)

发现很多阻塞在读请求阶段。数量刚好等于core-threads的配置值20。

 

找了jboss-threads的源码来看。

at org.jboss.threads.QueueExecutor$Worker.run(QueueExecutor.java:828)对应下面的代码。

Worker是jboss线程池中处理请求的thread。每次来了新{请求A}并且jboss决定需要新建thread处理的时候,就会new Worker({请求A}),Worker的运行方式是先执行一次runTask({请求A}),然后无限循环从queue中take{请求N}并执行runTask({请求N})。而stack显示worker卡在了runTask({请求A}),所以没法去处理queue中的请求。

private class Worker implements Runnable {

	private volatile Runnable first;

	public Worker(final Runnable command) {
		first = command;
	}

	public void run() {
		final Lock lock = QueueExecutor.this.lock;
		try {
			Runnable task = first;
			// Release reference to task
			first = null;
			runTask(task);
			for (;;) {
				// don't hang on to task while we possibly block waiting for the next one
				task = null;
				lock.lock();
				try {
					if (stop) {
						// drain queue
						if ((task = pollTask()) == null) {
							return;
						}
						Thread.currentThread().interrupt();
					} else {
						// get next task
						if ((task = takeTask()) == null) {
							return;
						}
					}
				} finally {
					lock.unlock();
				}
				runTask(task);
				Thread.interrupted();
			}
		} finally {
			boolean last = false;
			lock.lock();
			try {
				workers.remove(Thread.currentThread());
				last = stop && workers.isEmpty();
			} finally {
				lock.unlock();
			}
			if (last) {
				shutdownListenable.shutdown();
			}
		}
	}
}

为什么worker会卡在了runTask({请求A})呢?看一下at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:821)对应的代码(只保留了关键部分)。原来如果一个http请求是keepAlive的,这个task就会一直在while循环中处理客户端发来的后续请求,直到socket超时或者处理的请求数超过maxKeepAliveRequests。

public boolean process(InputStream input, OutputStream output) {
    ...	
    int keepAliveLeft = maxKeepAliveRequests;
	keepAlive = true;
	while (started && !error && keepAlive) {
		...
        socket.setSoTimeout(soTimeout);
		inputBuffer.parseRequestLine();
		inputBuffer.parseHeaders();
		prepareRequest();
		...
		if ( useKeepAliveAlgorithm ) {
			if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0) {
				keepAlive = false;
			}
		} else {
			keepAlive = false;
		}
		...
		inputBuffer.nextRequest();
		outputBuffer.nextRequest();
	}
}

 

现在让我们来解释一下我们遇到的问题的现象:

一开始几乎没有负载时,都是core-thread在处理请求,由于很久才会来一个请求,所以20个core-threads在接到新的请求之前,旧的请求就已经socket读取超时,Worker中的第一个runtask()也就结束了。后续的请求刚进入queue就被core-thread拉走并处理了。

当负载上升到一个很低的程度后,20个core-threads还阻塞在等待socket超时的阶段,新的请求已经进来,这时新进入queue的请求就没人来拉走了,导致queue从0-50不断增大,服务器却一直拒绝服务,因为所有20个core-threads都被阻塞住了。

当负载继续增大,速度超过core-threads等待socket超时的速度,queue最终到达50后,QueueExecutor在处理新进来的请求时就会创建新的thread来处理请求,这些新的thread和core-threads一样,处理完第一个请求后也阻塞在socket读取超时阶段了,但是在客户端看来服务器确实是在响应了。 

那么当thread到达1000后怎么办呢?不能通过创建thread来处理请求了,jboss对后续的请求是不是又会拒绝服务呢?这里来到了关键点,apache配置了最多只能有500个进程,也就是说jboss同时只会收到最多500个并发请求。之前由于500>20,很多新的请求不会发到20个core-thread阻塞等待的那些socket上,这20个core-thread只能无限等待下去直到socket超时。现在已经有了1000个线程了,由于500<1000,那么从apache来的请求一定是发到这1000个thread等待的那些socket上的,那这些thread直接就能在worker的第一个runtask()里处理新来的请求了,而不是从queue里拉取请求再处理。

解决方案有2种:

1. 设置thread在worker的第一个runtask()处理完socket的第一个请求后,不再阻塞等待后续请求,直接推出runtask()。这样thread就会很快从worker的第一个runtask()中返回,然后就能从queue中读取新来的请求进行处理了。

<system-properties>
	<property name="org.apache.coyote.http11.Http11Protocol.MAX_KEEP_ALIVE_REQUESTS" value="1" />
</system-properties>

2. 配置core-threads=max-threads=1000的值>=apache的进程数,这样就根本不会有任何请求进入queue,所有的请求都在worker的第一个runtask中完成处理。

<subsystem xmlns="urn:jboss:domain:threads:1.1">
    <thread-factory name="web-container-thread-factory" group-name="web-container-thread-group" thread-name-pattern="web-container-thread-%t"/>
    <bounded-queue-thread-pool name="http-executor">
        <core-threads count="1000"/>       
        <queue-length count="50"/>   
        <max-threads count="1000" />    
        <keepalive-time time="10" unit="minutes" />
        <thread-factory name="web-container-thread-factory"/>
    </bounded-queue-thread-pool>
</subsystem>

 

转载于:https://my.oschina.net/fifadxj/blog/884382

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值