2013-10-02 96 views
3

服務器本質上是一個項目隊列,而客戶端充當這些項目的生產者和/或消費者。Java中的多線程服務器

服務器必須:

  • 傾聽放/取請求,並相應地處理它們 - 這通常不會花太長時間,它包括:
    1. 解析短串;
    2. HashMap.get;
    3. 獲取鎖定;
    4. A PriorityQueue.pollPriorityQueue.offer;
  • 通知所有項目活動的每一個客戶端,只要有可能,讓每一個客戶有一個實時鑑於這是怎麼回事的。

設置此功能的最簡單的方法,是由具有螺紋accept荷蘭國際集團的客戶,然後爲每個客戶端創建兩個線程:

  • 一個處理該InputStream,哪些塊等待請求;
  • 而另一個處理OutputStream,它監聽隊列上的事件,並將信息發送到客戶端。

當然,這是不可擴展的,並且爲每個客戶端擁有兩個線程似乎是浪費。

我也想過使用一個線程,這將

  • 設置套接字超時約1read秒;
  • 繼續發送每個新事件給客戶端,如果read超時或處理請求後;
  • 循環這兩個動作。

但是,輪詢請求和事件也是浪費。

另一種方法是使用線程池,並將上述兩個操作中的每一個放在它們各自的Runnable中。然後這些可運行的對象將在Executor中彼此排隊。 這似乎也是浪費,如果不是更多。

我一直在讀somequestions,我現在好奇NIO,因爲非阻塞操作和事件驅動服務器似乎是正確的方法。

以上任何一種設計是否適合這項任務,還是應該使用NIO來解決?

就數字而言,這更是一個鍛鍊比真正的系統,所以它與數千客戶不必交易,但是,理想情況下,它應該能夠執行和規模好。

回答

1

每個客戶端的兩個線程絕對不可伸縮。

如果服務器上有M個核心,那麼實際上並不比運行M個線程要好。任何更高級別的暴露都會導致抖動,並會減少每秒執行的操作次數。

更好的設計分區可用核成ù更新器和L聽衆,其中 U + L型== M.

每個客戶端被分配(理想地與負載平衡,但是這是一個點綴),以更新器線程。每個更新事件都會多播到所有更新器線程,然後每個更新器線程都會更新其所有分配的客戶端。更新程序列表末尾的客戶端比最初更新的時間更晚,但沒有任何幫助:您只有很多硬件。

同樣,每個客戶端都分配給一個偵聽器線程,該線程處理多個偵聽器。客戶端輸入被轉儲到一個FIFO隊列中,並在偵聽器線程處理完成後立即處理。

然後,每個線程可以保持活動狀態並存儲在內存中,而客戶端數據通過系統移動。設計優雅地降級,因爲太多的客戶端意味着所有更新都變慢,作爲客戶端數量的線性函數。你提出的設計會降低速度。

現代(例如比2002年晚)的web服務器將這一切深深隱藏在實現中,因此開發人員無需對其進行管理。但它仍然是一個有用的練習。

+0

這是有道理的。每個客戶端想要線程的主要原因之一是,如果連接速度很慢,那麼只有該線程/客戶端會因此而受到影響。有一組更新線程將事件廣播到所有客戶端會遭受連接速度緩慢的影響,但我想這是無法解決的。 – afsantos

2

那麼您需要繼續使用ThreadPool,因爲您需要管理Runners !,我建議您在業務中使用impl MOA結構,因爲此客戶端連接到服務器並且服務器等待客戶端請求(數據),然後服務器將作業排隊(如果沒有可用的線程進行處理)並且直接響應指向服務器上的客戶端進程ID的長值並關閉套接字。 現在如果客戶請求已處理並準備好採取行動,該怎麼辦?所以這裏有兩種方法,好的一個是服務器向客戶端發出信號(因此客戶端需要監聽關於服務器響應的[ServerSocket])關於完成的請求。 OR客戶端會以一定的時間間隔檢查服務器並檢查進程的狀態。

+0

*「服務器信號客戶端」* - 你在這裏提出的建議,基本上是角色的倒置,不是嗎?讓客戶端監聽服務器響應。我懷疑這些要求需要很長時間才能實現。每隔一段時間就不是三路握手而不是保持插座打開的成本? – afsantos

+0

保持連接打開比在端口上偵聽更糟糕。當然是。當服務器在端口x上偵聽客戶端請求時,客戶端在端口y上偵聽響應。正如我所說,這只是一個建議。但請記住,如果客戶端打開一個端口來獲取響應,那麼客戶端應該可以訪問服務器。 – 2013-10-02 19:43:57

1

上述設計完全沒問題。這正是網絡服務器在引入nio之前的工作原理。

你有多少客戶?如果不是那麼多,不要擔心可擴展性,並儘量避免複雜的nio api。如果您確實需要擴展,請考慮使用某種抽象,例如netty。使用nio可能相當複雜,可能在不同的操作系統上工作的方式不同(一旦我遇到了可以在特定操作系統上覆制的奇怪錯誤)。

使用NIO您可以處理所有的客戶端請求的線程1-4

有時IO能勝過NIO /響應流程。

看到這個答案 - Old I/O thread per client model or NIO reactor pattern?

+0

鏈接的答案及其鏈接的文章都非常有見地。 *「上述設計是完全正確的」* - 使用此語句,您是指每個客戶端線程模型,使用定期輪詢還是'Runnable'方法,儘可能調度任務,以及(可能)限制線程 - 池大小? – afsantos

+0

如果您正在編寫ping/pong應用程序,例如web服務器,其中響應將緊跟在請求後,一個線程與SO_TIMEOUT將正常工作。或者根本不需要超時。讀取請求直到完成(請求長度可以在前幾個字節中指定),發回響應。如果您需要同時向兩個方向發送數據或從服務器推送通知 - 請爲每個客戶端提供2個線程。 – Anton

1

我設計並實施了一些生產水平的實時系統(談論毫秒級的延遲或更低,但客戶屈指可數)。恕我直言,你應該採取NIO方法。

NIO的核心基本上是select(),它允許您同時處理來自不同套接字/客戶端的輸入。之後,將事件放入適當的隊列中並/或在整個系統中廣播。那麼如何處理隊列和分配線程完全獨​​立於IO任務,並且取決於您自己的調整。另外看看ZeroMQ,它基本上應用了相同的想法;通過多個Socket模型查看他們的Poller。我相信大多數現代消息傳遞框架,包括JMS/EMS,TibcoRV,29 West LBM等(現在在Informatica下)都採用類似的方法。

+0

對於一個帶有4個線程的小系統,例如,這將通過'select'通過'select'來監聽傳入連接和客戶端輸入,然後有三個工作線程調度請求並向所有客戶端廣播,是嗎?在廣播時,如果客戶端連接速度特別慢,是否不會以負面方式影響所有後續客戶端,具體取決於客戶端迭代的順序? – afsantos

+0

當套接字準備好讀取時,它不會導致選擇器甚至會觸發SelectionKey.OP_READ。 – Anton