CYQ.Data V5 分布式自动化缓存设计介绍

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介:

前方:

其实完成这个功能之前,我就在思考:是先把想法写了来,和大伙讨论讨论后再实现,还是实现后再写文论述自己的思维。

忽然脑后传来一个声音说:你发文后会进入发呆阶段。

所以还是静下心,让我轻轻地把代码撸完再说。

最近这几天,自己在大脑里演练过各种技术难点,解决方案,推敲了各种该解决的问题,觉的差不多了,才决定撸码。

忽然发觉,原来代码是可以写在大脑里的。

要是你看到一个员工坐着2天没写一行代码,说明人家是高手,正在大脑编程。

好,不扯,回正文!

传统ORM的二级缓存为何失效?

有些ORM会提供:如Hibernate。

有些不提供:如EF,不提供是因为知道提供了也没啥鸟用,因为:

1:你不能强迫一个项目全部用单实体编程,多表时,用户更偏向于执行SQL语句。

2:没有分布式缓存做为基础,解决不了多应用程序部署的缓存策略问题。

因此:

1:若控制不了整个项目用户的SQL语句,单机的搞不了。

2:没有分布式缓存做基础,分布式的都搞不了。

这也是为啥EF一直不提供,是因为看到Hibernate虽然提供但并没多大卵用的原因吧!

疑惑数据库已有缓存,为何框架还要造孽?

主要原因:

1:数据库从请求到建立缓存,需要时间(框架缓存可以减缓数据库缓存失效时压力)

2:数据库是有链接数限制的,不可能允许大量并发的直连,需要外界分压。

3:数据库的缓存是单机性。

4:数据库发数据往服务器的时间比本机缓存的长。

自动缓存设计前的一些思考:

1:一开始我思考的缓存策略,是细化到行或列,于是了解数据库自身缓存后发现数据库目前也只是做了以表为单位。

2:MSSQL是有提供SqlDependency的缓存依赖项的,它可以从数据库层面通知你的数据何时失效。

3:但是SqlDependency和SqlCommand依赖太深,无法在所有数据库层面通用。

4:SqlDependency的缓存依赖只能在本地缓存。

5:其它数据库不支持依赖通知。

6:所以方案只能通过全局执行与分析,来处理缓存及失效策略。

7:单机时:全局拦截分析,如何分析出表?

8:应用分布式时:缓存及时失效?

9:用户直接修改数据库时:缓存如何失效?

还有好多好多问题,一直在思考......

缓存什么?

1:缓存单个对象时,是直接存档对象的,Cache返回时会根据本地或是远程选择是否Clone返回。。

2:缓存列表时:只存档字段类型仅包含:(数字、布尔、字符、时间、GUID)的字段,并转成Json字符串存档。

技术细节:

  A:一个对象存档在本机时,存档的是引用(可能出现误写操作);存档在分布式时,存档的不是引用,这会在使用时出现不确定性。

  B:大对象的存档,在缓存来去间需要序列化和反序列化,性能上降低很多。

因此:将列表转成Json存档,拿到时再还原,可以同时解决A和B的问题。

简单的说,如果对象有长字段,或者有二进制数据,是不会被缓存的,所以MSSQL的Timestamp字段就不要用了;

如果要用:AppConfig.DB.HiddenFields="字段名",把它隐藏了也行。

缓存时间?

考虑到通常访问量低时都是在中午和晚上的时间,因此,将缓存的对象的时间随机分布(早上的分布在中午失效,下午的分布在晚上失效)

考虑到分页时的查询,通常都关注前面几页,因此前面几页的数据,时间如上的时间段分布。

分页后面的数据,只默认2分钟的缓存时间。

其它规则有待讨论......

缓存多大?

1:在单机状态,检测到内存的可用比例低于15%时,则不再接受缓存。

2:在分布式缓存状态,暂时有多少扔多少。

缓存如何失效:

1:拦截请求:(包括(MAction)增删改查+(MProc)执行自定义语句+(MDataTable)批量方法)

2:分析语句的关联表(单表的可以拿表名,视图的拿关联结构涉及的表名,存储过程(目前没法),自定义SQL(语句分析出表名),批量(直接拿表名)

3:技术难点:如何从未知的SQL或视图中准确的分析出所有关联的表。

4:缓存失效:执行以下方法应该失效:增删改,执行ExeNonQuery,批量语句。

5:技术难点:

  1:对于视图(关联了多个表,如何根据一个表名,关联到相应涉及的视图语句失效?)

  2:对于分布式的应用,A服务更新,如何B服务器也失效。

如何处理修改频繁的表:

1:一开始想增加配置,让用户设置不参与缓存的表,认真思考后,发现根据缓存失效的时间和次数,可自动分析判断一个表是否修改频繁。

2:表操作相关增删查时,该表被置为失效(相关缓存会被移除),此时设置好时间间隔(6秒),在此时间段对该表相关的不缓存,同时提交的缓存删命令也可以无视。

3:对被分析出为修改频繁的表该如何处理?延长相应的不缓存时间,或是??还需要思考!!!

缓存失效的粒度能不能小?

1:目前的失效,和数据库一样,是以表为单位的。

2:对于插入操作,不会影响某一条数据的读取(所以单条数据的查询,是不应该受到插入操作的影响的)

3:还有其它情形是必然不会影响的?

框架有自动缓存,业务需不需要缓存?

1:数据库有自动缓存,框架的也可以自动缓存。

2:框架有自动缓存,同理业务也可以有缓存。

框架能处理的粒度是有限的,不能细到具体的行或列的缓存级别,因此在业务复杂和并发到一定量后,业务缓存是必要的。

数据库有缓存,业务也可做缓存,为何还思考往框架增加自动缓存?

1:数据库的默认缓存是固定的,需要配置,各种数据库环境不一致。

2:数据库链接池默认是固定的。

3:业务加缓存的事,往往是后期的动作。

现另一个现状是:

1:.NET 群体,存在很多初中级的开发人员,这部分人员的技术成长相对较慢,对缓存或性能调优并不熟。

2:国内有很多的中小网站,默认都抗不起并发,攻击成本很小,几百上千个并发就可以挂你站了。

因此,既然有现实的问题,就可以有对应的解决方案。

V5框架此功能的出现,就是为了从基础层面统一解决这些问题。

只有当.NET行业不在有慢网站的存在,整体提升档次了,有良好的口碑,才会引进更多的BOSS选用 .NET,大伙所期待的.NET春天也就近了。

V5的目前解决的问题:

总体而言,要实现这个功能,核心要解决以下问题:

下面我来将技术一点一点出卖:

5:AOP拦截问题:
首先,要实现这功能,就得全局拦截,扫荡过源码或用过V5的同学,听说过框架本身就有AOP的吧;

其次,得改造这个AOP:框架默认有一个空AOP,当外部有AOP装载的时候,会替换掉这个空AOP。

要实现这个自动缓存:本想在空AOP里实现,放着浪费,但若用户自定义的Aop被装载,又会被替换掉,走不通...

方案想了三四个,思考了三四夜,最后还是在撸码时才确定了现在的模式(这个告诉我们,想的差不多了就该撸码了,要100%想通再撸不太靠谱):

于是,我这样做了:

原有的Aop,改名成InterAop,不过是挂名的,因为它没有继承IAOP接口,而且从原本的单例变更成多例模式。

这里可以贴两行代码,意思是:在Bengin和End方法调用了外部AOP的接口,并根据外部AOP的状态决定后续的执行流程:

完整的源码你们自己SVN了:https://github.com/cyq1162/cyqdata.git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public AopResult Begin(AopEnum action)

   {
       AopResult ar = AopResult.Continue;
       if (outerAop != null)
       {
           ar = outerAop.Begin(action, Para);
           if (ar == AopResult.Return)
           {
               return ar;
           }
       }
       if (AppConfig.Cache.IsAutoCache && !IsTxtDataBase) // 只要不是直接返回
       {
           isHasCache = AutoCache.GetCache(action, Para); //找看有没有Cache
       }
       if (isHasCache)  //找到Cache
       {
           if (outerAop == null || ar == AopResult.Default)//不执行End
           {
               return AopResult.Return;
           }
           return AopResult.Break;//外部Aop说:还需要执行End
       }
       else // 没有Cache,默认返回
       {
           return ar;
       }
   }

   public void End(AopEnum action)
   {
       if (outerAop != null)
       {
           outerAop.End(action, Para);
       }
       if (!isHasCache && !IsTxtDataBase)
       {
           AutoCache.SetCache(action, Para); //找看有没有Cache
       }
   }

代码最后很少,但没想出来之前,2天都搞不定。

1:基础单表、视图操作
A:单表,这个是最简单的,传递进来的就是表名;

B:视图,这个麻烦一点,传递的是视图名;

于是,如何从视图获取相关参与的表名?你现在应该不知道,我来告诉你吧:

1
2
DBDataReader sdr=....
DataTable dt = sdr.GetSchemaTable();
这条语句,可以通杀所有的数据库,不用去N种数据库里搜各种元数据藏在哪了!!!

2:多表SQL语句操作:
对于SQL语句,可以用上面的方法,执行一个DataReader再拿,但我弄了一个简单的方法来找关联表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
internal static List GetTableNamesFromSql(string sql)

   {
       List<string> nameList = new List<string>();

       //获取原始表名
       string[] items = sql.Split(' ');
       if (items.Length == 1) { return nameList; }//单表名
       if (items.Length > 3) // 总是包含空格的select * from xxx
       {
           bool isKeywork = false;
           foreach (string item in items)
           {
               if (!string.IsNullOrEmpty(item))
               {
                   string lowerItem = item.ToLower();
                   switch (lowerItem)
                   {
                       case "from":
                       case "update":
                       case "into":
                       case "join":
                       case "table":
                           isKeywork = true;
                           break;
                       default:
                           if (isKeywork)
                           {
                               if (item[0] == '(' || item.IndexOf('.') > -1) { isKeywork = false; }
                               else
                               {
                                   isKeywork = false;
                                   nameList.Add(NotKeyword(item));
                               }
                           }
                           break;
                   }
               }
           }
       }
       return nameList;
   }

有可能会找多,找到后,再过滤一下名称是不是数据库里的表就可以了。

3:直接操作数据库
一开始设置的思维,是动态创建一个表,字段大概是这样的:

表名 更新时间

然后如果手工操作数据库,可以手工更改时间,也可以用触发器引发这里的更新。

然后后台线程定时扫这个表,就知道有没有表被更新了。

不过--------V5目前并木有实现它,只是开放了一个接口,可以让你在代码里调用移除缓存。

这个方法就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract partial class CacheManage
{

   /// <summary>
   /// 获取系统内部缓存Key
   /// </summary>
   public static string GetKey(CacheKeyType ckt, string tableName)
   {
       return GetKey(ckt, tableName, AppConfig.DB.DefaultDataBase, AppConfig.DB.DefaultDalType);
   }
   /// <summary>
   /// 获取系统内部缓存Key
   /// </summary>
   public static string GetKey(CacheKeyType ckt, string tableName, string dbName, DalType dalType)
   {
       switch (ckt)
       {
           case CacheKeyType.Schema:
               return TableSchema.GetSchemaKey(tableName, dbName, dalType);
           case CacheKeyType.AutoCache:
               return AutoCache.GetBaseKey(dalType, dbName, tableName);
       }
       return string.Empty;
   }

}
4:跨服务器操作
这个本来是简单的,后来又想麻烦了,因为要兼顾性能问题,缓存移除可能会频繁的问题。

后来,通过增加了缓存类型,来识别本地缓存或分布式缓存,来区别写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static void SetBaseKey(string baseKey, string key)

    {
        //baseKey是表的,不包括视图和自定义语句
        if (_MemCache.CacheType == CacheType.LocalCache)
        {
            if (cacheKeys.ContainsKey(baseKey))
            {
                cacheKeys[baseKey] = cacheKeys[baseKey].Append("," + key);
            }
            else
            {
                cacheKeys.Add(baseKey, new StringBuilder(key));
            }
        }
        else
        {
            StringBuilder sb = _MemCache.Get<StringBuilder>(baseKey);
            if (sb == null)
            {
                _MemCache.Set(baseKey, new StringBuilder(key));
            }
            else
            {
                sb.Append("," + key);
                _MemCache.Set(baseKey, sb);
            }
        }
    }

6:缓存失效问题  
这个问题,流程本来很简单的:

但思考到Cache多,而且分布式时,返回会卡,所以删除Cache操作就变成线程处理了。

后来为了避免线程多开,又把类改成了单例(一开始是多实例的)

现在,又把这线程的线程开启,放到LocalCache里和另一个线程作伴了,然后这个单例类又变更成了静态类。

V5框架怎么使用这功能:

升级版本到最新版即可!

总结:

1:没有这个功能之前:框架解决了三大问题:编程架构的统一(自动化)、数据库压力(读写分离)、服务器压力(分布式缓存)。

2:此功能的存在:是针对从基础层面提升行业项目的整体水平。

3:最近大脑有点发春,一个个创新Idea不断的从我大脑冒出来,折腾的我好累:

要思考架构、落实框架代码、要写文分享,写框架Demo、群里解答。

4:开源不赚钱,又投入这么多精力,只能把它当理想了,希望它有天成为.NET项目的标配数据层。

5:我博客是有打赞插件的,哈。

本文原创发表于博客园,作者为路过秋天,原文链接:http://www.cnblogs.com/cyq1162/p/5659077.html

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
4月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
3月前
|
缓存 NoSQL Java
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
缓存与分布式锁、Redisson分布式锁、缓存数据一致性【必须满足最终一致性】
152 14
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
|
2月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
73 4
|
4月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
这篇文章介绍了如何在SpringBoot项目中整合Redis,并探讨了缓存穿透、缓存雪崩和缓存击穿的问题以及解决方法。文章还提供了解决缓存击穿问题的加锁示例代码,包括存在问题和问题解决后的版本,并指出了本地锁在分布式情况下的局限性,引出了分布式锁的概念。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
|
5月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
4月前
|
缓存 NoSQL 关系型数据库
(八)漫谈分布式之缓存篇:唠唠老生常谈的MySQL与Redis数据一致性问题!
本文来聊一个跟实际工作挂钩的老生常谈的问题:分布式系统中的缓存一致性。
164 11
|
5月前
|
存储 缓存 NoSQL
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Redis用于搭建分布式缓存集群问题如何解决
104 1
|
5月前
|
存储 缓存 数据库
分布式篇问题之全量缓存解决数据库和缓存的一致性问题如何解决
分布式篇问题之全量缓存解决数据库和缓存的一致性问题如何解决
|
5月前
|
缓存 Devops 微服务
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
|
7月前
|
缓存 监控 数据库
分布式系统中缓存穿透问题与解决方案
在分布式系统中,缓存技术被广泛应用以提高系统性能和响应速度。然而,缓存穿透是一个常见而严重的问题,特别是在面对大规模请求时。本文将深入探讨缓存穿透的原因、影响以及一些有效的解决方案,以确保系统在面对这一问题时能够保持稳定和高效。
112 13

热门文章

最新文章