AMQ在Servlet3.0下出现的问题

问题一:http status 500

描述:
在项目切换tomcat7的时候,原来通过使用amq实现的消息推送出现了问题, amq.js发送的大部分请求都是500,只有少部分才是200。
分析:

在Servlet2.5容器下, servlet组件默认支持异步通信, 但是到Servlet3.0的时候需要手动进行开启支持。经过排查发现, 配置参数[async-supported]来开启这个异步支持。

解决:
1.刚开始只在amq的后端Servlet组件[AjaxServlet]中配置,发现依然会出现500错误。原来只要请求经过的Filter、Servlet都需要增加配置才行。
2.简单粗暴点: 在web.xml中所有Filter、Servlet都增加[async-supported]支持。或者找到该请求的过滤器链,增加相应的也行【比较麻烦】
<servlet>
		<servlet-name>AjaxServlet</servlet-name>
		<servlet-class>org.apache.activemq.web.AjaxServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>

问题二:Amq请求超时

描述:
在解决了第一个问题后,实际使用中又出现了amq【版本5.8.0】请求经常会超时,js报错【 Serverconnection dropped.】导致后台推送的消息不能实时更新,但偶尔又很正常。
分析:
看到问题后的第一反应会不会是Apache的请求超时时间太短造成的,但是查看Apache的timeout参数及amq的超时参数,明显前者大于后者。按理说请求不至于达到Apache的超时时间而被拒绝。带着这个问题,先看下amq的前端组件的实现,发现它是在请求之前先建立,如下代码所示:
var sendPoll = function(reCon) {
		if (reCon) {
			reConnect = reCon;
		}
		// Workaround IE6 bug where it caches the response
		// Generate a unique query string with date and random
		var now = new Date();
		var timeoutArg = sessionInitialized ? timeout : 0.001;
		var data = 'timeout=' + timeoutArg * 1000
				 + '&d=' + now.getTime()
				 + '&r=' + Math.random();
		var successCallback = sessionInitialized ? pollHandler : initHandler;

		var options = { 
			method: 'get',
			data: addClientId( data ),
			success: successCallback,
			error: pollErrorHandler
		};
			
		adapter.ajax(uri, options);
	};
通过调试工具拦截请求发现,参数timeout的值都是1,这就说明初次建立连接就是失败,后面的请求更不用说了。

这个时候我们需要看一下AjaxServlet的实现,这里先说一个Continuation机制,它是建立在NIO基础上,允许被"suspend"(挂起)和"rsueme"(继续、恢复)。在suspend之前一般需要设置timeout来设置阻塞时间, 同时“Continuation”对象会提供对应的监听对象来处理事件是否“超时”或者“完成”。而在Servlet3.0的环境下,MessageServletSupport中的Continuation使用的是Servlet3Continuation,而这个Continuation只有addContinuationListener方法会设置超时,我们可以看下代码:
代码2.1:MessageServletSupport中的doMessages方法
if (message == null && client.getListener().getUndeliveredMessages().size() == 0) {
                Continuation continuation = ContinuationSupport.getContinuation(request);

                if (continuation.isExpired()) {
                    response.setStatus(HttpServletResponse.SC_OK);
                    StringWriter swriter = new StringWriter();
                    PrintWriter writer = new PrintWriter(swriter);
                    writer.println("<ajax-response>");
                    writer.print("</ajax-response>");

                    writer.flush();
                    String m = swriter.toString();
                    response.getWriter().println(m);

                    return;
                }

                continuation.setTimeout(timeout);
                continuation.suspend();
                LOG.debug( "Suspending continuation " + continuation );

                // Fetch the listeners
                AjaxListener listener = client.getListener();
                listener.access();

                // register this continuation with our listener.
                listener.setContinuation(continuation);

                return;
            }
这里当没有消息时,并不会调用Servlet3Continuation的设置超时方法,也就是无法从“suspend”状态恢复。导致请求一直闲置到Apache的请求超时时间, 同时造成amq客户端无法完成初始化标识。
从中可以看出amq客户端的初始化只不过是发出一个请求, 设置一个超级短的时间, 但后台的流程跟正常的实时信息获取一致。 这就可以解释一个现象: 如果刚初始化的时候,ActiveMq对应的队列刚好有对应信息, 则会正确返回初始化标志被正确设置。 但是随后如果无法获取到队列数据, 则一直处于等待直到apache拒绝。
通过上述的排查发现, 我们有理由怀疑是不是当前ActiveMq组件包的“Continuation”实现有问题。
解决:
通过查找资料,发现这个是amq的一个 AMQ-3447该bug在5.9.0中修复,所以升级MQ版本即可。在5.9.0的 MessageServletSupport中的 doMessages方法中我们可以看下代码:
 if (message == null && client.getListener().getUndeliveredMessages().size() == 0) {
                Continuation continuation = ContinuationSupport.getContinuation(request);

                // Add a listener to the continuation to make sure it actually
                // will expire (seems like a bug in Jetty Servlet 3 continuations,
                // see https://issues.apache.org/jira/browse/AMQ-3447
                continuation.addContinuationListener(new ContinuationListener() {
                    @Override
                    public void onTimeout(Continuation cont) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Continuation " + cont.toString() + " expired.");
                        }
                    }

                    @Override
                    public void onComplete(Continuation cont) {
                        if (LOG.isDebugEnabled()) {
                           LOG.debug("Continuation " + cont.toString() + " completed.");
                        }
                    }
                });

                if (continuation.isExpired()) {
                    response.setStatus(HttpServletResponse.SC_OK);
                    StringWriter swriter = new StringWriter();
                    PrintWriter writer = new PrintWriter(swriter);
                    writer.println("<ajax-response>");
                    writer.print("</ajax-response>");

                    writer.flush();
                    String m = swriter.toString();
                    response.getWriter().println(m);

                    return;
                }

                continuation.setTimeout(timeout);
                continuation.suspend();
                LOG.debug( "Suspending continuation " + continuation );

                // Fetch the listeners
                AjaxListener listener = client.getListener();
                listener.access();

                // register this continuation with our listener.
                listener.setContinuation(continuation);

                return;
            }
这里通过设置ContinuationListener来保证当超时或者完成时能够返回给前台一个空的Ajax报文,不至于出现超时情况。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值