好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Memcached进行缓存层设计

Memcached进行缓存层设计

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

一、公共缓存接口

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

  namespace  DotNet.Common.EnyimCache
{
     /// <summary> 
     /// memcached公共缓存调用方法接口(读) 
     /// </summary> 
     public   interface  ICacheReaderService
    {

         /// <summary> 
         /// 返回指定key的对象 
         /// </summary> 
         /// <param name="key"></param> 
         /// <returns></returns> 
         object  Get( string  key);

         /// <summary> 
         /// 返回指定key的对象 
         /// </summary> 
         /// <typeparam name="T"></typeparam> 
         /// <param name="key"></param> 
         /// <returns></returns> 
        T Get<T>( string  key);

         /// <summary> 
         /// 是否存在 
         /// </summary> 
         /// <param name="key"></param> 
         /// <returns></returns> 
         bool  isExists( string  key);
    }

     /// <summary> 
     /// memcached公共缓存调用方法接口(写) 
     /// </summary> 
     public   interface  ICacheWriterService
    {
         /// <summary> 
         /// 缓存有效间隔时间 (以分钟为单位) 
         /// </summary> 
         int  TimeOut {  set ;  get ; }

         /// <summary> 
         /// 添加指定key的对象 
         /// </summary> 
         /// <param name="key"></param> 
         /// <param name="obj"></param> 
         void  Add( string  key,  object  obj);

         /// <summary> 
         /// 添加指定key的对象 
         /// </summary> 
         /// <typeparam name="T"></typeparam> 
         /// <param name="key"></param> 
         /// <param name="obj"></param> 
         void  Add<T>( string  key, T obj);

         /// <summary> 
         /// 移除指定key的对象 
         /// </summary> 
         /// <param name="key"></param> 
         bool  Remove( string  key);

         /// <summary> 
         /// 修改指定key的对象 
         /// </summary> 
         /// <param name="key"></param> 
         /// <returns></returns> 
         bool  Modify( string  key,  object  destObj);

         /// <summary> 
         /// 清空缓存 
         /// </summary> 
         /// <returns></returns> 
         bool  Release();
    }
}

 

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

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

二、缓存服务实现

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

  using  System;

 namespace  DotNet.Common.EnyimCache
{
     using  Enyim.Caching.Memcached;

     public   class  CacheReaderService : BaseService, ICacheReaderService
    {

         public   int  TimeOut
        {
             get ;
             set ;
        }

         public  CacheReaderService()
        {

        }

         public   object  Get( string  key)
        {
             object  obj =  null ;
            Client.TryGet(key,  out  obj);
             return  obj;
        }

         public  T Get<T>( string  key)
        {
             object  obj = Get(key);
            T result =  default (T);
             if  (obj !=  null )
            {
                result = (T)obj;
            }
             return  result;
        }

         public   bool  isExists( string  key)
        {
             object  obj = Get(key);
             return  (obj ==  null ) ?  false  :  true ;
        }
    }

     public   class  CacheWriterService : BaseService, ICacheWriterService
    {
         public   int  TimeOut
        {
             get ;
             set ;
        }

         public  CacheWriterService()
        {

        }

         public  CacheWriterService( int  timeOut)
        {
             this .TimeOut = timeOut;
        }

         public   void  Add( string  key,  object  obj)
        {
             if  (TimeOut > 0)
            {
                Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
            }
             else 
            {
                Client.Store(StoreMode.Add, key, obj);
            }
        }

         public   void  Add<T>( string  key, T obj)
        {
             if  (TimeOut > 0)
            {
                Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
            }
             else 
            {
                Client.Store(StoreMode.Add, key, obj);
            }
        }

         public   bool  Remove( string  key)
        {
             return  Client.Remove(key);
        }

         public   bool  Modify( string  key,  object  destObj)
        {
             return  Client.Store(StoreMode.Set, key, destObj);
        }

         /// <summary> 
         /// 清空缓存 TO DO 
         /// </summary> 
         /// <returns></returns> 
         public   bool  Release()
        {
             throw   new  NotImplementedException();
        }
    }
}

 

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

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

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

三、简单的读写测试

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

             ICacheWriterService writer = CacheBuilder.GetWriterService(); //writer 使用memcached默认过期时间 
            ICacheReaderService reader = CacheBuilder.GetReaderService(); //reader 

            #region 字符串

             string  strKey = " hello ";

             bool  isOK = writer.Remove(strKey);  //移除 
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            writer.Add(strKey, " hello world ");  //添加 
            Console.WriteLine(" Add key {0}, value:hello world ", strKey);

             bool  isExists = reader.isExists(strKey); //是否存在 
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

             string  result = reader.Get(strKey)  as   string ; //查询 
            Console.WriteLine(" Get key {0}:{1} ", strKey, result);

             bool  isModify = writer.Modify(strKey, " Hello Memcached! "); //修改 
            Console.WriteLine(" Modify key {0}, value:Hello Memcached. The result is:{1} ", strKey, isModify);

            result = reader.Get< string >(strKey);
            Console.WriteLine(" Generic get key {0}:{1} ", strKey, result);

            isOK = writer.Remove(strKey);
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            isExists = reader.isExists(strKey);
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            result = reader.Get(strKey)  as   string ;
            Console.WriteLine(" Get key {0}:{1} ", strKey, result);

            result = reader.Get< string >(strKey);
            Console.WriteLine(" Generic get key {0}:{1} ", strKey, result);
            Console.WriteLine();
            Console.WriteLine(" =========================================== ");
            Console.Read();

            #endregion

            #region 时间

            DateTime dtNow = DateTime.Now;
            strKey = " datetime ";
            isOK = writer.Remove(strKey);  //移除 
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            writer.Add(strKey, dtNow);  //添加 
            Console.WriteLine(" Add key {0}, value:{1} ", strKey, dtNow);

            isExists = reader.isExists(strKey); //是否存在 
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            DateTime dt = (DateTime)reader.Get(strKey); //查询 
            Console.WriteLine(" Get key {0}:{1} ", strKey, dt);

            dt = reader.Get<DateTime>(strKey);
            Console.WriteLine(" Generic get key {0}:{1} ", strKey, dt);

            isOK = writer.Remove(strKey);
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            isExists = reader.isExists(strKey);
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            Console.WriteLine(" Get key {0}:{1} ", strKey, reader.Get(strKey));

            Console.WriteLine(" Generic get key {0}:{1} ", strKey, reader.Get<DateTime>(strKey)); //default(datetime) 
            Console.WriteLine();
            Console.WriteLine(" =========================================== ");

            Console.Read();

            #endregion

            #region 类

            dtNow = DateTime.Now;
            Province province =  new  Province(13579, " 江苏 ", dtNow, dtNow);

            strKey =  string .Format(" {0}_{1} ", province.GetType().Name, province.Id); //省 
            isOK = writer.Remove(strKey);  //移除 
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            writer.Add(strKey, province);  //添加 
            Console.WriteLine(" Add key {0}, value:{1} ", strKey, dtNow);

            isExists = reader.isExists(strKey); //是否存在 
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            Province queryProvince = (Province)reader.Get(strKey); //查询 
            Console.WriteLine(" Get key {0}:{1} ", strKey, queryProvince.ProvinceName);

            queryProvince = reader.Get<Province>(strKey);
            Console.WriteLine(" Generic get key {0}:{1} ", strKey, queryProvince.ProvinceName);

            isOK = writer.Remove(strKey);
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            isExists = reader.isExists(strKey);
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            Console.WriteLine(" Get key {0}:{1} ", strKey, reader.Get(strKey));

            Console.WriteLine(" Generic get key {0}:{1} ", strKey, reader.Get<Province>(strKey));
            Console.WriteLine();
            Console.WriteLine(" =========================================== ");

            Console.Read();

            #endregion

            #region 集合(列表)

            dtNow = DateTime.Now;
            IList<City> listCities =  new  List<City>();
            City city =  new  City(135, province.Id, " 南京 ", " 210000 ", dtNow, dtNow);
            listCities.Add(city);
            city =  new  City(246, province.Id, " 苏州 ", " 215000 ", dtNow, dtNow);
            listCities.Add(city);

            strKey =  string .Format(" List_{0}_{1}_{2} ", province.GetType().Name, province.Id, city.GetType().Name); //省份对应城市 
            isOK = writer.Remove(strKey);  //移除 
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            writer.Add(strKey, listCities);  //添加 
            Console.WriteLine(" Add key {0}, value: ", strKey);
             foreach  (var item  in  listCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Id, item.CityName);
            }

            isExists = reader.isExists(strKey); //是否存在 
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            IList<City> queryCities = reader.Get(strKey)  as  IList<City>; //查询 
            Console.WriteLine(" Get key {0}: ", strKey);
             foreach  (var item  in  queryCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Id, item.CityName);
            }

            queryCities = reader.Get<IList<City>>(strKey);
            Console.WriteLine(" Generic get key {0}: ", strKey);
             foreach  (var item  in  queryCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Id, item.CityName);
            }

            isOK = writer.Remove(strKey);
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            isExists = reader.isExists(strKey);
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            Console.WriteLine(" Get key {0}:{1} ", strKey, reader.Get(strKey));

            Console.WriteLine(" Generic get key {0}:{1} ", strKey, reader.Get<IList<City>>(strKey));
            Console.WriteLine();
            Console.WriteLine(" =========================================== ");

            Console.Read();

            #endregion

            #region 集合(字典)

            dtNow = DateTime.Now;
            IDictionary< int , City> dictCities =  new  Dictionary< int , City>();
            city =  new  City(123, province.Id, " 镇江 ", " 212000 ", dtNow, dtNow);
            dictCities.Add(city.Id, city);
            city =  new  City(321, province.Id, " 扬州 ", " 225000 ", dtNow, dtNow);
            dictCities.Add(city.Id, city);

            strKey =  string .Format(" Dictionary_{0}_{1}_{2} ", province.GetType().Name, province.Id, city.GetType().Name); //省份对应城市 
            isOK = writer.Remove(strKey);  //移除 
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            writer.Add(strKey, dictCities);  //添加 
            Console.WriteLine(" Add key {0}, value: ", strKey);
             foreach  (var item  in  dictCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Key, item.Value.CityName);
            }

            isExists = reader.isExists(strKey); //是否存在 
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            IDictionary< int , City> queryDictCities = reader.Get(strKey)  as  IDictionary< int , City>; //查询 
            Console.WriteLine(" Get key {0}: ", strKey);
             foreach  (var item  in  queryDictCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Key, item.Value.CityName);
            }

            queryDictCities = reader.Get<IDictionary< int , City>>(strKey);
            Console.WriteLine(" Generic get key {0}: ", strKey);
             foreach  (var item  in  queryDictCities)
            {
                Console.WriteLine(" CityId:{0} CityName:{1} ", item.Key, item.Value.CityName);
            }

            isOK = writer.Remove(strKey);
            Console.WriteLine(" Removed key {0}:{1} ", strKey, isOK);

            isExists = reader.isExists(strKey);
            Console.WriteLine(" Key {0} exists:{1} ", strKey, isExists);

            Console.WriteLine(" Get key {0}:{1} ", strKey, reader.Get(strKey));

            Console.WriteLine(" Generic get key {0}:{1} ", strKey, reader.Get<IDictionary< int , City>>(strKey));
            Console.WriteLine();
            Console.WriteLine(" =========================================== ");

            Console.Read();

            #endregion
 

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

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

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

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

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

demo下载: SimpleCacheApp


作者: Jeff Wong
出处: http://jeffwongishandsome.cnblogs.com/
本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Memcached进行缓存层设计的详细内容...

  阅读:41次