2013-01-03 88 views
11

什麼是在REST服務前有請求隊列的最佳技術解決方案(框架/方法)。 ,這樣我就可以增加REST服務實例的數量以獲得更高的可用性,並通過將請求隊列置於前面來形成服務客戶端的服務/事務邊界。REST服務前的請求隊列

  1. 我需要很好的和輕量化技術/框架的選擇。請求隊列(JAVA)
  2. 方法來實現競爭性消費它。
+3

您可以使用負載平衡器嗎? – Henry

+0

你到目前爲止發現了什麼? – miku

+2

@Henry可能是他想要的是保持一個請求隊列集中即使所有的appservers都忙,沒有請求應該被拒絕,他們應該在池中等待輪到他們。 – Subin

回答

7

有幾個問題在這裏,這取決於你的目標。

首先,它不僅促進在後端資源的可用性。考慮你是否有5臺服務器在後端處理隊列請求。如果其中一臺服務器出現故障,那麼排隊的請求應該回退到隊列中,並重新發送到其餘4臺服務器中的一臺。

然而,雖然這些後端服務器正在處理中,前端服務器都保持在實際的,發起請求。如果其中一臺前端服務器出現故障,那麼這些連接將完全丟失,並且將由原始客戶端重新提交請求。

前提或許是簡單的前端系統是在一個失敗的風險較低,這是軟件相關的故障肯定是真的。但是網絡卡,電源,硬盤等對於人類的這種虛假的希望和平等的懲罰都是不可知論的。所以,在談論整體可用性時要考慮這一點。

至於設計,後端是一個簡單的過程等待在JMS消息隊列,並處理每個消息,因爲他們來。這裏有很多這樣的例子,任何JMS服務器都可以適用於高層。您所需要的只是確保消息處理是事務性的,以便如果消息處理失敗,則消息保留在隊列中,並可以重新發送到另一個消息處理程序。

您的JMS隊列的主要要求是可羣集化。 JMS服務器本身是系統中的單點故障。丟失了JMS服務器,並且您的系統幾乎死在水中,因此您需要能夠對服務器進行集羣,並讓消費者和生產者適當地處理故障切換。同樣,這是JMS服務器特有的,大部分都是這樣做的,但它在JMS世界中相當常見。

由於前端服務器是從REST請求的同步世界到後端處理器的異步世界的橋樑,因此前端變得棘手一點。 REST請求遵循典型的RPC模式,即從套接字使用請求負載,保持連接處於打開狀態,處理結果以及將結果傳回原始套接字。爲了說明這一點,你應該看看處理Servlet 3.0的異步Servlet,它可以在Tomcat 7,最新的Jetty(不知道是什麼版本),Glassfish 3.x等等中提供。

在這種情況下,您要做的是在請求到達時,使用HttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response)將名義上同步的Servlet調用轉換爲異步調用。

這將返回AsynchronousContext,一旦開始,允許服務器以釋放處理線程。然後你做幾件事情。

  1. 從請求中提取的參數。
  2. 爲請求創建一個唯一的ID。
  3. 從您的參數創建一個新的後端請求負載。
  4. 將ID與AsyncContext關聯,並保留上下文(例如將其放入應用程序範圍的Map中)。
  5. 將後端請求提交給JMS隊列。

在這一點上,初始化處理完成,你只需從doGet(或服務,或其他)返回。由於您尚未調用AsyncContext.complete(),因此服務器不會關閉與服務器的連接。由於您通過ID在地圖中存儲了AsyncContext存儲,因此暫時保持安全。

現在,當提交該請求到JMS隊列,它包含:請求的ID(您已產生),用於請求任何參數,和實際的服務器發出請求的識別。最後一點很重要,因爲處理結果需要返回到原點。源由請求標識和服務器標識標識。

當您的前端服務器啓動時,它也開始一個線程誰的工作是聽一個JMS響應隊列。當它建立它的JMS連接時,它可以設置一個過濾器,比如「只給我一個ABC123的ServerID的消息」。或者,您可以爲每個前端服務器創建一個唯一隊列,後端服務器使用該服務器ID來確定返回該答覆的隊列。

當後端處理器使用該消息,他們採取請求ID和參數,執行的工作,然後取結果,並把它們到JMS響應隊列。當它將結果返回時,它將添加始發ServerID和原始請求ID作爲消息的屬性。

所以,如果你得到了請求,最初是爲前端服務器ABC123,後端處理器將處理結果反饋給該服務器。然後,該偵聽器線程會在收到消息時收到通知。偵聽器線程任務是將該消息放入前端服務器內部的隊列中。

此內部隊列由線程池支持,線程池的任務是將請求負載發送回原始連接。它通過從消息中提取原始請求標識,從前面討論的內部映射中查找AsyncContext,然後將結果發送到與AsyncContext關聯的HttpServletResponse來完成此操作。最後,它調用AsyncContext.complete()(或類似的方法)告訴服務器你已經完成並允許它釋放連接。

對於持家,你應該有前端服務器誰的工作是當請求已經在地圖上已經等待了太久,以檢測在另一個線程。部分原始消息應該是請求開始的時間。該線程可以每秒喚醒一次,掃描地圖尋找請求,並且對於那些已經存在太長時間(比如說30秒)的任何​​請求,它可以將請求放到另一個內部隊列中,由一組處理程序消耗,這些處理程序用於通知客戶端請求超時。

您需要這些內部隊列,以便主要處理邏輯不會在客戶端上等待消耗數據。這可能是一個緩慢的連接或某事,所以你不想阻止所有其他待處理的請求逐一處理它們。

最後,你需要考慮,你很可能得到響應隊列的消息中,在您的內部地圖不再存在的請求。首先,請求可能已經超時,所以它不應該再存在。另一種情況是,該前端服務器可能已停止並已重新啓動,因此未決請求的內部映射將僅爲空。在這一點上,如果你發現你有一個不再存在的請求的回覆,你應該簡單地丟棄它(好吧,記錄它,然後丟棄它)。

您不能重複這些要求,有沒有真正的負載均衡器會返回給客戶端這樣的事情。如果客戶端允許您通過發佈的端點進行回調,那麼確保您可以讓另一個JMS消息處理程序發出這些請求。但這不是一種REST類型的事情,在這個討論級別的REST更多的是客戶機/服務器/ RPC。

至於哪個框架支持異步的Servlet比原始的Servlet一個更高的水平,(如新澤西州的JAX-RS或類似的東西),我不能說。我不知道哪個框架支持它。似乎這是Jersey 2.0的一項功能,目前還沒有。可能還有其他人,你必須環顧四周。另外,請不要關注Servlet 3.0。 Servlet 3.0只是單個容器中使用的技術的標準化(特別是Jetty),因此您可能希望查看Servlet 3.0以外的容器特定選項。

但是概念是相同的。最重要的是帶有過濾JMS連接的響應隊列偵聽器,AsyncContext的內部請求映射以及內部隊列和線程池,以便在應用程序中執行實際的工作。

+0

如果我必須運行5個微服務(嵌入式容器/碼頭)連接到一個隊列,以便他們正在競爭(爲了更好的吞吐量),我的客戶端庫只負責到隊列(請求/響應) – TheWhiteRabbit

+0

簡而言之,異步交互必須使用每個服務隨附的客戶端庫來隱藏,我正在尋找技術選擇對於那些介入來自服務客戶端的HTTP請求/響應的客戶端庫。 – TheWhiteRabbit

+0

我不明白。進行REST調用的客戶端不會是異步的,REST不是基於異步的系統。 –

2

如果你放鬆你的要求,它必須在Java中,你可以考慮HAProxy。它非常輕便,非常標準,並且很好地完成了許多好事(請求池/保持活動/排隊)。

但是在實施請求排隊之前要考慮兩次。除非您的流量極其突然,否則它將無能爲力,但會損害系統在負載下的性能。

假設您的系統每秒可以處理100個請求。你的HTTP服務器有一個有界的工作者線程池。請求池可以提供幫助的唯一方法是每秒接收超過100個請求。在您的工作線程池已滿後,請求開始堆積在負載平衡器池中。由於他們到達的速度比您能夠處理的速度快,隊列變得更大......並且變得更大......更大。最終,這個池也會被填滿,或者你的內存不足,負載平衡器(以及整個系統)也會崩潰。

如果您的Web服務器太忙,請開始拒絕請求並獲得一些額外的聯機容量。

如果您可以及時獲得額外的容量來處理請求,請求池確實可以提供幫助。它也可能會嚴重傷害你。在打開HTTP服務器的工作線程池前的輔助請求池之前,請仔細考慮這些後果。

0

我們使用的設計是一個REST接口接收所有的請求和將其指派到一個消息隊列(即RabbitMQ的)

然後工人收聽消息,並執行它們按照一定的規則。如果一切順利,你還是會有在MQ請求,如果你有大量的請求,只需將它添加工人...

檢查這個主題,它種了此概念的力量!

http://www.springsource.org/SpringOne2GX2012

+0

,但請求/響應模型是如何適用於此的?它是否適合同步的req/resp模型(從客戶的角度)?或者客戶端需要輪詢等待/偵聽(針對事件)的響應? – TheWhiteRabbit

+1

REST中有多個選項。即如果我呼叫該服務並且該請求尚未被處理,則服務器返回:202接受。這意味着「該請求已被接受處理,但處理尚未完成」。 – moskiteau