2013-02-13 34 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; 

,使之成爲整個實例的靜態。