下面测试使用Jetty的continuation机制,首先自定义一个服务,分别添加blockservlet和nonblockservlet。开启5个线程的线程池提供server服务。(jetty 8.1.8版本)
package test;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
public class PJetty{
public static void main(String[] args) throws Exception {
//设置connectors
SelectChannelConnector connector1 = new SelectChannelConnector();
connector1.setPort(1987);
connector1.setThreadPool(new QueuedThreadPool(5));
Server server = new Server();
server.setConnectors(new Connector[]{connector1});
//使用servlet处理请求
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(new ServletHolder(new BlockingServlet()), "/block");
context.addServlet(new ServletHolder(new NonBlockingServlet()), "/nonblock");
server.setHandler(context);
server.start();
server.join();
}
}
先实现一个阻塞的servlet服务BlockingServlet.java如下
package test;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BlockingServlet extends HttpServlet {
/**
* generated serialize number
*/
private static final long serialVersionUID = -3402527424876194224L;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//可以方便地修改日期格式
public void service(HttpServletRequest req, HttpServletResponse res)
throws java.io.IOException {
String start = dateFormat.format( new Date() );
String sleeptime = req.getParameter("st");
res.setContentType("text/plain");
res.getWriter().println("Request: "+sleeptime+"\tstart:\t" + start);
res.getWriter().flush();
try {
if(sleeptime != null){
Thread.sleep(Integer.parseInt(sleeptime)*1000);
}
} catch (Exception e) {}
String end = dateFormat.format( new Date() );
res.getWriter().println("Request: "+sleeptime+"\tend:\t" + end);
}
}
然后实现一个NonBlockingServlet.java如下,continuation采用suspend/resume模式
package test;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
public class NonBlockingServlet extends HttpServlet {
/**
* generated serialize number
*/
private static final long serialVersionUID = 3313258432391586994L;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//可以方便地修改日期格式
MyAsyncHandler myAsyncHandler ;
public void init() throws ServletException {
myAsyncHandler = new MyAsyncHandler() {
public void register(final MyHandler myHandler) {
try {
Thread.sleep(10000);
String end = dateFormat.format( new Date() );
myHandler.onMyEvent(end);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String start = dateFormat.format( new Date() );
String reqId = request.getParameter("st");
// 如果我们需要异步方式得到一个result,并放入request中
Object results = request.getAttribute("results");
if (results==null) // 如果异步处理尚未返回结果
{
final Continuation continuation = ContinuationSupport.getContinuation(request);
if(continuation.isInitial()){
continuation.setTimeout(8000);
response.setContentType("text/plain");
response.getWriter().println("Request: "+reqId+"\tstart:\t"+start);
response.getWriter().flush();
}
// 判断是否超时
if (continuation.isExpired())
{
// 返回超时Response
response.getWriter().println("timeout");
response.getWriter().flush();
return;
}
// 挂起HTTP连接
continuation.suspend();
// 注册一个异步事件处理器
myAsyncHandler.register(new MyHandler() {
public void onMyEvent(Object result) {
continuation.setAttribute("results", result);
continuation.resume();
}
});
return; // or continuation.undispatch();
}
// 连接恢复后返回结果
response.getWriter().println("Request: "+reqId+"\tend:\t" + results);
response.getWriter().flush();
}
private interface MyAsyncHandler{
public void register(MyHandler myHandler);
}
private interface MyHandler{
public void onMyEvent(Object result);
}
}
一旦continuation被挂起,需要注册一系列回调函数,请求将被挂起一直到continuation.resume()或continuation.complete()被调用,如果都没有被调用,则超时。
运行服务,测试结果如下:
root $ for i in `seq 1 10`; do lynx -dump http://192.168.201.167:1987/block?st=$i & done Request: 1 start: 2012/12/19 10:41:59 Request: 1 end: 2012/12/19 10:42:00 Request: 4 start: 2012/12/19 10:41:59 Request: 4 end: 2012/12/19 10:42:03 Request: 9 start: 2012/12/19 10:41:59 Request: 9 end: 2012/12/19 10:42:08 Request: 2 start: 2012/12/19 10:42:08 Request: 10 start: 2012/12/19 10:42:00 Request: 2 end: 2012/12/19 10:42:10 Request: 10 end: 2012/12/19 10:42:10 Request: 8 start: 2012/12/19 10:42:03 Request: 8 end: 2012/12/19 10:42:11 Request: 3 start: 2012/12/19 10:42:10 Request: 3 end: 2012/12/19 10:42:13 Request: 5 start: 2012/12/19 10:42:11 Request: 5 end: 2012/12/19 10:42:16 Request: 7 start: 2012/12/19 10:42:10 Request: 7 end: 2012/12/19 10:42:17 Request: 6 start: 2012/12/19 10:42:13 Request: 6 end: 2012/12/19 10:42:19 root $ for i in `seq 1 10`; do lynx -dump http://192.168.201.167:1987/nonblock?st=$i & done Request: 1 start: 2012/12/19 11:43:48 Request: 1 end: 2012/12/19 11:43:49 Request: 2 start: 2012/12/19 11:43:48 Request: 2 end: 2012/12/19 11:43:50 Request: 3 start: 2012/12/19 11:43:48 Request: 3 end: 2012/12/19 11:43:51 Request: 4 start: 2012/12/19 11:43:48 Request: 4 end: 2012/12/19 11:43:52 Request: 5 start: 2012/12/19 11:43:48 Request: 5 end: 2012/12/19 11:43:53 Request: 6 start: 2012/12/19 11:43:48 Request: 6 end: 2012/12/19 11:43:54 Request: 7 start: 2012/12/19 11:43:48 Request: 7 end: 2012/12/19 11:43:55 Request: 9 start: 2012/12/19 11:43:48 Request: 10 start: 2012/12/19 11:43:48 timeout timeout Request: 8 start: 2012/12/19 11:43:56 timeout
再用ab简单测试一下,nonblock模式rps(request per second)要高好几倍的样子吧
附:当异步handler操作结束返回结果时servlet或filter用来生成响应内容,典型地做法是通过设定request的attribute来传递结果results,用来判断请求是否被挂起。而suspend/continue模式用在异步的handler产生响应时使用。
continuation的suspend/continue模式实现如下:
package test;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
public class NonBlockingServlet extends HttpServlet {
/**
* Suspend/Resume
*/
private static final long serialVersionUID = 3313258432391586994L;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//可以方便地修改日期格式
MyAsyncHandler myAsyncHandler ;
public void init() throws ServletException {
myAsyncHandler = new MyAsyncHandler() {
public void register(final MyHandler myHandler,final String sleeptime) {
new Thread(new Runnable() {
public void run() {
try {
if(sleeptime != null){
Thread.sleep(Integer.parseInt(sleeptime)*1000);
}
String end = dateFormat.format( new Date() );
try {
myHandler.onMyEvent(end);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
};
}
protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws IOException
{
String start = dateFormat.format( new Date() );
final String sleeptime = request.getParameter("st");
final Continuation continuation = ContinuationSupport.getContinuation(request);
if(continuation.isInitial()){
response.setContentType("text/plain");
response.getWriter().println("Request: "+sleeptime+"\tstart:\t"+start);
response.getWriter().flush();
}
continuation.setTimeout(8000);
// if this is not a timeout
if (continuation.isExpired())
{
// 返回超时Response
response.getWriter().println("timeout");
response.getWriter().flush();
return;
}
// 挂起请求
continuation.suspend(response); // response may be wrapped.
// register with async service. The code here will depend on the
// the service used (see Jetty HttpClient for example)
myAsyncHandler.register(new MyHandler()
{
public void onMyEvent(Object result) throws IOException
{
// 连接恢复后返回结果
response.getWriter().println("Request: "+sleeptime+"\tend:\t" + result);
response.getWriter().flush();
continuation.complete();
}
},sleeptime);
}
private interface MyAsyncHandler{
public void register(MyHandler myHandler,String sleeptime);
}
private interface MyHandler{
public void onMyEvent(Object result) throws IOException;
}
}