原文是發表在文章中,剛看了下文章主要用于轉載,是以在随筆中重新釋出一下。
正如前文中《海量并發下充電業務優化實踐》所述,在充電過程中由于涉及到大量的實時資料處理,随着裝置規模的擴大,各個節點和服務均感受到較大的壓力。為了解決這個情況,文中探讨記憶體緩存元件的使用及相關的過程優化,預計優化後對于Redis的調用頻率可以下降50%以上,極大緩解目前Redis的服務壓力的同時為未來的系統規模增長打下良好基礎。
充電過程中設計的實時資料主要有四類:遙信、遙測、電量和BMS資料。這四類資料在充電過程中持續上傳直到充電結束。雲端在終端充電過程中的處理壓力也是由這些實時資料引起的。
由于目前實時資料處理過程的無狀态性和通訊叢集與實時資料處理叢集之間負載均衡的存在(圖1),無法确定由哪個節點來處理哪些終端的實時資料,在此場景下隻能依賴中心化的緩存服務,各個處理節點通過中心化的緩存服務來擷取資料恢複上下文。

圖1 充電和通訊服務部署圖
随着終端規模的擴大,雖然實時資料處理叢集的性能問題可以通過水準擴充來解決,但是卻給中心化的緩存和資料庫服務帶來了較大壓力。此時需要對于充電業務的實時資料處理流程進行優化(圖2)。優化從兩方面入手:
引入消息隊列來抗擊實時資料流量的突然變化,并且将處理節點和終端的對應關系相對固定;
改變實時資料處理節點的邏輯,由無狀态的服務改為帶狀态和上下文的處理節點。
其中第1點是其他文章的主題,本文中主要涉及到第2點内容。
圖2 優化後充電和通訊服務部署圖
是以在目前的程式架構下造成通路Redis和資料庫較多的問題。平時對于Redis的每分鐘調用次數(TPM)平均值為一百萬次(月均值),遇到裝置繁忙或者網絡波動時,峰值有可能到達兩百萬次的量級,對于Redis的性能産生了很大的壓力,而且也給整個架構帶來一些隐患。
圖3 Redis單日通路量(TPM_Pool是指各個連接配接池的每分鐘調用數)
對充電業務的優化改造方案計劃充分利用現有計算資源,增加記憶體緩存的使用力度,緩解Redis和資料庫的壓力。
使用記憶體緩存需要考慮的有如下三個方面:
控制記憶體占用的機制,不至于耗盡計算資源
記憶體緩存需要有多樣的逾時機制,滿足複雜的業務需求
記憶體緩存跟主緩存之間的同步機制
基于上面一些考慮,在公共技術部門的幫助下确定使用MemoryCache作為記憶體緩存改造的主要元件。
MemoryCache類是.Net 4.0之後出現的,其命名空間是System.Runtime.Caching(位于 System.Runtime.Caching.dll中)。MemoryCache繼承自ObjectCache并實作了IEnumerable和IDisposable接口。
MemoryCache的構造函數有兩個:
<code>MemoryCache(String, NameValueCollection)</code>
<code>MemoryCache(String, NameValueCollection, Boolean)</code>
使用構造函數可以構造出非預設的緩存執行個體并可以自定義部分屬性。
MemoryCache類有7個屬性:
名稱
描述
CacheMemoryLimit
擷取計算機上緩存可使用的記憶體量(以位元組為機關)。
Default
擷取對預設 MemoryCache 執行個體的引用。
DefaultCacheCapabilities
擷取緩存提供的功能的說明。
Item[String]
通過使用 MemoryCache 類的執行個體的預設索引器屬性,擷取或設定緩存中的值。
Name
擷取緩存的名稱。
PhysicalMemoryLimit
擷取緩存可使用的實體記憶體的百分比。
PollingInterval
擷取在緩存更新其記憶體統計資訊之前需等待的最大時間量。
MemoryCache的屬性都是隻讀屬性,設定這些屬性可以通過程式配置檔案中的<code><memoryCache></code>配置節進行配置。配置執行個體:
注意上文中配置節的name屬性值為<code>Default</code>,如果寫其他名字無法設定MemoryCache.Default的相關屬性,隻能使用MemoryCache的構造函數構造對應名稱的執行個體來使用。
1、Add方法
MemoryCache的Add方法有多個重載,該方法的用處是緩存項插入MemoryCache的執行個體中。
2、AddOrGetExisting方法
MemoryCache的AddOrGetExisting方法有多個重載,該方法的用處是緩存項插入MemoryCache的執行個體中,如果該緩存的key值已經存在,則傳回該key值對應的緩存項。
3、Contains方法
該方法用于檢查MemoryCache執行個體中是否存在某個key值
4、CreateCacheEntryChangeMonitor方法
建立 CacheEntryChangeMonitor 執行個體。此更改螢幕用于監視中指定的緩存條目 keys 集合項更改時觸發事件。
5、Get方法
傳回某個key值對應的緩存項内容的引用。
6、GetCacheItem方法
從緩存中傳回CacheItem執行個體形式的指定項。
7、GetEnumerator方法
建立緩存項的枚舉器,用于循環通路緩存項的集合。
8、GetValues方法
該方法有多個重載,用于批量擷取一組key值對應的緩存項。
9、Remove方法
該方法有多個重載,用于從緩存中删除緩存項。
10、Set方法
如果key值存在則更新緩存項,如果不存在則插入緩存項。
11、Trim方法
從緩存中删除總數百分比的緩存項。删除規則參考MemoryCache.Trim 方法 (Int32)
在前文中提到了對于記憶體緩存的三個基本要求,下面看MemoryCache如何滿足這三個方面的要求。
1、控制記憶體占用
可以通過在配置檔案中配置<code>cacheMemoryLimitMegabytes</code>和<code>physicalMemoryLimitPercentage</code>屬性分别對于MemoryCache的執行個體占用記憶體的絕對值和實體記憶體相對值進行限制。
2、緩存項失效
在MemoryCache的執行個體增加緩存項時可以指定緩存項政策,即CacheItemPolicy類對象。該類中有兩個屬性SlidingExpiration和SlidingExpiration:
AbsoluteExpiration:絕對逾時時間
SlidingExpiration:滑動逾時時間,即多少時間未使用即逾時
3、跟主緩存之間的同步機制
在指定緩存項政策時還可以在其ChangeMonitors屬性中增加螢幕,實作監視資料源變化的功能。這些螢幕都是派生自<code>ChangeMonitor</code>抽象類,目前在.Net中已經實作的螢幕有四種:
CacheEntryChangeMonitor
FileChangeMonitor
HostFileChangeMonitor
SqlChangeMonitor
如果有必要的話,可以根據業務時間實作自定義的螢幕實作記憶體緩存和主緩存之間的同步機制。
另外,根據MSDN的資料,MemoryCache的操作是線程安全的,這一點在處理充電實時資料的時候非常重要。
記憶體緩存是Redis資料和部分資料庫資料在記憶體中的映射。每個處理節點的記憶體緩存隻是Redis中資料的子集(圖4)。為了使用友善,緩存資料在記憶體中的資料類型和資料結構跟Redis中的資料組織方式基本一緻。
圖4 記憶體緩存和Redis資料集的關系
緩存項在擷取時如果擷取不到則從Redis中進行恢複,否則直接使用MemoryCache中的資料;MemoryCache緩存項設定後(比如調用Add或者Set方法)如果發現資料有變化則同步設定Redis的值,保持兩者的資料同步。對于資料的逾時時間則根據不同類型資料上傳頻率來确定,逾時自動清理,有需要時再從Redis中恢複資料。
使用MemoryCache将緩存放在記憶體中後,不但可以顯著減少對于Redis資料的查詢。而且隻在終端資料變化需要更新時,将資料同步到Redis中也可以減少對于Redis的更新頻率。預計改造後對于Redis的查詢操作能夠減少80%`90%,對于Redis的設定操作可以減少60%~70%。
使用記憶體緩存對于原業務流程進行優化改造,看起來并不複雜,但是引入記憶體緩存後帶來的不僅僅隻是對于緩存存儲方式的改變,而是牽扯到一系列相關的流程的變化,包括引入消息隊列,合理配置設定處理接口的負載,多級緩存之間的同步機制等等。這些業務流程的變化對于充電穩定性的影響非常大,在系統運作過程中改變業務流程,猶如在給行駛在高速路上的汽車更換輪胎,如何保證不同流程之間的切換也是需要在設計和部署時需要詳細考慮的問題。
對于實時資料處理系統而言,有多種多樣的架構設計,但是沒有一種架構是普适性的。各種架構都有自己的局限性。随着規模的擴大,需要不斷地優化架構設計,平衡系統各個子產品之間的負載,深入挖掘系統整體性能。正如上文所言,為了解決處理節點的高負載而選擇了無狀态的處理機制,規模擴大後為了降低中心化的緩存服務(Redis)的壓力,又有必要引入記憶體緩存,緩解Redis的壓力,優化處理流程,提高系統内部子產品互動的費效比。通過一系列的優化改造,在不增加伺服器的情況下,系統整體穩定性和處理性能又會有較大提升,為未來發展打下良好基礎。
原文連結:http://www.cnblogs.com/zhu-wj/p/7461104.html
MemoryCache 類
ChangeMonitor 類