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


    一、背景介绍:

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

    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("=============================================
    ");
    
            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缓存的方式多种多样,这么做可以把具体的获取和设置缓存的操作开放给使用者。在系统重构方面上,可以将一些通用的方法抽象出来,相对成本较低,扩展性好一些。

    五、题外话

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

  • 相关阅读:
    【模板小程序】链表排序(qsort/insert_sort/merge_sort)
    链表回文判断(C++)
    【模板小程序】十进制大数除法(输出商和余数)
    【模板小程序】字符串截取
    【模板小程序】翻转一个句子中的单词
    web前端基础背景
    MongoDB基本知识(补充)
    Python-ORM
    Python-元编程
    ElementUI 中 el-table 获取当前选中行的index
  • 原文地址:https://www.cnblogs.com/dcz2015/p/11126870.html
Copyright © 2020-2023  润新知