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

标签:C#,本地,分布式,缓存,数据,查询
0
投稿

猜你喜欢

  • 详解Java匿名内部类

    2023-04-30 14:51:28
  • android 触屏的震动响应接口调用方法

    2021-08-04 08:33:34
  • Java如何实现压缩文件与解压缩zip文件

    2022-01-28 09:14:00
  • Java详解实现多线程的四种方式总结

    2023-04-04 19:43:34
  • C#读写文件的方法汇总

    2022-03-01 04:41:41
  • 在Android app中实现九(n)宫格图片连续滑动效果

    2022-10-14 21:09:23
  • C#的并发机制优秀在哪你知道么

    2022-11-09 09:44:35
  • SpringBoot项目中的多数据源支持的方法

    2022-01-12 03:42:19
  • java使用HashMap实现斗地主(有序版)

    2021-08-30 10:24:10
  • Java实现文件切割拼接的实现代码

    2022-04-15 08:10:03
  • ViewPager顶部导航栏联动效果(标题栏条目多)

    2022-11-03 13:19:38
  • 解析:ClickOnce通过URL传递参数 XXX.application?a=1

    2022-05-26 01:54:34
  • Android M(6.x)使用OkHttp包解析和发送JSON请求的教程

    2021-11-25 21:17:58
  • 浅谈java 重写equals方法的种种坑

    2023-02-03 08:14:12
  • Android操作Excel文件的功能实现

    2022-08-29 17:22:55
  • c#扩展datatable转json示例

    2022-08-19 10:44:55
  • Java 数据库连接池详解及简单实例

    2023-08-10 15:54:07
  • C#数据类型转换(显式转型、隐式转型、强制转型)

    2021-11-24 13:44:25
  • DataGridView带图标的单元格实现代码

    2021-08-07 16:50:04
  • spring项目中切面及AOP的使用方法

    2021-12-01 21:11:29
  • asp之家 软件编程 m.aspxhome.com