一切从实战出发,将逐步从本人实际的开发框架中抽出各个模块的代码,从设计开始,到运行示例,逐步讲解,,将有关DDD理论的设计心得,融合到里面
本文讲解缓存模块
键值对缓存,任何应用都可能会用到的功能,基于高内聚的原则,结合DDD的理论,设计开发一个键值对缓存访问者的实体类型很有必要,应用中需要用到键值对缓存的地方都可以通过统一的缓存访问者对象来处理,
出于简化目的,本文中的代码移除了诸如DI容器、统一上下文、国际化、自定义错误等与主干代码关联不大的部分,通过简化代码来实现
KVCacheVisitor为键值对访问者实体类,包含主要属性为
Name:唯一名称
CacheType:要访问的缓存类型,不同的类型有不同的处理
CacheConfiguration:访问不同缓存类型需要用到的不同配置
主要方法:
Get<K,V>(Func<K, Task<V>> creator, K key)
获取指定键值的缓存对象,creator为值创建行为,当缓存键不存在时,将调用该行为来获取值
GetSync<K, V>(Func<K, V> creator, K key)
上述方法的同步方法
熟悉我设计的人都知道,实体类型将统一继承自EntityBase,行为方法实现通过IMP桥连接模式连接到具体实现类实现,在这不再累述,这里简化了DI注入以及获取,采用最简单的工厂来实现
同时设计接口IKVCacheVisitorRepository,用来查询访问者
这里为简化实现,在KVCacheVisitorRepository中直接定义一个Datas的静态变量,通过键值对映射,将KVCacheVisitor存储进去,实际应用中,可能需要持久化操作
为了能够处理不同类型,设计接口IRealKVCacheVisitService,该接口负责实际处理不同类型的缓存访问处理,通过工厂与KVCacheVisitor的acheType的键值对映射,来分发实际处理请求,熟悉我设计的人应该也很清楚这种设计
接口方法为
Task<V> Get<K, V>(string cacheConfiguration, Func<Task<V>> creator, string prefix, K key)
cacheConfiguration:KVCacheVisitor的CacheConfiguration,
creator:实际对象创建行为,注意,这里的creator和KVCacheVisitor里的不一样,因为在这里,调用方为IMP实现类,我需要隐藏掉参数
prefix:缓存键的前缀,由IMP实现类提供,用来辅助规划缓存的存储
key:缓存键
接下来就是为IRealKVCacheVisitService创建不同的实现,
在示例代码中,实现了两种策略的本地内存缓存,
实现代码在RealKVCacheVisitServices中,分别为RealKVCacheVisitServiceForLocalTimeout,RealKVCacheVisitServiceForLocalVersion,在实际应用中,我还实现了基于Redis的缓存访问服务,由于redis提供的api太牛逼,代码很简单,这里就不写了,
反而是这两种本地内存缓存存储,涉及到了一些算法类的开发,开发加测试加修bug,花了不少时间,
这里要说明一下,一个优秀的程序,它的代码必然是可控的,否则必然会出问题,结合到缓存来说,那就是每个缓存队列的长度,必然是有限的,不可能永远增加,这就涉及到了这个队列的设计算法,
我这里实现了双向链表以及链表访问策略(先进先出、最近最久未访问等),通过这些数据结构来充当缓存队列,具体请看代码,这里不再详细介绍
下面对两种本地内存缓存访问服务做介绍
RealKVCacheVisitServiceForLocalTimeout
基于基于本地超时设置的KV缓存访问服务,场景为每个缓存项都有一个创建时间,当前时间超过创建时间的指定间隔后,将重新创建对象,这是最简单的场景
这里,设定的类型为LocalTimeout,配置格式为
{
"MaxLength":最大缓存长度,
"ExpireSeconds":缓存过期时间(单位秒)
}
RealKVCacheVisitServiceForLocalVersion
基于本地版本号控制的KV缓存访问服务,缓存创建的代价很高,但改动的非常少,我们不想定时来重新创建缓存,而是通过设置不同的版本号,来告知系统缓存已过期,让系统来重新更新缓存,这样,如果我一直没有更新内容,系统中的缓存将永远生效,
对性能有很大的提升,设计了一个接口IKVCacheVersionService,用来获取指定版本名称的版本号,RealKVCacheVisitServiceForLocalVersion类中维护版本名称与版本号服务映射的键值对工厂
设定的类型为LocalVersion,配置的格式为
{
"MaxLength":最大缓存长度,
"VersionCallTimeout":版本服务调用间隔(单位秒),
"VersionNameMappings":
{
"{Key的类型的FullName}-{Value的类型的FullName}":"版本名称"
}
"DefaultVersionName":"默认版本名称,找不到KV类型与版本名称的映射时使用该名称"
}
这里KV缓存中K的类型与V的类型的FullName构建成与版本名称的映射,通过它来调用不同的版本服务
VersionCallTimeout的作用是用来控制版本服务的调用频率,因为调用版本服务本身也是有一些性能损耗,我们不能让它每次请求都被调用
当检测到版本号不一致时,该实现将清空对应的缓存队列
在很多应用中,我们实际上还需要实现多级缓存,比如本地内存缓存与Redis的组合,优先查询本地缓存,如果本地缓存不存在,则拉取Redis的缓存,如果Redis不存在,再创建缓存对象
这种应用场景,对应着RealKVCacheVisitServices中的RealKVCacheVisitServiceForCombination
它可以组合缓存,
设定的类型为Combination,配置格式为
{
"VistorNames":[访问者名称1,访问者名称2,...]
}
VistorNames中的访问者名称对应着KVCacheVisitor中的Name
名称靠前的访问者,将优先使用
在示例代码的入口处,分别写了以上这三种实现的示例代码,可以运行看一下,这套KV缓存访问者模块,能够满足现有应用的所有KV缓存访问需求,当有新的需求式,通过实现新的IRealKVCacheVisitService,
即可实现扩展
最后双向链表值得说明一下
关于双向链表,我们知道,链表的缺点在于无法直接定位到指定位置,同时不支持并发操作,因此,在我的实现中,另外定义了一个哈希字典,数据获取直接操作该哈希表,双向链表负责单线程执行链表策略(如FIFO、LRU),
两个互相不阻塞,链表策略通过事件代理保证与字典的最终一致性,这样,能够保证最好的性能,在缓存中,还产生了一个意想不到的好处,那就是在高并发量中可以让缓存多“活”一会儿,降低createor的调用频率,这点请自己调整示例代码的参数,自己体会吧
最后的最后
示例代码虽然是用的Core3.1,但实际上代码在其他的上面也能用,比如Framework,4.6.2以上即可
其他不多说,有兴趣的自己结合代码看