C#自由组合本地缓存、分布式缓存和数据库的数据

网友投稿 678 2023-04-06

C#自由组合本地缓存、分布式缓存和数据库的数据

C#自由组合本地缓存、分布式缓存和数据库的数据

一、背景介绍:

我们在进行数据存储的时候,有时候会加入本地缓存、分布式缓存以及数据库存储三级的结构,当我们取值的时候经常是像下面这样的流程:

1.先取本地缓存,如果值存在直接返回

2.本地缓存不存在,获取分布式缓存,存在直接返回,并更新本地缓存

3.分布式缓存不存在,查询数据库,更新分布式缓存、更新本地缓存,最后返回

但如果对于一些场景,可能只有本地缓存、只有分布式缓存或者说上面三种的几种组合,我们怎么要应对这样的变化,怎么能抽象出一套方式,能够应对各种不同数据存储方式造成的变化。

二、设计思路:

首先我们分析一下上面这个过程的模型,可以抽象出5个方法:

1.GetDataFromLocalCache

2.GetDataFromDistributeCache

3.GetDataFromDB

4.SetDataToLocalCache

5.SetDataToDistributeCache

其实,不同的场景无非就是这几个方法的组合,只不过里面的内容不同罢了,说到这里我们应该已经有思路了,可以利用委托来实现。

三、详细设计:

①定义一个类,包含上面五个方法的委托;

public class DataOperateInput{ public Func GetDataFromLocalCache { get; set; } = null; //获取本地缓存数据 public Func GetDataFromDistributeCache { get; set; } = null; //获取分布式缓存数据 public Func GetDataFromDb { get; set; } = null; //获取DB数据 public Action SetDataTolocalCache { get; set; } = null; //设置本地缓存数据 public Action SetDataToDistributeCache { get; set; } = null; //设置分布式缓存数据}

②实现一个方法,组合这五个方法。

public class DataOperate { ///

/// 获取数据入口 /// /// /// /// public T GetData(DataOperateInput input) where T : class, new() { T result = null; //需要从本地缓存取 if (input.GetDataFromLocalCache != null) { //调用本地缓存委托方法获取值 result = input.GetDataFromLocalCache(); if (result != null) { return result; } } //获取值为空或者不从本地缓存获取,调用下面的方法,从分布式缓存和Db中获取数据 return GetDataFromDistributeAndDB(input); } /// /// 从分布式缓存和Db中获取数据 /// /// /// /// private T GetDataFromDistributeAndDB(DataOperateInput 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); } /// /// 从数据库中获取数据 /// /// /// /// private T GetDataFromDB(DataOperateInput 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.定义一个枚举类,通过这个枚举可以自由组合数据源

///

/// 数据源类别/// [Flags]public enum DataSourceKind{ /// /// 本地缓存 /// LocalCache = 1, /// /// 分布式缓存 /// DistributeCache = 2, /// /// 数据库 /// 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.实现一个获取用户信息的方法

///

/// 获取用户数据 /// /// 用户Id(可以是自己相关的业务代码) /// 数据源类型(调用方可以自己组合) /// 是否需要更新本地缓存 /// 是否需要更新分布式缓存 /// 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(); //如果包含从本地缓存取值 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的代码。

///

/// 从本地缓存获取用户信息/// /// /// 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;}/// /// 从Redis缓存获取用户信息/// /// /// 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;}/// /// 从DB获取用户信息/// /// /// 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;}/// /// 设置用户信息到本地缓存/// /// /// private bool SetUserToLocalCache(User userInfo){ Console.WriteLine($"设置值到本地缓存:useId = {userInfo.UserId}"); return true;}/// /// 设置用户信息到Redis缓存/// /// /// 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缓存的方式多种多样,这么做可以把具体的获取和设置缓存的操作开放给使用者。在系统重构方面上,可以将一些通用的方法抽象出来,相对成本较低,扩展性好一些。

五、题外话

上面的代码中对于更新数据,没有做线程安全处理,多个进程去更新分布式缓存、同一进程的多个线程去更新本地缓存,可能都需要进行锁操作。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:MongoDB一个基于分布式文件存储的数据库(介于关系数据库和非关系数据库之间的数据库)
下一篇:分布式数据库中间件的实现原理介绍一:分库分表
相关文章