使用tomcat7创建异步servlet
该篇文章翻译自:http://developerlife.com/tutorials/?p=1437
一、简介
Servlet API 3.0 之前,需要使用类似Comet的方式来实现创建异步的Servlet。然而,Tomcat7 与 Servlet API 3.0 支持同步与异步方式。在同步Servlet中,一个处理客户端HTTP请求的线程将在整个请求的过程中被占用。对于运时较长的任务,服务器主要在等待一个应答,这导致了线程的饥渴,并且负载加重。这是由于即使服务器只是等待,服务端的线程还是被请求占光。
异步Servlet,使用在其他线程中执行耗时(等待)的操作,而允许Tomcat的线程返回线程池的方式来解决问题。当任务完成并且结果就绪,Servlet容器需要得到通知,然后另外一个线程将被分配用于处理将结果返回给客户端。客户端完全感受不到服务器的差别,并且不需要任何改变。为了允许异步魔法,新的Servlet需要使用一种回调机制(有App Server提供),告知app server结果就绪。另外,需要告知Servlet容器(app server)何时可以释放当前正在处理的请求(随后该任务将在后台的线程中得到真正的处理)。
二、简要的伪代码
1、客户端请求通过HTTP请求,然后被分配到某个Servlet。
2、Servlet.service方法在Servlet容器的某个线程中执行。
3、Servlet.service方法创建一个AsyncContext对象(使用sartAsync())。
4、Servlet.service方法后将创建AsyncContext对象传递给另外的线程执行。
5、Servlet.service方法随后返回并结束执行。
然后,客户端照样请求服务器,并且之前的那个连接被挂起等待,直到某时触发了如下事件:
1、后台的线程处理完AsyncContext任务,并且结果已经就绪,将通知AsyncContext处理已经完成。它将向HttpResponse中写入返回的数据,并且调用AsyncContext的Complete方法。这将通知Servlet容器将结果返回给客户端。
三、异常情况
假如某些异常在后台线程处理期间发生,客户端应用将得到某些网络异常。然而,若后台线程处理任务时有错误,有一种方式处理这种情况。当创建AsyncContext时,可以指定两件事情:
1、设置后台线程处理得到结果的最大时间,超过时产生一个超时异常。
2、可以在超时事件上设置监听器来处理这种情况。
因此,假如在后台线程出现了某些错误,经过一个你指定的合适时间后,监听器将被触发并且告知超时条件被触发。超时处理函数将被执行,可以向客户端发送一些错误信息。若后台线程在这之后试图向HttpResponse写入数据,将会触发一个异常。之后就需要将之前执行过程中产生的结果丢弃。
四、简单实现
将提供两组异步Servlet,一个简单实现一个一个复杂的。简单实现只是介绍异步Servlet的理念以及部分web.xml。另一个将展示耗时操作超时与触发异常,以便看到如何处理他们。
- @javax.servlet.annotation.WebServlet(
- // servlet name
- name = "simple",
- // servlet url pattern
- value = {"/simple"},
- // async support needed
- asyncSupported = true
- )
- public class SimpleAsyncServlet extends HttpServlet {
- /**
- * Simply spawn a new thread (from the app server's pool) for every new async request.
- * Will consume a lot more threads for many concurrent requests.
- */
- public void service(ServletRequest req, final ServletResponse res)
- throws ServletException, IOException {
- // create the async context, otherwise getAsyncContext() will be null
- final AsyncContext ctx = req.startAsync();
- // set the timeout
- ctx.setTimeout(30000);
- // attach listener to respond to lifecycle events of this AsyncContext
- ctx.addListener(new AsyncListener() {
- public void onComplete(AsyncEvent event) throws IOException {
- log("onComplete called");
- }
- public void onTimeout(AsyncEvent event) throws IOException {
- log("onTimeout called");
- }
- public void onError(AsyncEvent event) throws IOException {
- log("onError called");
- }
- public void onStartAsync(AsyncEvent event) throws IOException {
- log("onStartAsync called");
- }
- });
- // spawn some task in a background thread
- ctx.start(new Runnable() {
- public void run() {
- try {
- ctx.getResponse().getWriter().write(
- MessageFormat.format("<h1>Processing task in bgt_id:[{0}]</h1>",
- Thread.currentThread().getId()));
- }
- catch (IOException e) {
- log("Problem processing task", e);
- }
- ctx.complete();
- }
- });
- }
- }
服务端简单示例
代码说明:
1、可以直接命名Servlet并且提供一个特殊的url模式,以避免弄乱web.xml条目。
2、需要传递asyncSupported=true告知app server这个servlet需要采用异步模式。
3、在service方法中,超时时间设置为30秒,因此只要后台线程执行时间小于30秒就不会触发超时错误。
4、runnbale对象实际上传递给了app server在另外一个线程中执行。
5、AsyncContext监听器没有执行有价值的操作,只是打印一行日志。
- public class LoadTester {
- public static final AtomicInteger counter = new AtomicInteger(0);
- public static final int maxThreadCount = 100;
- public static void main(String[] args) throws InterruptedException {
- new LoadTester();
- }
- public LoadTester() throws InterruptedException {
- // call simple servlet
- ExecutorService exec1 = Executors.newCachedThreadPool();
- for (int i = 0; i < maxThreadCount; i++) {
- exec1.submit(new UrlReaderTask("http://localhost:8080/test/simple"));
- }
- exec1.shutdown();
- Thread.currentThread().sleep(5000);
- System.out.println("....NEXT....");
- // call complex servlet
- counter.set(0);
- ExecutorService exec2 = Executors.newCachedThreadPool();
- for (int i = 0; i < maxThreadCount; i++) {
- exec2.submit(new UrlReaderTask("http://localhost:8080/test/complex"));
- }
- exec2.awaitTermination(1, TimeUnit.DAYS);
- }
- public class UrlReaderTask implements Runnable {
- private String endpoint;
- public UrlReaderTask(String s) {
- endpoint = s;
- }
- public void run() {
- try {
- actuallyrun();
- }
- catch (Exception e) {
- System.err.println(e.toString());
- }
- }
- public void actuallyrun() throws Exception {
- int count = counter.addAndGet(1);
- BufferedReader in = new BufferedReader(
- new InputStreamReader(
- new URL(endpoint).openStream()));
- String inputLine;
- while ((inputLine = in.readLine()) != null) {
- System.out.println(MessageFormat.format("thread[{0}] : {1} : {2}",
- count, inputLine, endpoint));
- }
- in.close();
- }
- }
- }//end class ComplexLoadTester
客户端代码示例
代码说明:
1、这个简单的控台app只是产生100个线程并且同时执行GET请求,到简单与复杂的异步Servlet。
2、简单异步Servlet运行没问题,并且返回所有的回复。需要注意app server中的线程ID将会有很大的差异,这些线程来自由tomcat 7管理的线程池。另外在下面的复杂示例中你将看到比当前示例少得多的线程id。
五、复杂实现
在这个复杂实现中,有如下主要的改变,
1、这个Servlet管理自己固定大小的线程池,大小通过初试参数设置。这里设置成3。
2、头四个请求都在处理过程中发生异常,只是为了说明在service函数中未处理的异常发生时的情况。
3、耗时任务将被执行一个最大值为5秒的随机值,给AsyncContext设置的超时未60秒。这个结果在客户端请求的后20个将导致超时,因为只有3个服务器线程处理所有的100个并发请求。(100个并发的请求只有3个线程处理,每个任务1~5秒,排在后面的任务会在60秒之后才会得到执行)。
4、当tomcat7检测到超时,并且监听器被触发后,监听器需要调用AsyncContext.complete()函数。
5、一旦超时条件触发,tomcat7将会使AsyncContext包含的HttpRequest、HttpResponse对象无效。这是给耗时任务的一个信号,AsyncContext已经无效。这是为什么需要在往HttpResponse写入数据时检查Http对象是否为null。当一个超时发生时,耗时任务将不知道,并且必须检查request、response是否为null。如果为null,意味着需要停止处理,因为应答消息可以已经通过监听器或者tomcat7回复。
- @javax.servlet.annotation.WebServlet(
- // servlet name
- name = "complex",
- // servlet url pattern
- value = {"/complex"},
- // async support needed
- asyncSupported = true,
- // servlet init params
- initParams = {
- @WebInitParam(name = "threadpoolsize", value = "3")
- }
- )
- public class ComplexAsyncServlet extends HttpServlet {
- public static final AtomicInteger counter = new AtomicInteger(0);
- public static final int CALLBACK_TIMEOUT = 60000;
- public static final int MAX_SIMULATED_TASK_LENGTH_MS = 5000;
- /** executor svc */
- private ExecutorService exec;
- /** create the executor */
- public void init() throws ServletException {
- int size = Integer.parseInt(
- getInitParameter("threadpoolsize"));
- exec = Executors.newFixedThreadPool(size);
- }
- /** destroy the executor */
- public void destroy() {
- exec.shutdown();
- }
- /**
- * Spawn the task on the provided {@link #exec} object.
- * This limits the max number of threads in the
- * pool that can be spawned and puts a ceiling on
- * the max number of threads that can be used to
- * the init param "threadpoolsize".
- */
- public void service(final ServletRequest req, final ServletResponse res)
- throws ServletException, IOException {
- // create the async context, otherwise getAsyncContext() will be null
- final AsyncContext ctx = req.startAsync();
- // set the timeout
- ctx.setTimeout(CALLBACK_TIMEOUT);
- // attach listener to respond to lifecycle events of this AsyncContext
- ctx.addListener(new AsyncListener() {
- /** complete() has already been called on the async context, nothing to do */
- public void onComplete(AsyncEvent event) throws IOException { }
- /** timeout has occured in async task... handle it */
- public void onTimeout(AsyncEvent event) throws IOException {
- log("onTimeout called");
- log(event.toString());
- ctx.getResponse().getWriter().write("TIMEOUT");
- ctx.complete();
- }
- /** THIS NEVER GETS CALLED - error has occured in async task... handle it */
- public void onError(AsyncEvent event) throws IOException {
- log("onError called");
- log(event.toString());
- ctx.getResponse().getWriter().write("ERROR");
- ctx.complete();
- }
- /** async context has started, nothing to do */
- public void onStartAsync(AsyncEvent event) throws IOException { }
- });
- // simulate error - this does not cause onError - causes network error on client side
- if (counter.addAndGet(1) < 5) {
- throw new IndexOutOfBoundsException("Simulated error");
- }
- else {
- // spawn some task to be run in executor
- enqueLongRunningTask(ctx);
- }
- }
- /**
- * if something goes wrong in the task, it simply causes timeout condition that causes
- * the async context listener to be invoked (after the fact)
- * <p/>
- * if the {@link AsyncContext#getResponse()} is null, that means this context has
- * already timedout (and context listener has been invoked).
- */
- private void enqueLongRunningTask(final AsyncContext ctx) {
- exec.execute(new Runnable() {
- public void run() {
- try {
- // simulate random delay
- int delay = new Random().nextInt(MAX_SIMULATED_TASK_LENGTH_MS);
- Thread.currentThread().sleep(delay);
- // response is null if the context has already timedout
- // (at this point the app server has called the listener already)
- ServletResponse response = ctx.getResponse();
- if (response != null) {
- response.getWriter().write(
- MessageFormat.format("<h1>Processing task in bgt_id:[{0}], delay:{1}</h1>",
- Thread.currentThread().getId(), delay)
- );
- ctx.complete();
- }
- else {
- throw new IllegalStateException("Response object from context is null!");
- }
- }
- catch (Exception e) {
- log("Problem processing task", e);
- e.printStackTrace();
- }
- }
- });
- }
- }
复杂的服务器示例
六、异步还是同步
综上所述,API使用很直观,假设从一开始你就熟悉异步处理。然而假如你不熟悉异步处理,这种callback的方式会带来困惑与恐惧。另外Tomcat7与Servlet API 3.0更加容易配置servlet,在这个教程中都没有涉及,如符合语法规则的加载Servlet。
使用tomcat7创建异步servlet的更多相关文章
- Servlet 3特性:异步Servlet
解异步Servlet之前,让我们试着理解为什么需要它.假设我们有一个Servlet需要很多的时间来处理,类似下面的内容: LongRunningServlet.java package com.jou ...
- 关于servlet3.0中的异步servlet
刚看了一下维基百科上的介绍,servlet3.0是2009年随着JavaEE6.0发布的: 到现在已经有六七年的时间了,在我第一次接触java的时候(2011年),servlet3.0就已经出现很久了 ...
- WebFlux01 webflux概念、异步servlet、WebFlux意义
1 概念 待更新...... 2 异步servlet 2.1 同步servlet servlet容器(如tomcat)里面,每处理一个请求会占用一个线程,同步servlet里面,业务代码处理多久,se ...
- 异步servlet的原理探究
异步servlet是servlet3.0开始支持的,对于单次访问来讲,同步的servlet相比异步的servlet在响应时长上并不会带来变化(这也是常见的误区之一),但对于高并发的服务而言异步serv ...
- EasyUI创建异步树形菜单和动态添加标签页tab
创建异步树形菜单 创建树形菜单的ul标签 <ul class="easyui-tree" id="treeMenu"> </ul> 写j ...
- Filter 快速开始 异步Servlet 异步请求 AsyncContext 异步线程 异步派发 过滤器拦截
[web.xml] <filter> <filter-name>normalFilter</filter-name> <filter-class>net ...
- 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建异步树形菜单
jQuery EasyUI 树形菜单 - 创建异步树形菜单 为了创建异步的树形菜单(Tree),每一个树节点必须要有一个 'id' 属性,这个将提交回服务器去检索子节点数据. 创建树形菜单(Tree) ...
- 雷林鹏分享:jQuery EasyUI 表单 - 创建异步提交表单
jQuery EasyUI 表单 - 创建异步提交表单 本教程向您展示如何通过 easyui 提交一个表单(Form).我们创建一个带有 name.email 和 phone 字段的表单.通过使用 e ...
- 异步Servlet和异步过虑器
异步处理功能可以节约容器线程.此功能的作用是释放正在等待完成的线程,是该线程能够被另一请求所使用. 要编写支持异步处理的 Servlet 或者过虑器,需要设置 asyncSupported 属性为 t ...
随机推荐
- WPF数据双向绑定
设置双向绑定,首先控件要绑定的对象要先继承一个接口: INotifyPropertyChanged 然后对应被绑定的属性增加代码如下: 意思就是当Age这个属性变化时,要通知监听它变化的人. 即:Pr ...
- prettyprint
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- Android UI效果实现——Activity滑动退出效果
更新说明: 1.在QQ网友北京-旭的提醒下,在SlideFrame的initilize方法中添加了focusable.focusableInTouch.clickable的状态设置,否则会导致部分情况 ...
- Iframe 自适应高度的方法!
第一种方法:代码简单,兼容性还可以,大家可以先测试下. function SetWinHeight(obj) { var win=obj; if (document.getElementById) { ...
- jquery绑定事件失效的情况(转)
原文地址:http://www.thinksaas.cn/group/topic/348453/ jQuery中文api地址:http://www.jquery123.com/api/ jQuery官 ...
- OpenStack: OVS安装
> OVS安装:1. Install the Open vSwitch plug-in and its dependencies:# apt-get install \neutron-plugi ...
- C++函数传指针和传引用的区别
从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有 ...
- UIButton之Block回调
本文主要介绍了两种改写UIButton的点击事件方法——继承UIButton跟给UIButton添加一个分类.附代码 方法一:继承UIButton UIButtonBlock.h文件 如下 #impo ...
- SQL-Server数据库学习笔记-表
1. 表及其属性 表(Table):也称实体,是存储同类型数据的集合. 列(Field):也称字段.域或属性,它构成表的架构,具体表示为一条信息中的一个属性. 行(Row):也称元组(Tuple),存 ...
- [转]ubuntu 14.04 系统设置不见了
[转]ubuntu 14.04 系统设置不见了 http://blog.sina.com.cn/s/blog_6c9d65a10101i0i7.html 不知道删除什么了,系统设置不见了! 我在终端运 ...