2013-02-13 84 views
9

我想多线程化我的GAE servlet,以便同一个实例上的同一个servlet可以处理多达10个(在前端实例I 相信最大线程数为10)并发请求来自不同用户的同时,在它们之间进行时间分割。Multithread GAE servlet处理并发用户

public class MyServlet implements HttpServlet { 
    private Executor executor; 

    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) { 
     if(executor == null) { 
      ThreadFactory threadFactory = ThreadManager.currentRequestFactory(); 
      executor = Executors.newCachedThreadPoolthreadFactory); 
     } 

     MyResult result = executor.submit(new MyTask(request)); 

     writeResponseAndReturn(response, result); 
    } 
} 

所以当GAE启动时,它第一次到达这个servlet的请求,创建一个Executor,然后保存基本。然后每个新的servlet请求使用该执行器来产生一个新的线程。显然MyTask中的所有内容都必须是线程安全的。

我关心的是这是否真的做到了我希望的事情。也就是说,这个代码是否创建了一个无阻塞的servlet,可以同时处理多个用户的多个请求?如果没有,为什么和我需要做些什么来解决它?而且,总的来说,GAE大师可以发现哪些是错误的?提前致谢。

+0

+1 - 我真的很喜欢你正在考虑这个问题的方式以及你试图解决问题的方式。但是,幸运的是,GAE不会以这种方式工作,并且'currentRequestThreadFactory()'方法根据您如何读取它的名称并不完全符合您的期望。我在下面发布了一个答案,希望能够清除方法名称中的歧义。 (这是当前请求线程工厂,而不是当前请求线程工厂);) – 2013-02-23 07:45:56

+0

只是出于好奇:有什么具体的你试图实现?我问,因为有一段时间,我正在研究完全相同的问题。我希望能够使用GAE实现某种长轮询推送通知,但事实证明,还有很多其他问题会以这种方式得到解决。在允许您调整其安排和处理请求的方式方面,GAE确实非常有限。而其中的一些局限性有点模糊......但是,当然,这就是它的速度和可扩展性。 – 2013-02-23 21:29:08

回答

6

我不认为你的代码会工作。

doGet方法正在由servlet容器管理的线程中运行。当请求进入时,一个servlet线程被占用,并且直到doGet方法返回才会被释放。在您的代码中,executor.submit将返回Future对象。要获得实际结果,您需要在Future对象上调用get方法,并且它会阻止,直到MyTask完成其任务。只有在此之后,doGet方法返回和新的请求可以踢。

我不熟悉GAE,但根据their docs,你可以宣布你的servlet是线程安全的,则容器将派出多个请求的每个Web服务器并行:

<!-- in appengine-web.xml --> 
<threadsafe>true</threadsafe> 
5

您隐问了两个问题,所以让我回答这两个:

1.我怎样才能让我的AppEngine实例来处理多个并发请求?

你真的只需要做两两件事:

  1. 添加语句<threadsafe>true</threadsafe>appengine-web.xml文件,你可以在war\WEB-INF文件夹中找到。
  2. 确保代码中所有您的请求处理实际上是线程安全的,即只使用局部变量在doGet(...)doPost(...)等方法,或确保您同步所有访问类或全局变量。

这将告诉AppEngine实例处理服务器框架,你的代码是线程安全的,您允许它来调用所有的请求处理的多次在不同的线程来处理在同一时间几个请求。注意:AFAIK,不可能根据每个servlet设置一个。所以,全部你的servlets需要是线程安全的!

因此,本质上,您发布的执行程序代码已包含在每个AppEngine实例的服务器代码中,并且实际上从AppEngine创建(或重复使用)的单独线程的run方法内调用您的doGet(...)方法每个请求。基本上doGet()已经你的MyTask()

的文档的相关部分是在这里(虽然它并没有真正多说):https://developers.google.com/appengine/docs/java/config/appconfig#Using_Concurrent_Requests

2.是否张贴代码这个有用(或任何其他)目的是什么?

AppEngine以其当前形式不允许您创建和使用自己的线程来接受请求。它不仅可以让你创建线程doGet(...)处理程序,使用你所提到的currentRequestThreadFactory()方法,但只有这一个要求做并行处理,而不是接受平行的第二个(这种情况doGet())。

名称currentRequestThreadFactory()在这里可能有点误导。这并不意味着它会返回RequestThreadscurrentFactory,即处理请求的线程。这意味着它返回的Factory可以在currentRequest内创建Threads。因此,不幸的是,实际上甚至不允许使用返回的ThreadFactory超出当前执行的范围,就像您基于它创建Executor并将其保留在类变量中一样。

对于前端实例,您在doGet()调用中创建的任何线程将在您的doGet()方法返回时立即终止。对于后端实例,您可以创建保持运行的线程,但由于您不允许打开服务器套接字来接受这些线程内的请求,所以这些仍然不允许您自己管理请求处理。

你可以找到你可以不能的AppEngine上的servlet这里里面做更多的细节:

The Java Servlet Environment - The Sandbox(特别是线程部分)

为了完整,让我们来看看如何将您的代码制作为“合法”:

以下应该可以工作,但它不会在你的代码能够并行处理多个请求方面产生影响。这将完全由您在appengine-web.xml中的<threadsafe>true</threadsafe>设置决定。所以,从技术上讲,这段代码实际上效率很低,并且在两个线程之间分裂了基本上线性的程序流。但在这里它是反正:

public class MyServlet implements HttpServlet { 

    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) { 
     ThreadFactory threadFactory = ThreadManager.currentRequestThreadFactory(); 
     Executor executor = Executors.newCachedThreadPool(threadFactory); 

     Future<MyResult> result = executor.submit(new MyTask(request)); // Fires off request handling in a separate thread 

     writeResponse(response, result.get()); // Waits for thread to complete and builds response. After that, doGet() returns 
    } 
} 

既然你已经是一个单独的线程特定于当前正在处理请求里面,你一定要救自己“线程内螺纹”的,只是这样做,而不是:

public class MyServlet implements HttpServlet { 

    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) { 
     writeResponse(response, new MyTask(request).call()); // Delegate request handling to MyTask object in current thread and write out returned response 
    } 
} 

或者,甚至更好的办法就是将代码从MyTask.call()移到doGet()方法中。 ;)

除了 - 关于你提到的10个同时的servlet线程的限制:

这是一个(临时)的设计决策,允许谷歌控制其服务器上的负载更容易(特别是内存使用servlet)。

你可以找到关于这些问题在这里进行更多的讨论:

此主题已被窃听的挫折感了我,也因为我我非常信任超精益servlet代码,所以我通常的servlet可以轻松处理数百个(如果不是数千个)并发请求。由于每个实例有10个线程的任意限制,所以不得不支付更多的实例,这对我来说至少可以说是有点令人讨厌。但通过阅读上面张贴的链接,这听起来像他们意识到这一点,并正在寻求更好的解决方案。那么,让我们来看看谷歌I/O 2013年将在五月带来什么公告... :)

2

我第二埃里克森马库斯A的评估

但是,如果由于某种原因(或对于一些其他的情况),你要遵循使用您的代码段为出发点的路径,我建议你改变你的遗嘱执行人定义:

private static Executor executor; 

,使之成为整个实例的静态。