C#实现自由组合本地缓存、分布式缓存和数据查询
作者:奋斗的大橙子 发布时间:2021-06-13 00:56:11
一、背景介绍:
我们在进行数据存储的时候,有时候会加入本地缓存、分布式缓存以及数据库存储 * 的结构,当我们取值的时候经常是像下面这样的流程:
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
猜你喜欢
- 目录为什么选择MQTTMQTT, 启动!使用方式Client模式创建工厂类创建工具类Spring Integration总结为什么选择MQT
- 一、导入和导出导入:通过解析excel表格中的数据,然后将数据放到一个集合中,接着通过对持久层操作,将数据插入到数据库中,再加载一下页面,从
- 一、概述现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射
- 本文实例讲述了C#图像处理之木刻效果实现方法。分享给大家供大家参考。具体如下://木刻效果public Bitmap PFilterMuKe
- 使用限制JDBC未支持列表Sharding-JDBC暂时未支持不常用的JDBC方法。DataSource接口不支持timeout相关操作Co
- 前言:在实际的应用开发中,很多时候往往因为一些不可控的因素导致程序出现一些错误,这个时候就要及时把异常信息反馈给客户端,便于客户端能够及时地
- 前言RedisTemplate是Spring对于Redis的封装。如上图所示,RedisTemplate中定义了对5种数据结构操作。redi
- 死锁问题死锁定义多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为死锁。死锁举例注意:线程和锁的关系是:一个线程可以拥有多把锁,
- 一个项目中需要使用两个数据库,Oracle 和Mysql,于是参考各个blog,实现此功能。写好后才发现,原来的事务失效了,我去...spr
- 本文实例讲述了C#使用Word中的内置对话框的方法,分享给大家供大家参考。具体实现方法如下:使用 Microsoft Office Word
- 我们知道在编程时许多操作(如更新UI)需要在主线程中完成,而且,耗时操作(如网络连接)需要放在子线程中,否则会引起ANR。所以我们常使用Ha
- 引言备忘录模式经常可以遇到,譬如下面这些场景:浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退
- 第三章 字符串,比较器和过滤器JDK引入的一些方法对写出函数式风格的代码很有帮助。JDK库里的一些的类和接口我们已经用得非常熟悉了,比如说S
- 手把手教你用C#开发Android应用程序的方法和流程摘要:用C#能开发RFID-android吗?C#真的能开发android程序吗?C#
- springboot使用mybatis一对多的关联查询由于刚开始写java不久,对sql语句的熟悉度还是不够熟练,虽然现在使用的mybati
- 最近要给一个 Winform 项目添加功能,需要一个能显示进度条的弹窗,还要求能够中止任务,所以就做了一个,在此做个记录总结。虽然用的是比较
- (一)springboot web项目打jar包1、打包两种打包方式maven命令打包切换目录到工程根下,pom.xml所在位置,运行mav
- /** * 冒泡排序估计是每本算法书籍都会提到的排序方法。 * 它的基本思路是对长度为N的序列,用N趟来将其排成有序序列。 * 第1趟
- 目录前言一、Spring Boot对Redis的支持二、实战1、添加依赖2、redis配置3、实现序列化4、创建Redis连接工厂,同时注册
- 骑缝章是用于往来业务合同,以确保合同真实、有效的印章加盖方法,是一种防范风险的重要方式。在Java程序中,可以通过使用工具来辅助加盖这种骑缝