網站首頁 編程語言 正文
一、背景介紹:
我們在進行數據存儲的時候,有時候會加入本地緩存、分布式緩存以及數據庫存儲三級的結構,當我們取值的時候經常是像下面這樣的流程:
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緩存的方式多種多樣,這么做可以把具體的獲取和設置緩存的操作開放給使用者。在系統重構方面上,可以將一些通用的方法抽象出來,相對成本較低,擴展性好一些。
五、題外話
上面的代碼中對于更新數據,沒有做線程安全處理,多個進程去更新分布式緩存、同一進程的多個線程去更新本地緩存,可能都需要進行鎖操作。
原文鏈接:https://www.cnblogs.com/dcz2015/p/11126870.html
相關推薦
- 2024-03-24 MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用
- 2022-11-20 C#設計模式之裝飾器模式實例詳解_C#教程
- 2022-09-04 python?matplotlib庫繪圖實戰之繪制散點圖_python
- 2022-07-13 w2ui fixedBody 屬性
- 2022-11-03 python數據分析基礎知識之shape()函數的使用教程_python
- 2022-12-22 關于Python?ImportError:?No?module?named?通用解決方法_pytho
- 2022-10-06 Django數據映射(一對一,一對多,多對多)_python
- 2022-04-12 qt5之QFile讀寫文件功能詳解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支