C#實現自由組合本地緩存、分佈式緩存和數據查詢
一、背景介紹:
我們在進行數據存儲的時候,有時候會加入本地緩存、分佈式緩存以及數據庫存儲三級的結構,當我們取值的時候經常是像下面這樣的流程:
1.先取本地緩存,如果值存在直接返回
2.本地緩存不存在,獲取分佈式緩存,存在直接返回,並更新本地緩存
3.分佈式緩存不存在,查詢數據庫,更新分佈式緩存、更新本地緩存,最後返回
但如果對於一些場景,可能隻有本地緩存、隻有分佈式緩存或者說上面三種的幾種組合,我們怎麼要應對這樣的變化,怎麼能抽象出一套方式,能夠應對各種不同數據存儲方式造成的變化。
二、設計思路:
首先我們分析一下上面這個過程的模型,可以抽象出5個方法:
- 1.GetDataFromLocalCache
- 2.GetDataFromDistributeCache
- 3.GetDataFromDB
- 4.SetDataToLocalCache
- 5.SetDataToDistributeCache
其實,不同的場景無非就是這幾個方法的組合,隻不過裡面的內容不同罷瞭,說到這裡我們應該已經有思路瞭,可以利用委托來實現。
三、詳細設計:
①定義一個類,包含上面五個方法的委托;
public class DataOperateInput<T> { public Func<T> GetDataFromLocalCache { get; set; } = null; //獲取本地緩存數據 public Func<T> GetDataFromDistributeCache { get; set; } = null; //獲取分佈式緩存數據 public Func<T> GetDataFromDb { get; set; } = null; //獲取DB數據 public Action<T> SetDataTolocalCache { get; set; } = null; //設置本地緩存數據 public Action<T> SetDataToDistributeCache { get; set; } = null; //設置分佈式緩存數據 }
②實現一個方法,組合這五個方法。
public class DataOperate { /// <summary> /// 獲取數據入口 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="input"></param> /// <returns></returns> public T GetData<T>(DataOperateInput<T> input) where T : class, new() { T result = null; //需要從本地緩存取 if (input.GetDataFromLocalCache != null) { //調用本地緩存委托方法獲取值 result = input.GetDataFromLocalCache(); if (result != null) { return result; } } //獲取值為空或者不從本地緩存獲取,調用下面的方法,從分佈式緩存和Db中獲取數據 return GetDataFromDistributeAndDB(input); } /// <summary> /// 從分佈式緩存和Db中獲取數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="input"></param> /// <returns></returns> private T GetDataFromDistributeAndDB<T>(DataOperateInput<T> input) where T : class, new() { T result = null; if (input.GetDataFromDistributeCache != null) { //從緩存中取值 result = input.GetDataFromDistributeCache(); //如果需要設置會本地緩存,那麼設置 if (result != null) { //如果設置本地緩存的委托存在,調用它設置本地緩存 input.SetDataTolocalCache?.Invoke(result); } } //獲取值為空或者不從分佈式緩存獲取,調用下面的方法,從Db中獲取數據 return GetDataFromDB(input); } /// <summary> /// 從數據庫中獲取數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="input"></param> /// <returns></returns> private T GetDataFromDB<T>(DataOperateInput<T> input) where T : class, new() { T result = null; if (input.GetDataFromDb != null) { //從DB中取值 result = input.GetDataFromDb(); //如果需要設置會分佈式緩存和本地緩存,那麼設置 if (result != null) { input.SetDataToDistributeCache?.Invoke(result); input.SetDataTolocalCache?.Invoke(result); } } return result; } }
③ 具體實現一個服務類,和各種GetData、SetData方法;
A.定義一個枚舉類,通過這個枚舉可以自由組合數據源
/// <summary> /// 數據源類別 /// </summary> [Flags] public enum DataSourceKind { /// <summary> /// 本地緩存 /// </summary> LocalCache = 1, /// <summary> /// 分佈式緩存 /// </summary> DistributeCache = 2, /// <summary> /// 數據庫 /// </summary> DataBase = 4 }
B.定義一個具體的實體類,舉例我這裡定義瞭一個User類
public class User : IUser { public long UserId { get; set; } public string Name { get; set; } public int Age { get; set; } public int Sex { get; set; } }
C.實現一個獲取用戶信息的方法
/// <summary> /// 獲取用戶數據 /// </summary> /// <param name="userId">用戶Id(可以是自己相關的業務代碼)</param> /// <param name="dataSources">數據源類型(調用方可以自己組合)</param> /// <param name="needUpdatelocal">是否需要更新本地緩存</param> /// <param name="needUpdateDistribute">是否需要更新分佈式緩存</param> /// <returns></returns> public User GetUserInfo(long userId, DataSourceKind dataSources = DataSourceKind.LocalCache , bool needUpdatelocal = false, bool needUpdateDistribute = false) { Console.WriteLine($"======數據源:{dataSources.ToString()} 是否更新本地:{needUpdatelocal} 是否更新Redis:{needUpdateDistribute}======"); //初始化一個輸入參數類 var input = new DataOperateInput<User>(); //如果包含從本地緩存取值 if (dataSources.HasFlag(DataSourceKind.LocalCache)) { input.GetDataFromLocalCache = () => { //!!這裡可以寫具體的 獲取本地緩存的處理邏輯 return GetUserFromLocalCache(userId); }; } //如果包含從分佈式緩存取值 if (dataSources.HasFlag(DataSourceKind.DistributeCache)) { input.GetDataFromDistributeCache = () => { //!!這裡可以寫具體的獲取分佈式緩存的處理邏輯 return GetUserFromRedisCache(userId); }; if (needUpdatelocal) { input.SetDataTolocalCache = (value) => { //!!這裡可以寫具體的設定本地緩存的處理邏輯 SetUserToLocalCache(value); }; } } //如果包含從數據庫緩存取值 if (dataSources.HasFlag(DataSourceKind.DataBase)) { input.GetDataFromDb = () => { //!!這裡可以寫具體的獲取數據庫數據的處理邏輯 return GetUserFromDB(userId); }; if (needUpdateDistribute) { //!!這裡可以寫具體的設定分佈式緩存的處理邏輯 input.SetDataToDistributeCache = (value) => { SetUserToRedisCache(value); }; } if (needUpdatelocal) { //!!這裡可以寫具體的設定本地緩存的處理邏輯 input.SetDataTolocalCache = (value) => { SetUserToLocalCache(value); }; } } //執行我們組合好的input var result = new DataOperate().GetData(input); Console.WriteLine("=============================================\n"); return result; }
上面的代碼描述瞭使用封裝好的GetData的方法的使用,其中有些委托的方法是需要具體實現的,這裡我沒有詳細寫。下面列出用於測試的GetUserFromLocalCache、GetUserFromRedisCache、GetUserFromDB、SetUserToLocalCache以及SetUserToRedisCache的代碼。
/// <summary> /// 從本地緩存獲取用戶信息 /// </summary> /// <param name="userId"></param> /// <returns></returns> private User GetUserFromLocalCache(long userId) { User user = null; if (userId == 1 ) { user = new User { UserId = userId, Age = 10, Name = $"BigOrange_{userId}", Sex = 1 }; } if (user == null) { Console.WriteLine($"從本地緩存取值 未查詢到 UserId={userId}"); } else { Console.WriteLine($"從本地緩存取值 UserId={user.UserId} Name={user.Name} "); } return user; } /// <summary> /// 從Redis緩存獲取用戶信息 /// </summary> /// <param name="userId"></param> /// <returns></returns> private User GetUserFromRedisCache(long userId ) { User user = null; if (userId == 1 || userId == 2 ) { user = new User { UserId = userId, Age = 10, Name = $"BigOrange_{userId}", Sex = 1 }; } if (user == null) { Console.WriteLine($"從Redis緩存取值 未查詢到 UserId={userId}"); } else { Console.WriteLine($"從Redis緩存取值 UserId={user.UserId} Name={user.Name}"); } return user; } /// <summary> /// 從DB獲取用戶信息 /// </summary> /// <param name="userId"></param> /// <returns></returns> private User GetUserFromDB(long userId) { Console.WriteLine("從數據庫中取值"); User user = null; if (userId == 1 || userId == 2 || userId == 3) { user = new User { UserId = userId, Age = 10, Name = $"BigOrange_{userId}", Sex = 1 }; } if (user == null) { Console.WriteLine($"從DB取值 未查詢到 UserId={userId}"); } else { Console.WriteLine($"從DB取值 UserId={user.UserId} Name={user.Name}"); } return user; } /// <summary> /// 設置用戶信息到本地緩存 /// </summary> /// <param name="userInfo"></param> /// <returns></returns> private bool SetUserToLocalCache(User userInfo) { Console.WriteLine($"設置值到本地緩存:useId = {userInfo.UserId}"); return true; } /// <summary> /// 設置用戶信息到Redis緩存 /// </summary> /// <param name="userInfo"></param> /// <returns></returns> private bool SetUserToRedisCache(User userInfo) { Console.WriteLine($"設置值到Redis緩存:useId = {userInfo.UserId}"); return true; }
④測試一下
根據上面的代碼,寫瞭一些測試用的條目:
static void Main(string[] args) { var userInfoService = new UserInfoService(); /* * 測試用例 數據庫中存在 User1、User2、User3 分佈式緩存 User1、User2 本地緩存 User1 */ //1.隻從本地緩存取值 userInfoService.GetUserInfo(1, DataSourceKind.LocalCache); userInfoService.GetUserInfo(2, DataSourceKind.LocalCache); //2.隻從Redis緩存取值 userInfoService.GetUserInfo(2, DataSourceKind.DistributeCache); userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache); //3.隻從DB取值 userInfoService.GetUserInfo(3, DataSourceKind.DataBase); userInfoService.GetUserInfo(4, DataSourceKind.DataBase); //4.從本地緩存和Redis取值 userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DistributeCache); //不更新到本地 userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, false); //更新到本地 userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, true); //5.從Redis和DB取值 userInfoService.GetUserInfo(2, DataSourceKind.DistributeCache | DataSourceKind.DataBase); userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, false); userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true); //6.從本地和DB取值 userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DataBase); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DataBase, false,false); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DataBase, true, false); //7.三者都使用 userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false); userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false); userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,false); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true, false); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true); userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,true); Console.ReadKey(); }
執行結果:
======數據源:LocalCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數據源:LocalCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
===================================================數據源:DistributeCache 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 UserId=2 Name=BigOrange_2
===================================================數據源:DistributeCache 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 未查詢到 UserId=3
===================================================數據源:DataBase 是否更新本地:False 是否更新Redis:False======
從DB取值 UserId=3 Name=BigOrange_3
===================================================數據源:DataBase 是否更新本地:False 是否更新Redis:False======
從DB取值 未查詢到 UserId=4
===================================================數據源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數據源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
===================================================數據源:LocalCache, DistributeCache 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
設置值到本地緩存:useId = 2
===================================================數據源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 UserId=2 Name=BigOrange_2
從DB取值 UserId=2 Name=BigOrange_2
===================================================數據源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數據源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設置值到Redis緩存:useId = 3
===================================================數據源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數據源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數據源:LocalCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設置值到本地緩存:useId = 3
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
從DB取值 UserId=2 Name=BigOrange_2
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
設置值到本地緩存:useId = 2
從DB取值 UserId=2 Name=BigOrange_2
設置值到本地緩存:useId = 2
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設置值到本地緩存:useId = 3
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設置值到Redis緩存:useId = 3
===================================================數據源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:True======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設置值到Redis緩存:useId = 3
設置值到本地緩存:useId = 3
=============================================
四、總結一下
類似上面的用戶信息,可能對於不同系統、不同性能要求,獲取方式會有所不同。
打個比方:對於一個後臺管理系統,用戶信息獲取是一個低頻操作,可能隻需要從數據庫中獲取,此時一般後臺系統不會設置本地緩存和分佈式緩存,而對於一個接口系統,可能每天有幾百萬的訪問量,此時如果隻從數據庫獲取,很難承受,所以要利用到分佈式緩存和本地緩存。層次越多那麼變化和組合也就越多,但是每個實體的存取如果都各自實現自己的方式,又比較浪費,所以如果能抽象出一套方法,隻需要告訴方法存取的方式,然後得到自己想要的數據,或許這樣是比較好的方式,而具體怎麼拿、怎麼存,還是由調用的人去給出,這樣可以應對復雜的規則。這也是為什麼要使用這麼多委托的原因,由於像上面獲取和設定User緩存的方式多種多樣,這麼做可以把具體的獲取和設置緩存的操作開放給使用者。在系統重構方面上,可以將一些通用的方法抽象出來,相對成本較低,擴展性好一些。
五、題外話
上面的代碼中對於更新數據,沒有做線程安全處理,多個進程去更新分佈式緩存、同一進程的多個線程去更新本地緩存,可能都需要進行鎖操作。
以上就是這篇文章的全部內容瞭,希望本文的內容對大傢的學習或者工作具有一定的參考學習價值,謝謝大傢對WalkonNet的支持。如果你想瞭解更多相關內容請查看下面相關鏈接
推薦閱讀:
- 基於ABP框架實現數據字典開發
- C#中Attribute特性的用法
- C#微信公眾號開發之用戶上下文WeixinContext和MessageContext
- 詳解C#中普通緩存的使用
- C#調用百度翻譯實現翻譯HALCON的示例