c#滑窗快取
- 2019 年 10 月 3 日
- 筆記
前言
在大數據時代,軟體系統需要具備處理海量數據的能力,同時也更加依賴於系統強大的存儲能力與數據響應能力。各種大數據的工具如雨後春筍般孕育而生,這對於系統來說是極大的利好。但在後端採用分散式、雲存儲和虛擬化等技術大刀闊斧地解決大部分存儲問題後,仍然不足以滿足所有的業務需求。對於以用戶為終點的軟體系統來說,無論後台多麼強大都難以避免有一部分數據流向終端,面向用戶。為了應對這最後一公里的通勤問題,我們得在終端快取部分數據來提高系統的響應效率。另外一方面,受限於用戶終端的機器性能,快取大量的數據反而會降低系統響應速度,甚至讓系統崩潰。為此,我們需要一個根據系統當前狀態動態調整最急需的數據的快取器,滑窗快取是一個很不錯的選擇。最終,我們找到了SlidingWindowCache,一個基於 .NET standard 實現的滑窗快取。
SlidingWindowCache 簡介
SlidingWindowCache 基於鍵值對快取,可以快取以特定序列序列組織的數據,比如時間序列數據。其本身帶有預先快取的能力,當系統狀態滿足預設條件後將自動快取數據。每次自動快取的量可自行配置。當快取超出窗口後即被視為無用數據,會被自動釋放。同樣的,快取窗口大小可進行配置。
作為 key/value 快取,該快取的 value 可以是任意類型的數據。但為了滿足有序組織,目前的 key 只支援 int、long、float 和 double 四種類型。對於時間序列數據來說,可以將時間轉化為 long 作為 key 使用。後面將以 DataTime 轉為 Ticks 為例進行演示(事實上轉為時間戳更具有通用性),直接展示使用常式更加容易說明問題。
SlidingWindowCache 使用
SlidingWindowCache 配置
SlidingWindowCache 的絕大部分配置都在ISlidingWindowConfig<TKey>
介面中定義,目前具有以下重要的配置:
TKey StartPoint { get; set; }
—— 快取序列的起點TKey EndPoint { get; set; }
—— 快取序列的終點TKey PerLoadSize { get; set; }
—— 每次快取請求的大小。在自動快取中,將自動向數據源請求數據TKey TotalLoadSize { get; set; }
—— 總共載入的數據大小。在自動快取中,快取數據到達該閾值則停止自動快取TKey TotalCacheSize { get; set; }
—— 總共快取的數據大小。即滑動窗口的大小,超出該窗口的數據被自動釋放int LoadParallelLimit { get; set; }
—— 自動載入數據時並發量閾值float LoadTriggerFrequency { get; set; }
—— 載入觸發頻率。為 1 時,只要狀態一改變,立即觸發自動載入。float RemoveTriggerFrequency { get; set; }
—— 移除觸發頻率。為 1 時,只要狀態一改變,立即觸發自動移除。float ForwardAndBackwardScale { get; set; }
—— 前後比例(TKey 大端為前,習慣了以時間箭頭為前)。以快取大小來說,當前 TKey 作為分割點。
我們可以用形象的比喻來做進一步的解釋。StartPoint
和EndPoint
限定了窗體能滑動的邊界。TotalCacheSize
限定了窗體的大小,在某種意義上來說,該窗體是殘破不堪的,因其並未隨時擁有所有的數據。它等待著修補匠進行破窗修補(數據源載入)。TotalLoadSize
限定了每個狀態生命周期中修補破窗的總大小,也就是自動請求數據量的大小。PerLoadSize
則為每次修補的大小,即每次向數據源請求的數據量。LoadParallelLimit
可以理解為可以同時工作的修補匠的最多人數。LoadTriggerFrequency
則可以理解為當狀態變更時,修補匠的出勤率。
SlidingWindowCache 快取
SlidingWindowCache 當前只提供少數重要的功能,全在ISlidingWindowCache<TKey, TData>
介面中進行定義。
// 當前點,用來標記快取狀態 TKey CurrentPoint { get; set; } // 當前快取的key的個數 int Count { get; } // 從快取中獲取數據 Task<IEnumerable<TData>> GetCacheData(TKey start, TKey end, Func<TData, TKey> keyOfTData); // 載入源數據的委託(必須進行賦值) Func<TKey, TKey, CancellationToken, Task<IEnumerable<TData>>> DataSourceDelegate { get; set; } // 自動載入任務狀態報告事件 event EventHandler<TaskStatus> OnDataAutoLoaderStatusChanged;
SlidingWindowCache 具體使用
下面以快取時間序列數據為例做一具體使用介紹
// 自定義數據模擬類 public class DataModel { private static readonly Lazy<DataModel> _lazy = new Lazy<DataModel>(() => new DataModel()); public static DataModel Instance => _lazy.Value; public long Point { get; set; } // 模擬大量數據,佔用記憶體 public long[] data = new long[1000]; // 模擬伺服器數據請求 public Task<IEnumerable<DataModel>> LoadDataFromSource(long s, long e, CancellationToken cancellationToken) { return Task.Run(() => { var rd = new Random(); // 模擬遠程訪問數據時可能的延遲 Task.Delay(rd.Next(50, 400), cancellationToken).Wait(cancellationToken); var diff = (int)(e - s); var count = diff > 100 ? 100 : diff; var result = Enumerable.Range(0, count) .Select(t => new DataModel { Point = s + rd.Next(diff) }) .OrderBy(t => t.Point) .ToList(); return (IEnumerable<DataModel>)result; }, cancellationToken); } } // 滑窗配置 var config = new SlidingWindowConfig<long> { PerLoadSize = new TimeSpan(0, 2, 0).Ticks, StartPoint = new DateTime(2019, 1, 1).Ticks, EndPoint = new DateTime(2019, 2, 1).Ticks, TotalLoadSize = new TimeSpan(0, 30, 0).Ticks, TotalCacheSize = new TimeSpan(7, 0, 0).Ticks }; // 實例化快取器 var cache = new SlidingWindowCache<long, DataModel>(config) { // 提供獲取源數據的委託 DataSourceDelegate = DataModel.Instance.LoadDataFromSource, CurrentPoint = config.StartPoint }; // 獲取2019-1-1 0:1:39至2019-1-1 0:2:0之間的數據 // lamda表達式t => t.Point提供快取類型DataModel中的TKey的獲取方法,用於數據過濾 var data = await cache.GetCacheData( new DateTime(2019, 1, 1, 0, 1, 39).Ticks, new DateTime(2019, 1, 1, 0, 2, 0).Ticks, t => t.Point);
上述例子中,我們可能查看的數據總範圍為:2019-1-1 至 2019-2-1,總共為一個月的數據量。而終端機器允許快取的數據量最多只能有 7 個小時。為了減少伺服器壓力,每次請求兩分鐘的數據量,預先自動快取為半小時的數據量。在某一次數據獲取中(2019-1-1 0:1:39 至 2019-1-1 0:2:0),獲取 21 秒的數據,lamda 將提供自動篩選的憑據。隨著cache.CurrentPoint
逐漸增加(這裡模擬時間增加),可以看到記憶體的大致變化趨勢:
隨著時間增加,記憶體使用量首先會持續增加,當達到設定閾值後便自動下降。此後,便在某一窗口之間重複震蕩。符合滑動窗口快取的預期。
後記
SlidingWindowCache 已經投入實際使用環境中,每次請求的量達到千級甚至萬級,總共快取的量達到百萬級別(後端使用 Hbase 作為最終的存儲方案,前端以 SlidingWindowCache 作為最前的快取方案)。
SlidingWindowCache 項目剛剛起步,歡迎提出改進意見。