采用EntLib5.0(Unity+Interception+Caching)实现项目中可用的Caching机制

本文深入探讨了缓存策略在实际项目中的应用,包括对外部数据的定时更新、内部数据的有效利用及触发更新机制的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看了园子里很多介绍Caching的文章,多数都只介绍基本机制,对于Cache更新和依赖部分,更是只简单的实现ICacheItemRefreshAction接口,这在实际项目中是远远不够的。实际项目中,至少应该考虑以下3点:

  1. 外部数据:通过外部服务,从其他系统取来的数据。我们无法控制,也不知道啥时候会被更新。对于这部分数据,我们采用定时更新的策略,默认1小时更新1次,可配置。
  2. 内部数据:系统自己产生的数据,可以完全掌控。对这部分数据,我们实现ICacheItemRefreshAction接口,一旦失效,立即重取。
  3. 内部数据如何触发更新:具体哪些操作会触发哪个cache失效,例如Add/Delete/Update[User][s]->Get[User][s]失效,这种策略应该可以配置。

接下去分别展开。

 

0、前言

      下面的讨论分成2大块,一是环境的搭建,包括用Unity提供服务实例+用Unity提供Interception。经过这一块,就可以AOP的方式实现Caching了,这部分园子里的文章很多,我简略的过一下。二是Caching的实现,分别从外部数据缓存+内部缓存+内部触发更新3部分来讨论。

 

1、环境的搭建

1.1、用Unity提供服务实例

这部分其实就是用Unity实现IInstanceProvider,再配合IEndpointBehavior之类的,来实现WCF与Unity的集成。核心代码如下:

1 public class UnityInstanceProvider : IInstanceProvider{
2   public object GetInstance(InstanceContext context, Message msg){
3     return UnityWrapper.Instance.Resolve(serviceType);
4   }
5 }

具体可参见Artech的这篇《WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成》。

 

1.2、用Unity提供Interception(即PIAB,PolicyInjection)

     这里要先吐槽一下EntLib,向后的兼容性做的太差了,完全不符合微软一贯的风格。每出一个大版本就一堆Breaking Changes和Deprecated,真心有点郁闷。当然估计他们也有苦衷,比如EntLib6里的Caching,被包含到Net40里了,确实也只能放弃。好吧,回归正题。好在EntLib自带的帮助文档写的非常详尽,有问题应该优先去Manual里查看相关内容。

      EntLib4.1的PIAB模块,到EntLib5里就完全基于Unity的Interception来实现了,到EntLib6里就干脆取消了。这里我们采用EntLib5里的配置来实现策略注入,核心配置如下:

 1 <unity>
 2 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
 3 <assembly name="Service"/>
 4 <namespace name="Service"/>
 5 <alias alias="CachingCallHandler" type="Service.Unity.CachingCallHandler, Service"/>
 6 <container name="UnityContainer">
 7   <extension type="Interception" />
 8   <interception>
 9     <policy name="caching">
10       <matchingRule name="allServiceMatch" type="NamespaceMatchingRule">
11         <constructor>
12           <param name="namespaceName" value="Service"/>
13         </constructor>
14       </matchingRule>
15       <callHandler name="cachingService" type="CachingCallHandler"/>
16     </policy>
17   </interception>
18   <register type="IService1">
19     <interceptor type="TransparentProxyInterceptor" />
20     <policyInjection />
21   </register>
22 </container>
23 </unity>

这里有个细节,为了方便配置,1是可以用EntLib里自带的EntLibConfig.exe,2是在vs菜单->XML->架构里,添加UnityConfiguration20.xsd或者EnterpriseLibrary.Configuration.xsd,这样就有智能提示了。如果有EntLib的安装目录里找不到.xsd文件,可以用Everything搜一下。

 

2、Caching的实现

2.1、外部数据缓存

    外部数据是我们不可控的数据,什么时候更新不确定。我们采取定时更新的策略,更新的频率主要取决于外部数据的变化频率,比如我们项目里依赖是外部的基础数据,变化较少,所以只需1小时甚至半天更新一次。

    原本我以为AbsoluteTime是我想要的(注:SlidingTime是用户第一次命中Cache后,再滑动一段时间才过期),但通过ILSpy查看内部代码才知道,EntLib不是在一到期就自动触发ICacheItemRefreshAction接口的,而是在用户访问Cache的时候,再根据时间判断是否过期,如果过期再触发RefreshAction。代码如下:

1 namespace Microsoft.Practices.EnterpriseLibrary.Caching{
2 public class Cache{
3 public object GetData(string key){
4   if(cacheItem.HasExpired()){
5     this.inMemoryCache.Remove(key);
6     RefreshActionInvoker.InvokeRefreshAction(cacheItem, CacheItemRemovedReason.Expired, this.instrumentationProvider);
7     this.instrumentationProvider.FireCacheExpired(1L);
8 }}}}

    这样的用户体验当然差了些,不能让用户享受到Caching的全部好处,我们改用Timer自动更新Cache,注意请使用System.Threading.Timer,而不是System.Timers.Timer。代码如下://我们用自定义的TimerCacheAttribute来标识这类方法 

 1 var atts = input.MethodBase.GetCustomAttributes(typeof(TimerCacheAttribute), false);
 2 if(atts.Length > 0){
 3   RefreshTimerUtil.Instance[key] = new Timer(e => {
 4     object[] args = e as object[];
 5     if(args == null || args.Length != 2) return;
 6 
 7     var tmpInput = args[0] as IMethodInvocation;
 8     var tmpNext = args[1] as GetNextHandlerDelegate;
 9     var tmpReturn = tmpNext()(tmpInput, tmpNext);
10     if(tmpReturn.Exception == null) {
11       CacheWrapper.Instance.Add(key, tmpReturn.ReturnValue);
12     }
13   },
14   new object[] { input, getNext },
15   TimeSpan.Zero,
16   new TimeSpan(1, 0, 0));
17 }

 

2.2、内部数据缓存

内部数据可以直截了当的插入Cache,然后就静静的等待增删改操作来把它置为过期。

1 CacheWrapper.Instance.Add(key, methodReturn.ReturnValue,
2   CacheItemPriority.Normal,
3   new CacheRefreshAction { Input = input, GetNext = getNext });

当然,要实现ICacheItemRefreshAction接口,代码如下:

 1 [Serializable]
 2 public class CacheRefreshAction : ICacheItemRefreshAction {
 3   public IMethodInvocation Input {get; set;}
 4   public GetNextHandlerDelegate GetNext {get; set;}
 5   public void Refresh(string removedKey, object expiredValue, CacheItemRemovedReason removalReason) {
 6     if( Input == null || GetNext == null) return;
 7     var methodReturn = GetNext()(Input, GetNext);
 8     if(methodReturn.Exception == null) {
 9       CacheWrapper.Instance.Add(removedKey, methodReturn.ReturnValue,
10         CacheItemPriority.Normal,
11         new CacheRefreshAction { Input = Input, GetNext = GetNext });
12     }
13   }
14 }

    据说MemoryCache是不需要实现ISerializable的,而其他Caching要么实现ISerializable、要么打[Serializable]标签。不过这种实现方式,IMethodInvocation和GetNextHandlerDelegate目测序列化都略麻烦,好在目前我们只用到MemoryCache,可以先不管,以后再突破。

 

2.3、内部数据触发更新

    最后就剩触发更新了,如何实现类似Add/Delete/Update[User][s]->Get[User][s]的效果呢?大致思路是:1)判断方法名里是否StartWith关键字(Add/Delete/Update);2)截取出目标关键字User、并生成对应的复数形式Users;3)与Get等动作拼接成CacheKey,把所有命中的CacheKey置为过期(CacheManager.Remove),即可触发对应的RefreshAction。代码如下:

 1 var entity = string.Empty;
 2 foreach (var act in ConfigUtil.Action){
 3   if(methodName.StartsWith(act)) {
 4     entity = methodName.Substring(act.Length);
 5     break;
 6 }}
 7 
 8 var entities = PluralUtil.Pluralize(entity);
 9 if(entities == entity) entities = PluralUtil.Singularize(entity);
10 foreach (var prefix in ConfigUtil.Prefix) {
11   CacheWrapper.Instance.RemoveStartWith(prefix + entity);
12   CacheWrapper.Instance.RemoveStartWith(prefix + entities);
13 }

这里的产生单复数的代码是从EntityFramework里借来的,具体请查看EnglishPluralizationService

如果有不正确的地方,欢迎批评指正!

转载于:https://www.cnblogs.com/AlexanderYao/p/4284433.html

模拟鼠标和键盘 注意:不支持Windows 8 / 8.1。 Interceptor是Windows键盘驱动程序的包装器(包装http://oblita.com/Interception)。 使用驱动程序,Interceptor可以模拟按键和鼠标点击... 使用DirectX的游戏,通常不接受使用SendInput()的击键 Windows的受保护区域,如Windows登录屏幕或UAC调暗屏幕 任何应用程序 因为驱动程序模拟击键和鼠标单击,所以目标窗口必须处于活动状态(即,在发送击键和鼠标点击时,不能在另一个窗口上执行多任务)。 如何使用 下载并构建此项目并在项目中引用其DLL。 下载'interception.dll',这是一个由驱动程序作者编写的独立库。将它放在与可执行文件相同的目录中。这是必需的。 从作者的网页下载并安装“install-interception.exe”。安装后重新启动计算机。 在您的代码中,要加载驱动程序,请调用(阅读下面的代码注释;您必须设置过滤模式以捕获按键事件或发送按键操作!): Input input = new Input(); // Be sure to set your keyboard filter to be able to capture key presses and simulate key presses // KeyboardFilterMode.All captures all events; 'Down' only captures presses for non-special keys; 'Up' only captures releases for non-special keys; 'E0' and 'E1' capture presses/releases for special keys input.KeyboardFilterMode = KeyboardFilterMode.All; // You can set a MouseFilterMode as well, but you don't need to set a MouseFilterMode to simulate mouse clicks // Finally, load the driver input.Load(); 做你的东西。 input.MoveMouseTo(5, 5); // Please note this doesn't use the driver to move the mouse; it uses System.Windows.Forms.Cursor.Position input.MoveMouseBy(25, 25); // Same as above ^ input.SendLeftClick(); input.KeyDelay = 1; // See below for explanation; not necessary in non-game apps input.SendKeys(Keys.Enter); // Presses the ENTER key down and then up (this constitutes a key press) // Or you can do the same thing above using these two lines of code input.SendKeys(Keys.Enter, KeyState.Down); Thread.Sleep(1); // For use in games, be sure to sleep the thread so the game can capture all events. A lagging game cannot process input quickly, and you so you may have to adjust this to as much as 40 millisecond delay. Outside of a game, a delay of even 0 milliseconds can work (instant key presses). input.SendKeys(Keys.Enter, KeyState.Up); input.SendText("hello, I am typing!"); /* All these following characters / numbers / symbols work */ input.SendText("abcdefghijklmnopqrstuvwxyz"); input.SendText("1234567890"); input.SendText("!@#$%^&*()"); input.SendText("[]\\;',./"); input.SendText("{}|:\"?"); // And finally input.Unload(); 笔记: BadImageFormatException如果您没有为解决方案中的所有项目(包括此项目)使用正确的体系结构(x86或x64),则可能会获得。因此,您可能必须下载此项目的源代码才能将其重建为正确的体系结构。这应该很简单,构建过程应该没有错误。 您必须从http://oblita.com/Interception下载'interception.dll' 。 如果你已经完成了以上所有操作(正确安装了拦截驱动程序,将interception.dll放在你的项目文件夹中),你仍然无法发送击键: 驱动程序有一个限制,即它不能在不接收至少一次击键的情况下发送击键。这是因为驱动程序不知道键盘是哪个设备ID,因此它必须等待接收击键以从击键中推断出设备ID。 总之,在发送击键之前,请始终按键盘一次。点按任意键。然后你可以发送击键。这不适用于接收击键,因为通过接收击键,您当然已经按下了一个键。 MoveMouseTo()和MoveMouseBy()完全忽略键盘驱动程序。它使用System.Windows.Forms.Position来设置和获取游标的位置(它为下面的各个函数调用标准的Win32 API)。 原因是,在探索键盘驱动程序的鼠标移动功能时,我注意到它没有按像素单位移动光标,而是似乎通过加速移动光标。当我想将光标移动到某个位置时,这会不断产生不一致的值。因为Win32游标设置API通常不被游戏等阻止,所以我发现只需调用这些标准API即可,而无需使用驱动程序。请注意,这仅适用于设置光标位置。拦截光标仍然可以正常工作。例如,您可以使用Interceptor反转鼠标的x和y轴。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值