一、背景介绍:

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

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 = ,
/// <summary>
/// 分布式缓存
/// </summary>
DistributeCache = ,
/// <summary>
/// 数据库
/// </summary>
DataBase = }

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 == )
{
user = new User
{
UserId = userId,
Age = ,
Name = $"BigOrange_{userId}",
Sex =
};
} 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 == || userId == )
{
user = new User
{
UserId = userId,
Age = ,
Name = $"BigOrange_{userId}",
Sex =
};
} 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 == || userId == || userId == )
{
user = new User
{
UserId = userId,
Age = ,
Name = $"BigOrange_{userId}",
Sex =
};
} 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(, DataSourceKind.LocalCache);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache); //2.只从Redis缓存取值
userInfoService.GetUserInfo(, DataSourceKind.DistributeCache);
userInfoService.GetUserInfo(, DataSourceKind.DistributeCache); //3.只从DB取值
userInfoService.GetUserInfo(, DataSourceKind.DataBase);
userInfoService.GetUserInfo(, DataSourceKind.DataBase); //4.从本地缓存和Redis取值
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache);
//不更新到本地
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, false);
//更新到本地
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, true); //5.从Redis和DB取值
userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase);
userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, false);
userInfoService.GetUserInfo(, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true); //6.从本地和DB取值
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase, false,false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DataBase, true, false); //7.三者都使用
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true, false);
userInfoService.GetUserInfo(, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true);
userInfoService.GetUserInfo(, 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缓存的方式多种多样,这么做可以把具体的获取和设置缓存的操作开放给使用者。在系统重构方面上,可以将一些通用的方法抽象出来,相对成本较低,扩展性好一些。

五、题外话

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

C#自由组合本地缓存、分布式缓存和数据库的数据的更多相关文章

  1. mybatis本地缓存&分布式缓存干货分享

    前言:干货记录学习mybatis实际开发中缓存的使用. 环境: springboot2.X + mybatis3.x Mybatis是一款持久层框架,它提供了一级缓存和二级缓存. 名词解释 一级缓存( ...

  2. WEB 应用缓存解析以及使用 Redis 实现分布式缓存

    什么是缓存? 缓存就是数据交换的缓冲区,用于临时存储数据(使用频繁的数据).当用户请求数据时,首先在缓存中寻找,如果找到了则直接返回.如果找不到,则去数据库中查找.缓存的本质就是用空间换时间,牺牲数据 ...

  3. Redis实现分布式缓存

    Redis 分布式缓存实现(一) 1. 什么是缓存(Cache) 定义:就是计算机内存中的一段数据: 2. 内存中数据特点 a. 读写快    b. 断电立即丢失 3. 缓存解决了什么问题? a. 提 ...

  4. 分布式进阶(十八) 分布式缓存之Memcached

    分布式缓存 分布式缓存出于如下考虑:首先是缓存本身的水平线性扩展问题,其次是缓存大并发下本身的性能问题,再次避免缓存的单点故障问题(多副本和副本一致性). 分布式缓存的核心技术包括首先是内存本身的管理 ...

  5. 本地缓存google.guava及分布式缓存redis 随笔

    近期项目用到了缓存,我选用的是主流的google.guava作本地缓存,redis作分布式 缓存,先说说我对本地缓存和分布式缓存的理解吧,可能不太成熟的地方,大家指出,一起 学习.本地缓存的特点是速度 ...

  6. .Net Core 跨平台开发实战-服务器缓存:本地缓存、分布式缓存、自定义缓存

    .Net Core 跨平台开发实战-服务器缓存:本地缓存.分布式缓存.自定义缓存 1.概述 系统性能优化的第一步就是使用缓存!什么是缓存?缓存是一种效果,就是把数据结果存在某个介质中,下次直接重用.根 ...

  7. 【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)

    一.缓存 当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象.所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从 ...

  8. 主要从架构上来做优化,负载均衡、CDN、静态化、数据库的水平切割和纵向切割、读写分离、分布式缓存着手

    语言知识一种工具,甚至技术本身也只是一种工具,本身并不值钱,关键在于用于何种行业,产生了什么价值. 但从语言来看,我个人更喜欢php,然后是C#,然后是java从框架而言,先是java,然后C#,再次 ...

  9. c#实例化继承类,必须对被继承类的程序集做引用 .net core Redis分布式缓存客户端实现逻辑分析及示例demo 数据库笔记之索引和事务 centos 7下安装python 3.6笔记 你大波哥~ C#开源框架(转载) JSON C# Class Generator ---由json字符串生成C#实体类的工具

    c#实例化继承类,必须对被继承类的程序集做引用   0x00 问题 类型“Model.NewModel”在未被引用的程序集中定义.必须添加对程序集“Model, Version=1.0.0.0, Cu ...

随机推荐

  1. iOS面霸计划(难度)

    一.面试题 1.Xcode项目的目录结构是怎么分组的? 2.简单介绍下在真机上调试开发证书申请流程. 3.按Home键时,怎么保存程序运行状态 4.当程序运行过程中,按下home键以后,ios程序会调 ...

  2. excel自学笔记 from av50264533

    1.函数公式 MINUTE(serial_number)  函数解读 Serial_number 表示一个时间值,其中包含要查找的分钟  函数公式 NOW()  函数解读 显示出现在的时间 计算通话时 ...

  3. 【ARM-Linux开发】如何使用opkg在RicoBoard上在线安装软件包

    类似于debian的apt-get,Redhat的yum类似,嵌入式Linux平台可以使用opkg实现在线安装软件包的功能,在我们提供的matrix文件系统中,已经包含了opkg工具,但是还没有配置一 ...

  4. 【计算机视觉】Selective Search for Object Recognition论文阅读2

    Selective Search for Object Recognition 是J.R.R. Uijlings发表在2012 IJCV上的一篇文章.主要介绍了选择性搜索(Selective Sear ...

  5. Flarum 安装问题 编译安装 fileinfo.so

    大部分人上传 会遇到文件没有权限这一问题 ,这个好解决 下边是服务器 php.ini 扩展 fileinfo 由于php 版本是7.1.8 最新的 又是用的一键环境安装 所以 没有安装这个扩展 1.去 ...

  6. Mac brew命令踩坑

    brew是mac上的在线包管理软件,相当于linux apt以及yum包管理工具 1.取消brew的自动更新(https://blog.csdn.net/yf9595/article/details/ ...

  7. 最新 哔哩哔哩java校招面经 (含整理过的面试题大全)

    从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.哔哩哔哩等10家互联网公司的校招Offer,因为某些自身原因最终选择了哔哩哔哩.6.7月主要是做系统复习.项目复盘.Leet ...

  8. 日常工作问题解决:Redhat6.5--解决yum无法正常安装配置问题

    1.问题描述 解决RedHat6.5下yum功能不能用问题: 在redhat6.5下使用yum安装时,会提示:This system is not registered to Red Hat Subs ...

  9. jmeter BeanShell实例-----两个变量之间的断言对比(转)

    jmeter BeanShell实例-----两个变量之间的断言对比 在jmeter的中,断言没法对两个变量的进行对比后判断,只能使用Bean Shell断言来进行,总是有人来问怎么写呢.这里写一个简 ...

  10. todo---jaxen