你实际上提出了两个问题,我来回答一下:
1. 如何让我的AppEngine实例处理多个并发请求?
你只需要做两件事情:
- 在你的
appengine-web.xml
文件中添加语句<threadsafe>true</threadsafe>
,这个文件可以在war\WEB-INF
文件夹中找到。
- 确保你所有请求处理器中的代码都是线程安全的,即在
doGet(...)
、doPost(...)
等方法中只使用局部变量,或者确保同步对类或全局变量的所有访问。
这将告诉AppEngine实例服务器框架,你的代码是线程安全的,并且你允许它在不同的线程中多次调用所有的请求处理器来同时处理多个请求。注意: 据我所知,无法在每个servlet上设置这个属性。因此,所有的servlet都需要是线程安全的!
因此,你发布的执行器代码已经包含在每个AppEngine实例的服务器代码中,并且实际上从AppEngine为每个请求创建(或重用)的单独线程的run方法内部调用你的
doGet(...)
方法。基本上,
doGet()
已经是你的
MyTask()
。
文档的相关部分在这里(虽然它并没有提供太多信息):https://developers.google.com/appengine/docs/java/config/appconfig#Using_Concurrent_Requests
2. 发布的代码对此(或任何其他)目的有用吗?
在其当前形式下,AppEngine不允许您创建和使用自己的线程来接受请求。它只允许您在doGet(...)
处理程序内部使用currentRequestThreadFactory()
方法创建线程,但仅用于此一请求的并行处理,而不能同时接受第二个请求(这发生在doGet()
之外)。
名称
currentRequestThreadFactory()
在这里可能有点误导。它并不意味着它将返回
RequestThreads
的
current
Factory
,即处理请求的线程。它的意思是它返回一个
Factory
,可以在
currentRequest
内创建
Threads
。因此,遗憾的是,实际上甚至不允许在当前
doGet()
执行的范围之外使用返回的ThreadFactory,就像你建议的那样,基于它创建一个Executor并将其保留在类变量中。
对于前端实例,在
doGet()
调用中创建的任何线程都会在
doGet()
方法返回时立即终止。对于后端实例,您被允许创建保持运行的线程,但由于您不被允许在这些线程内打开服务器套接字以接受请求,因此这些仍然不允许您自己管理请求处理。
您可以在此处找到有关在appengine servlet内部可以和不能做的更多详细信息:
Java Servlet环境 - 沙盒(特别是Threads部分)
为了完整起见,让我们看看如何使你的代码“合法”:
以下代码应该可以工作,但在处理多个请求并行时没有任何区别。这将仅由你的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));
writeResponse(response, result.get());
}
}
既然您已经在处理当前请求的特定线程内部,那么您肯定应该避免“线程内嵌线程”,而应该采用以下方法:
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代码可以轻松处理数百甚至数千个并发请求,所以由于每个实例只能有10个线程的任意限制而不得不支付更多实例的费用,这让我感到有点恼火。但是阅读我上面发布的链接后,听起来他们已经意识到这一点,并正在努力寻找更好的解决方案。所以,让我们期待5月份Google I/O 2013的公告吧... :)
currentRequestThreadFactory()
方法也不完全符合人们根据其名称所期望的方式。我在下面发布了一个答案,希望能澄清该方法名称中的歧义。(它是current-Request-Thread-Factory,而不是current-RequestThread-Factory);) - Markus A.