AMQ在Servlet3.0下出现的问题

来源:互联网 发布:红色警戒3起义时刻mac 编辑:程序博客网 时间:2024/05/16 23:40

问题一: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 randomvar 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报文,不至于出现超时情况。


原创粉丝点击