实现一个简单的缓存模块·续, 添加Memcached调用实现

jusfr 原创,转载请注明来自博客园

在之前的实现中,我们初步实现了一个缓存模块:包含一个基于Http请求的缓存实现,一个基于HttpRuntime.Cache进程级的缓存实现,但观察代码,会发现如下问题:

1. 有部分逻辑如 Boolean TryGet<T>(String key, out T entry) 的实现有重复现象,Do not repeat yourself 提醒我们这里可以改进;
2. 分区特性虽然实现了,但是使用了额外的接口承载,而大多数运用中,调用者无论是操作缓存项的创建还是过期,都不太关心分区参数 Region;的机制问题,计数和全部过期貌似不太现实,从这个接口派生恐怕不妥,怎么办?
3. IHttpRuntimeCacheProvider 接口中功能太多,本文要添加一个基于 Memcached 的缓存实现类,而 Memcached 天然不支持遍历等操作怎么办?

处理第1个问题,先梳理一下缓存获取即 GetOrCreate 逻辑,多数情况是这样的

1)尝试从某容器或客户端如 HttpContext.Current.Items、HttpRuntime.Cache、MemcachedClient 判断缓存是否存在及获取缓存对象;
2)缓存对象存在时进行类型对比,比如 id 已经被缓存成整型,现在新接口尝试将 Guid 类型写入,本文使用严格策略,该操作将抛出 InvalidOperationException 异常;
3)缓存不存在时,执行委托计算出缓存值,将其写入容器;

可以看出, GetOrCreate 将调用 TryGet 方法及 Overwrite 方法,我们可以使用抽象类,将前者写成具体实现,将后两者写成抽象方法,由具体子类去实现。

 1     public interface ICacheProvider {
 2         Boolean TryGet<T>(String key, out T entry);
 3         T GetOrCreate<T>(String key, Func<T> function);
 4         T GetOrCreate<T>(String key, Func<String, T> factory);
 5         void Overwrite<T>(String key, T entry);
 6         void Expire(String key);
 7     }
 8
 9     public abstract class CacheProvider : ICacheProvider {
10         protected virtual String BuildCacheKey(String key) {
11             return key;
12         }
13
14         protected abstract Boolean InnerTryGet(String key, out Object entry);
15
16         public virtual Boolean TryGet<T>(String key, out T entry) {
17             String cacheKey = BuildCacheKey(key);
18             Object cacheEntry;
19             Boolean exist = InnerTryGet(cacheKey, out cacheEntry);
20             if (exist) {
21                 if (cacheEntry != null) {
22                     if (!(cacheEntry is T)) {
23                         throw new InvalidOperationException(String.Format("缓存项`[{0}]`类型错误, {1} or {2} ?",
24                             key, cacheEntry.GetType().FullName, typeof(T).FullName));
25                     }
26                     entry = (T)cacheEntry;
27                 }
28                 else {
29                     entry = (T)((Object)null);
30                 }
31             }
32             else {
33                 entry = default(T);
34             }
35             return exist;
36         }
37
38         public virtual T GetOrCreate<T>(String key, Func<T> function) {
39             T entry;
40             if (TryGet(key, out entry)) {
41                 return entry;
42             }
43             entry = function();
44             Overwrite(key, entry);
45             return entry;
46         }
47
48         public virtual T GetOrCreate<T>(String key, Func<String, T> factory) {
49             T entry;
50             if (TryGet(key, out entry)) {
51                 return entry;
52             }
53             entry = factory(key);
54             Overwrite(key, entry);
55             return entry;
56         }
57
58         public abstract void Overwrite<T>(String key, T value);
59
60         public abstract void Expire(String key);
61     }

抽象类 CacheProvider 的 InnerTryGet、Overwrite、Expire 是需要实现类来完成的,GetOrCreate 调用它们来完成核心逻辑;于是 HttpContextCacheProvider 的实现,逻辑在父类实现后,看起来非常简洁了:

 1     public class HttpContextCacheProvider : CacheProvider, ICacheProvider {
 2         private const String _prefix = "HttpContextCacheProvider_";
 3         protected override String BuildCacheKey(String key) {
 4             return String.Concat(_prefix, key);
 5         }
 6
 7         protected override Boolean InnerTryGet(String key, out Object entry) {
 8             Boolean exist = false;
 9             entry = null;
10             if (HttpContext.Current.Items.Contains(key)) {
11                 exist = true;
12                 entry = HttpContext.Current.Items[key];
13             }
14             return exist;
15         }
16
17         public override void Overwrite<T>(String key, T entry) {
18             HttpContext.Current.Items[BuildCacheKey(key)] = entry;
19         }
20
21         public override void Expire(String key) {
22             HttpContext.Current.Items.Remove(BuildCacheKey(key));
23         }
24     }

这里不准备为基于 HttpContext 的缓存提供太多特性,但基于 HttpRuntime.Cache 的缓存就需要像过期之类的功能,在实现之前先考虑问题2

首先,既然用户没有必要甚至不知道分区存在,我们直接实现支持分区特性的子类好了;然后,计数与过期功能 HttpRuntime.Cache 支持但 Memcached 不,所以这部分功能需要从 IHttpRuntimeCacheProvider 中拆分出来,没错,扩展方法!于是拆分如下:

 1     public interface IRegion {
 2         String Region { get; }
 3     }
 4
 5     public interface IHttpRuntimeCacheProvider : ICacheProvider {
 6         T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration);
 7         T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration);
 8         void Overwrite<T>(String key, T value, TimeSpan slidingExpiration);
 9         void Overwrite<T>(String key, T value, DateTime absoluteExpiration);
10     }

其中IHttpRuntimeCacheProvider接口定义了带有过期参数的缓存操作方法,我们需要实现抽象方法与额外接口如下:

 1 public class HttpRuntimeCacheProvider : CacheProvider, IHttpRuntimeCacheProvider, IRegion {
 2         private static readonly Object _nullEntry = new Object();
 3         private String _prefix = "HttpRuntimeCacheProvider_";
 4
 5         public virtual String Region { get; private set; }
 6
 7         public HttpRuntimeCacheProvider() {
 8         }
 9
10         public HttpRuntimeCacheProvider(String region) {
11             Region = region;
12         }
13
14         protected override bool InnerTryGet(String key, out object entry) {
15             entry = HttpRuntime.Cache.Get(key);
16             return entry != null;
17         }
18
19         protected override String BuildCacheKey(String key) {
20             //Region 为空将被当作  String.Empty 处理
21             return Region == null
22                 ? String.Concat(_prefix, key)
23                 : String.Concat(_prefix, Region, key);
24         }
25
26         private Object BuildCacheEntry<T>(T value) {
27             Object entry = value;
28             if (value == null) {
29                 entry = _nullEntry;
30             }
31             return entry;
32         }
33
34
35         public T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration) {
36             T value;
37             if (TryGet<T>(key, out value)) {
38                 return value;
39             }
40             value = function();
41             Overwrite(key, value, slidingExpiration);
42             return value;
43         }
44
45         public T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration) {
46             T value;
47             if (TryGet<T>(key, out value)) {
48                 return value;
49             }
50             value = function();
51             Overwrite(key, value, absoluteExpiration);
52             return value;
53         }
54
55         public override void Overwrite<T>(String key, T value) {
56             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value));
57         }
58
59         //slidingExpiration 时间内无访问则过期
60         public void Overwrite<T>(String key, T value, TimeSpan slidingExpiration) {
61             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null,
62                 Cache.NoAbsoluteExpiration, slidingExpiration);
63         }
64
65         //absoluteExpiration 时过期
66         public void Overwrite<T>(String key, T value, DateTime absoluteExpiration) {
67             HttpRuntime.Cache.Insert(BuildCacheKey(key), BuildCacheEntry<T>(value), null,
68                 absoluteExpiration, Cache.NoSlidingExpiration);
69         }
70
71         public override void Expire(String key) {
72             HttpRuntime.Cache.Remove(BuildCacheKey(key));
73         }
74
75         internal Boolean Hit(DictionaryEntry entry) {
76             return (entry.Key is String)
77                 && ((String)entry.Key).StartsWith(BuildCacheKey(String.Empty));
78         }
79     }

HttpRuntimeCacheProvider 暴露了一个 internal 修饰的方法,提供给扩展方法调用:

 1     public static class HttpRuntimeCacheProviderExtensions {
 2
 3         public static void ExpireAll(this HttpRuntimeCacheProvider cacheProvider) {
 4             var entries = HttpRuntime.Cache.OfType<DictionaryEntry>()
 5                 .Where(cacheProvider.Hit);
 6             foreach (var entry in entries) {
 7                 HttpRuntime.Cache.Remove((String)entry.Key);
 8             }
 9         }
10
11         public static Int32 Count(this HttpRuntimeCacheProvider cacheProvider) {
12             return HttpRuntime.Cache.OfType<DictionaryEntry>()
13                 .Where(cacheProvider.Hit).Count();
14         }
15
16         public static String Dump(this HttpRuntimeCacheProvider cacheProvider) {
17             var builder = new StringBuilder(1024);
18             builder.AppendLine("--------------------HttpRuntimeCacheProvider.Dump--------------------------");
19             builder.AppendFormat("EffectivePercentagePhysicalMemoryLimit: {0}\r\n", HttpRuntime.Cache.EffectivePercentagePhysicalMemoryLimit);
20             builder.AppendFormat("EffectivePrivateBytesLimit: {0}\r\n", HttpRuntime.Cache.EffectivePrivateBytesLimit);
21             builder.AppendFormat("Count: {0}\r\n", HttpRuntime.Cache.Count);
22             builder.AppendLine();
23             var entries = HttpRuntime.Cache.OfType<DictionaryEntry>().Where(cacheProvider.Hit).OrderBy(de => de.Key);
24             foreach (var entry in entries) {
25                 builder.AppendFormat("{0}\r\n    {1}\r\n", entry.Key, entry.Value.GetType().FullName);
26             }
27             builder.AppendLine("--------------------HttpRuntimeCacheProvider.Dump--------------------------");
28             Debug.WriteLine(builder.ToString());
29             return builder.ToString();
30         }
31     }

考虑到计数、全部过期等功能并不常用,所以这里基本实现功能,并未周全地考虑并发、效率问题;至此功能拆分完成,我们转入 Memcached 实现

Memcached 客户端有相当多的C#实现,这里我选择了 EnyimMemcached,最新版本为2.12,见 https://github.com/enyim/EnyimMemcached 。与 HttpRuntimeCacheProvider 非常类似,从 CacheProvider 继承,实现 IHttpRuntimeCacheProvider, IRegion 接口,完成必要的逻辑即可。

 1     public class MemcachedCacheProvider : CacheProvider, IHttpRuntimeCacheProvider, IRegion {
 2         private static readonly MemcachedClient _client = new MemcachedClient("enyim.com/memcached");
 3
 4         public String Region { get; private set; }
 5
 6         public MemcachedCacheProvider()
 7             : this(String.Empty) {
 8         }
 9
10         public MemcachedCacheProvider(String region) {
11             Region = region;
12         }
13
14         protected override String BuildCacheKey(String key) {
15             return Region == null ? key : String.Concat(Region, "_", key);
16         }
17
18         protected override bool InnerTryGet(string key, out object entry) {
19             return _client.TryGet(key, out entry);
20         }
21
22
23         public T GetOrCreate<T>(String key, Func<T> function, TimeSpan slidingExpiration) {
24             T value;
25             if (TryGet<T>(key, out value)) {
26                 return value;
27             }
28             value = function();
29             Overwrite(key, value, slidingExpiration);
30             return value;
31         }
32
33         public T GetOrCreate<T>(String key, Func<T> function, DateTime absoluteExpiration) {
34             T value;
35             if (TryGet<T>(key, out value)) {
36                 return value;
37             }
38             value = function();
39             Overwrite(key, value, absoluteExpiration);
40             return value;
41         }
42
43         public override void Overwrite<T>(String key, T value) {
44             _client.Store(StoreMode.Set, BuildCacheKey(key), value);
45         }
46
47         //slidingExpiration 时间内无访问则过期
48         public void Overwrite<T>(String key, T value, TimeSpan slidingExpiration) {
49             _client.Store(StoreMode.Set, BuildCacheKey(key), value, slidingExpiration);
50         }
51
52         //absoluteExpiration 时过期
53         public void Overwrite<T>(String key, T value, DateTime absoluteExpiration) {
54             _client.Store(StoreMode.Set, BuildCacheKey(key), value, absoluteExpiration);
55         }
56
57         public override void Expire(String key) {
58             _client.Remove(BuildCacheKey(key));
59         }
60     }

EnyimMemcached 天然支持空缓存项,另外过期时间会因为客户端与服务器时间不严格一致出现测试未通过的情况,它不推荐使用过多的 MemcachedClient 实例,所以此处写成单例形式,另外如何配置等问题,请翻看项目的 Github,本文只使用了最基本的配置,见源代码,更多设置项及解释见 Github

需要注意的是,EnyimMemcached 处理的自定义对象需要使用 [Serializable] 修饰,不然操作无效且不报错,存在产生重大Bug的可能;

最后是工厂类 CacheProviderFactory 的实现,这里从类库项目中排除掉了,即可以是形如 #if DEBUG 类的条件编译,也可以按配置文件来,个人感觉应该在应用中提供统一的入口功能即可。另外 Memcached 的特性本文使用有限,所以未从新接口派生,各位看自己需求扩展既是。

补图:

包含测试用例的源码见 Github , jusfr 原创,转载请注明来自博客园

时间: 2024-08-11 07:38:04

实现一个简单的缓存模块·续, 添加Memcached调用实现的相关文章

一步步实现一个基本的缓存模块

一步步实现一个基本的缓存模块 1. 前言    2.  请求级别缓存    2.1 多线程    3.  进程级别缓存    3.1 分区与计数    3.2 可空缓存值    3.3 封装与集成    4.  小结 1. 前言 面向读者:初.中级用户: 涉及知识:HttpContext.HttpRuime.Cache.DictionaryEntry.Unit Test等: 文章目的:这里的内容不会涉及 Memcached.Redies 等进程外缓存的使用,只针对包含WEB应用的常见场景,实现一

【Nginx】开发一个简单的HTTP模块

首先来分析一下HTTP模块是如何介入Nginx的. 当master进程fork出若干个workr子进程后,每个worker子进程都会在自己的for死循环中不断调用事件模块: for ( ;; ) { .... ngx_process_events_and_timers(cycle); /* 调用事件模块 */ .... } 事件模块检测是否有TCP连接请求,当收到一个SYN包后,由事件模块建立一条TCP连接.连接建立成功后,交由HTTP框架处理,HTTP框架负责接收HTTP头部,并根据头部信息将

哪种缓存效果高?开源一个简单的缓存组件j2cache

背景 现在的web系统已经越来越多的应用缓存技术,而且缓存技术确实是能实足的增强系统性能的.我在项目中也开始接触一些缓存的需求. 开始简单的就用jvm(java托管内存)来做缓存,这样对于单个应用服务器来说很好. 为了系统的可用性,需要做灾备,那么就要多准备一套系统环境,这时就会有一些共享资源的问题,比如Tomcat的session共享出来 几个系统会公用一套缓存数据,这样就变成一个共享池 需求的增长也就带来了系统的变化,也正为这种变化我开始思考怎么让这些代码兼容,并为以后的系统模块提供比较统一

nginx 学习五 filter模块简介和实现一个简单的filter模块

1 nginx过滤模块简介 过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理.它的处理时间在获取回复内容之后, 向用户发送响应之前.它的处理过程分为两个阶段,过滤HTTP回复的头部和主体,在这两个阶段可以分别对头部和主体 进行修改. 2 过滤模块执行顺序 2.1 ngx_http_output_(head, body)_filter_pt 先看一下nginx常用的过滤模块,在ngx_moudles.c中有一下代码: ngx_module_t *ngx_modules

用Verilog语言实现一个简单的MII模块

项目中要求简单地测试一下基于FPGA的模拟平台的RJ45网口,也就是需要实现一个MII或者RMII模块.看了一下官方网口PHY芯片的官方文档,还是感觉上手有点障碍,想在网络上找些参考代码看看,最后只在opencores找到了一些MAC层控制模块,代码庞大且复杂,对于初学者来说阅读起来很困难. 于是在此以一个初学者的角度记录一下我实现一个简单的MII模块的过程,并且指出一些实现过程中要注意的问题.希望可以帮助有需要的朋友. 为了便于测试,我选择了和我们平台使用相同物理芯片的FPGA开发板NEXYS

基于Servlet、JSP、JDBC、MySQL的一个简单的用户注册模块(附完整源码)

最近看老罗视频,做了一个简单的用户注册系统.用户通过网页(JSP)输入用户名.真名和密码,Servlet接收后通过JDBC将信息保存到MySQL中.虽然是个简单的不能再简单的东西,但麻雀虽小,五脏俱全,在此做一归纳和整理.下面先上源码: 一.index.jsp <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <% String path =

Java实现一个简单的缓存方法

缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率.下面我实现了一个简单的缓存,步骤如下. 创建缓存对象EntityCache.java public class EntityCache {   /**    * 保存的数据    */   private Object datas;   /**    * 设置数据失效时间,为0表示永不失效    */   private long time

Python学习第三天(一个简单制作导入模块)

Python一个简单的模块制作和导入 一个简单的模块 [[email protected] python]# cat my.py name = 'I am wuang!' 导入模块 >>> import my >>> print my.name I am wuang! 直接导入模块属性名字 >>> from my import name >>> print name I am wuang!

手把手教你编写一个简单的PHP模块形态的后门

看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web server端的script语言.目前很多web应用程序都基于php语言实现.由于php是个开源软件并易于扩展,所以我们可以通过编写一个PHP模块(module 或者叫扩展 extension)来实现一个Backdoor. 本文就简单介下如何一步步编写一个简单的php 动态扩展后门. 0×01. p