2016-07-10 570 views
-1

我們有一個常見的日誌記錄問題,我們做3種類型的日誌記錄(讓我們說:跟蹤,審計,計數)是我們記錄的3個方面。我們同時從REST Web服務中運行的代碼爲每個請求提供所有請求。生產者 - 消費者多個生產者多個隊列單個消費者

對於每個請求,我們會對每個日誌記錄區域進行多次調用。比方說,平均每個Web請求約100次日誌調用,一些跟蹤,一些審計,一些計數器,總共100次調用,每個Web請求。每個日誌記錄調用都會將一些數據寫入某個存儲以供稍後處理[不重要的是該存儲是什麼,但將其存入該存儲肯定是I/O綁定的 - (實際上它是一個天藍色的隊列,因此它是跨網間的HTTP調用) ]。

我們的問題是,記錄任何信息(3個區域中的任何一個)的行爲會使服務請求線程太長而無法寫入所有日誌接收器100次。我們已經測量了請求處理時間花費在記錄接收器上的時間的四分之三。所以,你可以看到我們需要顯着優化!

我們想加快速度,並從日誌寫入中釋放請求線程。我們希望使用另一個線程在後臺按自己的步調將日誌記錄數據寫入3個日誌記錄存儲區。所以我們認爲將日誌記錄排隊寫入內存隊列中,以便日誌寫入線程可以並行處理它們併發出多個請求,這是一條路。

Stephen Cleary的書「C#Cookbook中的併發」(fab參考)指出,在這種情況下使用像BlockingCollection<T>這樣的阻塞集合是理想的。因此,一次一個線程可以生成數據(並寫入內存隊列),另一個線程可以使用內存隊列中的數據。似乎這對我們的情況是理想的。然而,在我們的例子中,因爲我們運行在ASP.NET主機中,並且線程池線程對於Web服務器非常寶貴(爲了可伸縮性),我們只希望有一個線程專用於[消費]全部3個日誌隊列。讓所有其他線程處理入站Web請求,並[生成]記錄數據。

所以問題變爲:如何使用的BlockingCollection<T> 3個實例(每個類型的日誌記錄),並支持:

  1. 任何用於產生數據的生產者[呼入web請求線程]的
  2. 一個單獨的(專用)日誌線程使用者,可以持續有效地使用所有三個隊列。

任何人都可以想到一個設計模式,可以在這裏工作嗎?對我們來說缺少的一件事是消費者如何有效地清空所有三個隊列,而不會阻塞任何隊列,並且不斷處理所有三個隊列。

+0

你可以有一個'BlockingCollection '其中'T'是一些自定義的類,可以包含任何三種類型。您可以在此類型中使用枚舉來檢測此類真正擁有哪種類型。 –

+0

謝謝,但我沒有看到你的回答,我們如何處理所有三個'BlockingCollection '隊列與同一個消費者線程,連續不等待任何一個隊列。 –

+0

我建議你有一個'BlockingCollection',而不是三個。 –

回答

1

以下是我在類似場景中成功使用的模式。

首先,我通常使用ConcurrentQueue<T>,因爲沒有任何情況需要阻止。也許這將取決於您的日誌記錄的數量。該隊列包含在Buffer類中。它可以包含多個隊列。

所有請求線程都會將項目放入隊列中。在同一個線程上,Buffer正在根據最大大小檢查數量,如果它確定需要刷新緩衝區,它將在單獨的線程上執行此操作。還有一個定時器,即使緩衝區未滿,緩衝區也會被刷新。無論哪種情況,都需要進行檢查以確保緩衝區尚未刷新。

結果是,除非緩衝區是永久填滿的,否則不需要是始終專用於日誌的線程,當沒有消息時阻塞。

您還可以通過批量發送消息而不是單獨發送消息來提高性能。您可以通過發送一個包含50-100條消息的「消息」來消除大量開銷,而不是單獨發送每條消息。

+0

感謝@Scott,這裏有一些很好的想法,以及您建議的一些優化在我們的場景中有一定意義。我特別喜歡增加超時以及最大閾值。你是否建議我們使用Task.Run()踢日誌線程(從請求線程)? –

+1

是的。我有一個通用的實現,但我找不到它,所以我再次寫它。這個想法是一個通用的'接口','BufferedSink '是一個實現。這又包含它寫入的另一個「ISink 」。它還依賴於包含最大緩衝區大小,定時器間隔的'IBufferedSinkSettings'接口,並且還可以指定對內部緩衝區的刷新是否應該同步。 –