热门:网页模板.net视频教程JQueryMVCjsonExtJs源码示例三级联动JQuery菜单
您现在的位置:.Net中文社区>> .Net编程>>正文内容

简单利用Memcached进行缓存层设计

发布时间:2011年07月18日点击数: 佚名

正在考虑web应用缓存层的设计,参考了不少资料,估计还是需要用到相对成熟应用广泛的分布式缓存Memcached。在.net平台上早就有相对成熟的Memcached客户端产品,如BeITMemcachedEnyimMemcached,业余时间看了一下源码,自己分析并调用一下并不困难。这里简单介绍一下利用Memcached的一个简单的缓存层设计,示例代码基于EnyimMemcached,下面以贴代码为主。

一、公共缓存接口【DEMO下载

分析asp.net web caching的缓存类,我们大致可以抽象出如下几个接口方法:

  1. namespace DotNet.Common.EnyimCache 
  2.     /// <summary> 
  3.     /// memcached公共缓存调用方法接口(读) 
  4.     /// </summary> 
  5.     public interface ICacheReaderService 
  6.     { 
  7.  
  8.         /// <summary> 
  9.         /// 返回指定key的对象 
  10.         /// </summary> 
  11.         /// <param name="key"></param> 
  12.         /// <returns></returns> 
  13.         object Get(string key); 
  14.  
  15.         /// <summary> 
  16.         /// 返回指定key的对象 
  17.         /// </summary> 
  18.         /// <typeparam name="T"></typeparam> 
  19.         /// <param name="key"></param> 
  20.         /// <returns></returns> 
  21.         T Get<T>(string key); 
  22.  
  23.         /// <summary> 
  24.         /// 是否存在 
  25.         /// </summary> 
  26.         /// <param name="key"></param> 
  27.         /// <returns></returns> 
  28.         bool isExists(string key); 
  29.     } 
  30.  
  31.     /// <summary> 
  32.     /// memcached公共缓存调用方法接口(写) 
  33.     /// </summary> 
  34.     public interface ICacheWriterService 
  35.     { 
  36.         /// <summary> 
  37.         /// 缓存有效间隔时间 (以分钟为单位) 
  38.         /// </summary> 
  39.         int TimeOut { setget; } 
  40.  
  41.         /// <summary> 
  42.         /// 添加指定key的对象 
  43.         /// </summary> 
  44.         /// <param name="key"></param> 
  45.         /// <param name="obj"></param> 
  46.         void Add(string key, object obj); 
  47.  
  48.         /// <summary> 
  49.         /// 添加指定key的对象 
  50.         /// </summary> 
  51.         /// <typeparam name="T"></typeparam> 
  52.         /// <param name="key"></param> 
  53.         /// <param name="obj"></param> 
  54.         void Add<T>(string key, T obj); 
  55.  
  56.         /// <summary> 
  57.         /// 移除指定key的对象 
  58.         /// </summary> 
  59.         /// <param name="key"></param> 
  60.         bool Remove(string key); 
  61.  
  62.         /// <summary> 
  63.         /// 修改指定key的对象 
  64.         /// </summary> 
  65.         /// <param name="key"></param> 
  66.         /// <returns></returns> 
  67.         bool Modify(string key, object destObj); 
  68.  
  69.         /// <summary> 
  70.         /// 清空缓存 
  71.         /// </summary> 
  72.         /// <returns></returns> 
  73.         bool Release(); 
  74.     } 

看命名就知道,增删改查是也。根据个人使用缓存的经验,修改操作通常是不需要的,如果确实需要修改缓存数据,直接删除然后添加就是改了。

还有,你可能会问,这里为什么要定义两个接口?原因主要是考虑到读操作(查询)是经常使用的,而写操作(增删改)相对较少,所以也把它们设计成读写分离的方式。

二、缓存服务实现

这里就需要调用Memcached客户端封装好的调用方法,实现增删改查等方法。

  1. using System; 
  2.  
  3. namespace DotNet.Common.EnyimCache 
  4.     using Enyim.Caching.Memcached; 
  5.  
  6.     public class CacheReaderService : BaseService, ICacheReaderService 
  7.     { 
  8.  
  9.         public int TimeOut 
  10.         { 
  11.             get
  12.             set
  13.         } 
  14.  
  15.         public CacheReaderService() 
  16.         { 
  17.  
  18.         } 
  19.  
  20.         public object Get(string key) 
  21.         { 
  22.             object obj = null
  23.             Client.TryGet(key, out obj); 
  24.             return obj; 
  25.         } 
  26.  
  27.         public T Get<T>(string key) 
  28.         { 
  29.             object obj = Get(key); 
  30.             T result = default(T); 
  31.             if (obj != null
  32.             { 
  33.                 result = (T)obj; 
  34.             } 
  35.             return result; 
  36.         } 
  37.  
  38.         public bool isExists(string key) 
  39.         { 
  40.             object obj = Get(key); 
  41.             return (obj == null) ? false : true
  42.         } 
  43.     } 
  44.  
  45.     public class CacheWriterService : BaseService, ICacheWriterService 
  46.     { 
  47.         public int TimeOut 
  48.         { 
  49.             get
  50.             set
  51.         } 
  52.  
  53.         public CacheWriterService() 
  54.         { 
  55.  
  56.         } 
  57.  
  58.         public CacheWriterService(int timeOut) 
  59.         { 
  60.             this.TimeOut = timeOut; 
  61.         } 
  62.  
  63.         public void Add(string key, object obj) 
  64.         { 
  65.             if (TimeOut > 0) 
  66.             { 
  67.                 Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut)); 
  68.             } 
  69.             else 
  70.             { 
  71.                 Client.Store(StoreMode.Add, key, obj); 
  72.             } 
  73.         } 
  74.  
  75.         public void Add<T>(string key, T obj) 
  76.         { 
  77.             if (TimeOut > 0) 
  78.             { 
  79.                 Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut)); 
  80.             } 
  81.             else 
  82.             { 
  83.                 Client.Store(StoreMode.Add, key, obj); 
  84.             } 
  85.         } 
  86.  
  87.         public bool Remove(string key) 
  88.         { 
  89.             return Client.Remove(key); 
  90.         } 
  91.  
  92.         public bool Modify(string key, object destObj) 
  93.         { 
  94.             return Client.Store(StoreMode.Set, key, destObj); 
  95.         } 
  96.  
  97.         /// <summary> 
  98.         /// 清空缓存 TO DO 
  99.         /// </summary> 
  100.         /// <returns></returns> 
  101.         public bool Release() 
  102.         { 
  103.             throw new NotImplementedException(); 
  104.         } 
  105.     } 

基类里初始化一个MemcachedClient示例Client,这个Client的方法里封装了较多的函数。查看源码可以知道,它们本质上都是 向Memcached服务端发送相关指令(run command),然后解析返回的二进制数据,如果您熟悉memcached所使用的协议,理解起来应该会相当简单。本文示例只使用了客户端提供的几个方 法。

同时要注意,在实现具体缓存服务的时候,CacheWriterService有两个构造函数,其中带参数的是为缓存显式指定过期时间。这个参数在实际应用中通常需要配置,显然是比较灵活一些的。

备注:在接口中有一个函数Release,本来的目标是清空所有的缓存数据,但是客户端没有直接提供对应的函数,如果您有好的方法,请不吝赐教。

三、简单的读写测试

贴一下字符串、时间、单个类和集合的增删改查示例代码:

  1. ICacheWriterService writer = CacheBuilder.GetWriterService();//writer 使用memcached默认过期时间 
  2. ICacheReaderService reader = CacheBuilder.GetReaderService();//reader 
  3.  
  4. #region 字符串 
  5.  
  6. string strKey = "hello"
  7.  
  8. bool isOK = writer.Remove(strKey); //移除 
  9. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  10.  
  11. writer.Add(strKey, "hello world"); //添加 
  12. Console.WriteLine("Add key {0}, value:hello world", strKey); 
  13.  
  14. bool isExists = reader.isExists(strKey);//是否存在 
  15. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  16.  
  17. string result = reader.Get(strKey) as string;//查询 
  18. Console.WriteLine("Get key {0}:{1}", strKey, result); 
  19.  
  20. bool isModify = writer.Modify(strKey, "Hello Memcached!");//修改 
  21. Console.WriteLine("Modify key {0}, value:Hello Memcached. The result is:{1}", strKey, isModify); 
  22.  
  23. result = reader.Get<string>(strKey); 
  24. Console.WriteLine("Generic get key {0}:{1}", strKey, result); 
  25.  
  26. isOK = writer.Remove(strKey); 
  27. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  28.  
  29. isExists = reader.isExists(strKey); 
  30. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  31.  
  32. result = reader.Get(strKey) as string
  33. Console.WriteLine("Get key {0}:{1}", strKey, result); 
  34.  
  35. result = reader.Get<string>(strKey); 
  36. Console.WriteLine("Generic get key {0}:{1}", strKey, result); 
  37. Console.WriteLine(); 
  38. Console.WriteLine("==========================================="); 
  39. Console.Read(); 
  40.  
  41. #endregion 
  42.  
  43. #region 时间 
  44.  
  45. DateTime dtNow = DateTime.Now; 
  46. strKey = "datetime"
  47. isOK = writer.Remove(strKey); //移除 
  48. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  49.  
  50. writer.Add(strKey, dtNow); //添加 
  51. Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow); 
  52.  
  53. isExists = reader.isExists(strKey);//是否存在 
  54. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  55.  
  56. DateTime dt = (DateTime)reader.Get(strKey);//查询 
  57. Console.WriteLine("Get key {0}:{1}", strKey, dt); 
  58.  
  59. dt = reader.Get<DateTime>(strKey); 
  60. Console.WriteLine("Generic get key {0}:{1}", strKey, dt); 
  61.  
  62. isOK = writer.Remove(strKey); 
  63. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  64.  
  65. isExists = reader.isExists(strKey); 
  66. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  67.  
  68. Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey)); 
  69.  
  70. Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<DateTime>(strKey));//default(datetime) 
  71. Console.WriteLine(); 
  72. Console.WriteLine("==========================================="); 
  73.  
  74. Console.Read(); 
  75.  
  76. #endregion 
  77.  
  78. #region 类 
  79.  
  80. dtNow = DateTime.Now; 
  81. Province province = new Province(13579, "江苏", dtNow, dtNow); 
  82.  
  83. strKey = string.Format("{0}_{1}", province.GetType().Name, province.Id);//省 
  84. isOK = writer.Remove(strKey); //移除 
  85. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  86.  
  87. writer.Add(strKey, province); //添加 
  88. Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow); 
  89.  
  90. isExists = reader.isExists(strKey);//是否存在 
  91. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  92.  
  93. Province queryProvince = (Province)reader.Get(strKey);//查询 
  94. Console.WriteLine("Get key {0}:{1}", strKey, queryProvince.ProvinceName); 
  95.  
  96. queryProvince = reader.Get<Province>(strKey); 
  97. Console.WriteLine("Generic get key {0}:{1}", strKey, queryProvince.ProvinceName); 
  98.  
  99. isOK = writer.Remove(strKey); 
  100. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  101.  
  102. isExists = reader.isExists(strKey); 
  103. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  104.  
  105. Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey)); 
  106.  
  107. Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<Province>(strKey)); 
  108. Console.WriteLine(); 
  109. Console.WriteLine("==========================================="); 
  110.  
  111. Console.Read(); 
  112.  
  113. #endregion 
  114.  
  115. #region 集合(列表) 
  116.  
  117. dtNow = DateTime.Now; 
  118. IList<City> listCities = new List<City>(); 
  119. City city = new City(135, province.Id, "南京""210000", dtNow, dtNow); 
  120. listCities.Add(city); 
  121. city = new City(246, province.Id, "苏州""215000", dtNow, dtNow); 
  122. listCities.Add(city); 
  123.  
  124. strKey = string.Format("List_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市 
  125. isOK = writer.Remove(strKey); //移除 
  126. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  127.  
  128. writer.Add(strKey, listCities); //添加 
  129. Console.WriteLine("Add key {0}, value:", strKey); 
  130. foreach (var item in listCities) 
  131.     Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName); 
  132.  
  133. isExists = reader.isExists(strKey);//是否存在 
  134. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  135.  
  136. IList<City> queryCities = reader.Get(strKey) as IList<City>;//查询 
  137. Console.WriteLine("Get key {0}:", strKey); 
  138. foreach (var item in queryCities) 
  139.     Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName); 
  140.  
  141. queryCities = reader.Get<IList<City>>(strKey); 
  142. Console.WriteLine("Generic get key {0}:", strKey); 
  143. foreach (var item in queryCities) 
  144.     Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName); 
  145.  
  146. isOK = writer.Remove(strKey); 
  147. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  148.  
  149. isExists = reader.isExists(strKey); 
  150. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  151.  
  152. Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey)); 
  153.  
  154. Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<IList<City>>(strKey)); 
  155. Console.WriteLine(); 
  156. Console.WriteLine("==========================================="); 
  157.  
  158. Console.Read(); 
  159.  
  160. #endregion 
  161.  
  162. #region 集合(字典) 
  163.  
  164. dtNow = DateTime.Now; 
  165. IDictionary<int, City> dictCities = new Dictionary<int, City>(); 
  166. city = new City(123, province.Id, "镇江""212000", dtNow, dtNow); 
  167. dictCities.Add(city.Id, city); 
  168. city = new City(321, province.Id, "扬州""225000", dtNow, dtNow); 
  169. dictCities.Add(city.Id, city); 
  170.  
  171. strKey = string.Format("Dictionary_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市 
  172. isOK = writer.Remove(strKey); //移除 
  173. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  174.  
  175. writer.Add(strKey, dictCities); //添加 
  176. Console.WriteLine("Add key {0}, value:", strKey); 
  177. foreach (var item in dictCities) 
  178.     Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName); 
  179.  
  180. isExists = reader.isExists(strKey);//是否存在 
  181. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  182.  
  183. IDictionary<int, City> queryDictCities = reader.Get(strKey) as IDictionary<int, City>;//查询 
  184. Console.WriteLine("Get key {0}:", strKey); 
  185. foreach (var item in queryDictCities) 
  186.     Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName); 
  187.  
  188. queryDictCities = reader.Get<IDictionary<int, City>>(strKey); 
  189. Console.WriteLine("Generic get key {0}:", strKey); 
  190. foreach (var item in queryDictCities) 
  191.     Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName); 
  192.  
  193. isOK = writer.Remove(strKey); 
  194. Console.WriteLine("Removed key {0}:{1}", strKey, isOK); 
  195.  
  196. isExists = reader.isExists(strKey); 
  197. Console.WriteLine("Key {0} exists:{1}", strKey, isExists); 
  198.  
  199. Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey)); 
  200.  
  201. Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<IDictionary<int, City>>(strKey)); 
  202. Console.WriteLine(); 
  203. Console.WriteLine("==========================================="); 
  204.  
  205. Console.Read(); 
  206.  
  207. #endregion 

这里就不贴全部代码了,文章最后有示例可以下载。

在我的简单测试中,对常见基础数据类型如(字符串、数组、数字和时间)、集合(列表和字典)都有良好的表现,对datatable和dataset同样表现不俗,但是不太建议直接缓存这两种重粒度的类型。

在显式指定过期时间的示例中,指定过期时间是一分钟,但是memcached实际过期时间有时候好像会多于一分钟,估计是系统内部的延迟。

在本地计算机上进行10万次循环添加缓存的过程中,发现系统内存果然增加的非常厉害。然后查询性能并没有显著下降,也许和我的单机测试环境有关,所以我认为测试结果并没有说服力,要知道,memcached的优势是它的分布式缓存实现。

有人发现如何保证缓存系统的键唯一也非常令人头疼。同样的缓存框架,不同项目不同开发者如何保证自己程序添加的缓存键唯一呢?有一种简单方法就是通 过拼接字符串成为有意义的主键,比如按照项目名、命名空间、类名、数据库中的主键组合构成主键等等。当然了,在查询的时候也要自己封装特定格式的字符串主 键。个人感觉确实是一个行之有效的方法。

本站热点业务

更多模板/案例展示

关于我们 | 联系我们 | 团队日志 | 网站地图 | 网站合作