9

I'd like to multithread my GAE servlets so that the same servlet on the same instance can handle up to 10 (on frontend instance I believe the max # threads is 10) concurrent requests from different users at the same time, timeslicing between each of them.

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);
    }
}

So basically when GAE starts up, the first time it gets a request to this servlet, an Executor is created and then saved. Then each new servlet request uses that executor to spawn a new thread. Obviously everything inside MyTask must be thread-safe.

What I'm concerned about is whether or not this truly does what I'm hoping it does. That is, does this code create a non-blocking servlet that can handle multiple requests from multiple users at the same time? If not, why and what do I need to do to fix it? And, in general, is there anything else that a GAE maestro can spot that is dead wrong? Thanks in advance.

4

3 回答 3

6

我认为您的代码行不通。

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

我不熟悉 GAE,但根据他们的文档,您可以将您的 servlet 声明为线程安全的,然后容器将并行向每个 Web 服务器发送多个请求:

<!-- in appengine-web.xml -->
<threadsafe>true</threadsafe>
于 2013-02-18T07:43:16.700 回答
5

你含蓄地问了两个问题,所以让我两个都回答:

1. 如何让我的 AppEngine Instance 处理多个并发请求?

你真的只需要做两件事:

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

这将告诉 AppEngine 实例服务器框架您的代码是线程安全的,并且您允许它在不同线程中多次调用所有请求处理程序以同时处理多个请求。注意:AFAIK,不可能将这个设置为每个 servlet 的基础。所以,你所有的 servlet 都需要是线程安全的!

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

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

2. 发布的代码对这个(或任何其他)目的有用吗?

当前形式的 AppEngine 不允许您创建和使用自己的线程来接受请求。它只允许您使用您提到的方法在处理程序内部创建线程,但只能对这一个请求进行并行处理,而不是并行接受第二个请求(这发生在外部)。doGet(...)currentRequestThreadFactory() doGet()

这个名字currentRequestThreadFactory()在这里可能有点误导。这并不意味着它将返回current Factoryof RequestThreads,即处理请求的线程。这意味着它返回一个Factory可以ThreadscurrentRequest. 因此,不幸的是,实际上甚至不允许在当前doGet()执行范围之外使用返回的 ThreadFactory,就像您建议的那样,基于它创建一个 Executor 并将其保存在类变量中。

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

您可以在此处找到有关在 appengine servlet 中可以做什么和不能做什么更多详细信息:

Java Servlet 环境 - 沙盒(特别是线程部分)

为了完整起见,让我们看看如何使您的代码“合法”:

以下应该可以工作,但它不会对您的代码能够并行处理多个请求产生影响。这将完全由<threadsafe>true</threadsafe>appengine-web.xml 中的设置决定。因此,从技术上讲,这段代码效率非常低,并且将一个基本上线性的程序流拆分为两个线程。但无论如何,这里是:

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 线程的限制:

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

您可以在此处找到有关这些问题的更多讨论:

这个话题也一直困扰着我,因为我坚信超精简的 servlet 代码,所以我常用的 servlet 可以轻松处理数百甚至数千个并发请求。至少可以说,由于每个实例 10 个线程的任意限制而不得不为更多实例付费,这对我来说有点烦人。但是阅读我上面发布的链接,听起来他们已经意识到这一点并且正在研究更好的解决方案。那么,让我们看看 Google I/O 2013 将在 5 月发布哪些公告...... :)

于 2013-02-23T06:44:08.493 回答
2

我支持 ericsonMarkus A的评估。

但是,如果由于某种原因(或其他情况)您想要遵循使用您的代码片段作为起点的路径,我建议您将执行程序定义更改为:

private static Executor executor;

以便它跨实例变为静态。

于 2013-02-26T13:42:06.430 回答