基於.net的通用內存緩存模型組件

    談到緩存,咱們天然而然就會想到緩存的好處,好比:web

  •     下降高併發數據讀取的系統壓力:靜態數據訪問、動態數據訪問
  •     存儲預處理數據,提高系統響應速度和TPS
  •     下降高併發數據寫入的系統壓力
  •     提高系統可用性,後臺宕機後,系統還存在可用的機會

    緩存技術一直是優化程序性能的一個重要手段,在互聯網技術體系中,也不例外。可是在分佈式架構下,你們開始更多的使用分佈式緩存,好比Redis、MemcacheD等等,對進程內的內存緩存使用的愈來愈少。其主要緣由無外乎幾點:緩存

     一是,數據不能作到強一致性,程序內存數據緩存同步的週期相對分佈緩存更慢一些。微信

     二是,須要對緩存的各類同步策略進行封裝,並控制同步時機。進程內緩存的使用比分佈式緩存的使用具備更高的技術門檻。沒有分佈緩存使用簡單。    架構

    雖然分佈式緩存具備很是多很好的特性,可是當徹底拋棄了程序內存緩存後,分佈式緩存將會被濫用,應用程序甚至過分的依賴分佈式緩存。筆者認爲,任何一種技術的濫用,都將可能致使系統架構在健壯性上存在缺陷。分佈式緩存雖然很好用,性能也不錯,可是與進程內存緩存比起來,性能仍是差了好多個數量級。要想把系統的性能作到極致,僅僅依賴Redis等分佈式緩存還不不夠的,還須要充分利用進程內存緩存。併發

Image

    緩存技術,從出現到如今,總結來講,已有四個階段的發展:本地緩存、分佈式緩存、彈性緩存平臺,彈性應用平臺。本地緩存的特色是數據存儲在應用代碼所在內存空間,可提供快速的數據訪問,納秒級性能。缺點也很明顯,數據沒法分佈式共享,無容錯處理。分佈式緩存的特色是數據在固定數目的集羣節點間分佈存儲,緩存容量可擴展(靜態擴展),可是擴展過程當中需大量配置,無容錯機制。彈性緩存平臺的特性是數據在集羣節點間分佈存儲,基於冗餘機制實現高可用性。其優勢是可動態擴展,具備容錯能力,可是複製備份會對系統性能形成必定影響。彈性應用平臺的特色是彈性緩存與代碼執行的組合體,將業務邏輯代碼轉移到數據所在節點執行,極大地下降數據傳輸開銷,提高系統性能。縱觀整個緩存技術的發展,經歷了從分散到集中,又到集中並分散的一個過程。彈性應用平臺做爲最終的緩存解決方案,已經不只僅停留在緩存技術自己,而是更多的考慮瞭如何更好的與業務代碼無縫集成,並提供進程內存級別的性能。dom

     基於此,咱們規劃設計了一個通用的內存緩存組件。經過此組件,能夠實現各類內存數據的緩存,以及緩存數據同步等,並提供了分佈式緩存數據同步到進程內存的方案。此組件與傳統的緩存組件有很大的不一樣,它是對進程內存緩存的使用和同步作了抽象和總結,直接提供了一套模型。上文也提到,使用進程內存緩存最大的挑戰在與數據同步,那麼,咱們先看一下影響進程內存緩存同步的一些因素。經過下圖,咱們看到在同步策略、同步時機、同步模式上都有不少選擇。進程內存緩存的使用,其實就是下面三個維度組合處理的一個結果。分佈式

Image

    在實際的業務中,同步策略更多的會基於時間、數據版本或者時間來作,而後選擇合適的同步時機和模式來執行數據同步。因此,在組件的封裝上,咱們支持了三種應用模型:ide

  • 對緩存數據標記有效期,過時自動清理
  • 緩存數據時,同時緩存數據版本,定時校驗或實時校驗數據版本,發現版本不一致時清理或從新同步緩存數據
  • 緩存數據並訂閱數據變化通知,當收到變化通知後,更新緩存數據

模型一:對緩存數據標記有效期,過時自動清理高併發

     此模型主要適用於, 好比:字符串資源信息查詢、App充電地圖數據、App最新動態、高頻率日誌記錄、配置中心數據的緩存等等。性能

  • 數據實時性要求很低,訪問頻率很高,變化頻率較小的數據查詢

  • 訪問頻率很高,查詢參數基本一致的數據查詢

  • 訪問頻率很高,容許丟失的輔助信息寫入

    代碼示例:

//nuget:Teld.Core
//引用:Teld.Core.Cache.ServiceEx.dll
 

 

//建立一個帶過時時間的本地內存容器
using(var container = LocalCacheService.CreateExpirationContainer("TTP-Cache-CFG"))
{
    //向容器中增長項目,3秒鐘的有效期
    container.Add("Name", "張三", new TimeSpan(0, 0, 3));
    //想容器中增長項目,永久有效
    container.Add("Address", "鑫盛大廈1號樓12層北特來電");
}

模型二:緩存數據時,同時緩存數據版本,定時校驗或實時校驗數據版本,發現版本不一致時清理或從新同步緩存數據

     此模型主要適用於, 好比:幫助查詢等。

  • 數據實時性要求不高,訪問頻率很高的數據查詢
  • 訪問頻率很高,查詢參數基本一致的數據查詢

     代碼示例1: 定時同步,每分鐘執行一次版本同步

static voidMain(string[] args)

{

   //建立緩存數據同步代理

   CacheValidateEventHandler<UserInfo> handler = new CacheValidateEventHandler<UserInfo>(SyncUserData);
   //建立一個帶版本校驗的本地內存緩存容器,每隔60s,自動進行一次數據全量同步
   using(varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.Timing, SyncModel.All, 60))

   {

       container.SyncFailed += Container_SyncFailed;   //數據同步失敗,事件通知

       container.SyncData();   //當即執行一次數據同步

 

       var user = container.Get("Name");                     //從緩存中獲取數據

       Console.WriteLine(JsonConvert.SerializeObject(user));

   }         

   Console.ReadLine();

}
 

//數據同步,返回全量或者增量數據,並返回數據的最新版本

static Dictionary<string, UserInfo> SyncUserData(CacheValidateEventArgs e,out string newVersion)

{

   //經過e.Version獲取上次同步的數據數據

   Dictionary<string, UserInfo> result = new Dictionary<string, Cache.UserInfo>();

   Random r = new Random(DateTime.Now.Second);
   result.Add("Name", new Cache.UserInfo() { Name = "Name", Age =r.Next(1,20) , IsMale = true , LastModifyTime = DateTime.Now});

   newVersion = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");

 

   return result;

}

    代碼示例2: 調用緩存Get方法時,自動同步緩存    

static void Main (string[] args)

{ //建立緩存數據同步代理

     CacheValidateEventHandler<UserInfo> handler = new CacheValidateEventHandler<UserInfo>(SyncUserData);

     //建立一個帶版本校驗的本地內存緩存容器

     varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.GetCheck, SyncModel.All);

     container.SyncFailed += Container_SyncFailed; //數據同步失敗,事件通知

     var user = container.Get("Name"); //從緩存數據中獲取數據

     Console.WriteLine(JsonConvert.SerializeObject(user));

     Console.ReadLine();

}

模型三:緩存數據並訂閱數據變化通知,當收到變化通知後,更新緩存數據

    此模型主要適用於, 好比:電站狀態緩存等。

    • 數據實時性要求比較高,訪問頻率很高的數據查詢

    代碼示例1: 帶事件更新通知的緩存    

public void GetString_Invoke()

{

//建立一個帶MQ變化通知的本地內存緩存容器

using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",

                 (CacheValidateEventArgs e, out string newVersion) =>

                 {

                     newVersion = Guid.NewGuid().ToString();

return BuildStringData();

                 }, SyncModel.All, 1))

            {

             container.SyncData(); //爲容器初始化數據

             var data = container.Get("lastModifytime");  //獲取數據項

             Assert.IsNotNull(data);

             var data1 = container.Get("lastModifytime");

             Assert.AreEqual(data, data1);

             //發送數據項的更新通知事件

             LocalCacheService.SendDataChangedEvent(container.Name, "lastModifytime");

             Thread.Sleep(5000);

             var data2 = container.Get("lastModifytime");

             Assert.AreNotEqual(data2, data);

            }

}

    代碼示例2:數據刪除的事件通知

public void GetString_Delete()
        {
            //建立一個帶MQ變化通知的本地內存緩存容器
            using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",
                 (CacheValidateEventArgs e, out string newVersion) =>
                 {
                     newVersion = Guid.NewGuid().ToString();
                     return BuildStringData();
                 }, SyncModel.All, 1))
            {
                container.SyncData();                                                   //爲容器初始化數據
                var data = container.Get("lastModifytime");           //獲取數據項
                Assert.IsNotNull(data);
                var data1 = container.Get("lastModifytime");
                Assert.AreEqual(data, data1);
                LocalCacheService.SendDataChangedEvent(container.Name, "lastModifytime", EventType.Delete);   //發送數據項的刪除通知事件
                Thread.Sleep(5000);
                var data2 = container.Get("lastModifytime");
                Assert.IsNull(data2);
            }
        }

    以上是此緩存組件應用的三種模型。三種模型,可經過 LocalCacheService 建立。其代碼以下:

 public class LocalCacheService

    {
        /// <summary>
        /// 建立過時自動清理的本地內存緩存容器
        /// </summary>
        /// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>
        /// <param name="limitSize">限制使用的內存大小(M)</param>
        public static IExpirationCacheContainer CreateExpirationContainer(string key,long? limitSize =null)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能爲空!", nameof(key));
            }
            return new InMemoryExpirationContainer(key, limitSize);
        }
        /// <summary>
        /// 建立基於版本比較的本地內存緩存容器
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>
        /// <param name="handler">基於版本比較的數據處理器</param>
        /// <param name="syncTime">同步時機</param>
        /// <param name="model">同步模式</param>
        /// <param name="syncTimeMin">同步時機爲Timing時,同步時間間隔</param>
        /// <returns></returns>
        public static IVersionCacheContainer<T> CreateVersionContainer<T>(string key, CacheValidateEventHandler<T> handler, SyncTime syncTime = SyncTime.Invoke,SyncModel model = SyncModel.All,int syncTimeSec=180) where T : class
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能爲空!", nameof(key));
            }
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            if (syncTimeSec == 0)
                syncTimeSec = 180;
            return new InMemoryVersionCacheContainer<T>(key, handler, syncTime, model, TimeSpan.FromSeconds(syncTimeSec));
        }
        /// <summary>
        /// 建立基於事件通知的本地內存緩存容器
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>
        /// <param name="handler">基於版本比較的數據處理器</param>
        /// <param name="model">同步模式</param>
        /// <param name="syncTimeMin">同步時機爲Timing時,同步時間間隔</param>
        /// <returns></returns>
        public static IVersionCacheContainer<T> CreateEventContainer<T>(string key,CacheValidateEventHandler<T> handler, SyncModel model = SyncModel.All, int syncTimeSec = 180) where T : class
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能爲空!", nameof(key));
            }
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            if (syncTimeSec == 0)
                syncTimeSec = 180;
            return new InMemoryEventCacheContainer<T>(key,model,handler, TimeSpan.FromSeconds(syncTimeSec));
        } 
    }

  同步模式和同步時機的定義以下:

/// <summary>

/// 同步模式
/// </summary>
public enum SyncModel : int
{
    All, //全量同步,清楚歷史緩存信息,從新插入
    Increase, //增量同步,同步變化部分,不對歷史緩存數據清理
    Clear //僅清理歷史數據
}

/// <summary>
/// 同步時機
/// </summary>
public enum SyncTime : int
{
    Invoke, //調用方執行SyncData方法主動同步
    Timing, //定時同步
    GetCheck //Get方法是自動同步
}

  以上是咱們在進程內存組件的一些實踐心得,更多技術細節,歡迎同窗們來電溝通。微信號:vveiliang

相關文章
相關標籤/搜索