zoukankan      html  css  js  c++  java
  • Memcached进行缓存层设计

    正在考虑web应用缓存层的设计,参考了不少资料,估计还是需要用到相对成熟应用广泛的分布式缓存Memcached。在.net平台上早就有相对成熟的Memcached客户端产品,如BeITMemcachedEnyimMemcached,业余时间看了一下源码,自己分析并调用一下并不困难。这里简单介绍一下利用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/
    本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。

  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2109122.html
Copyright © 2011-2022 走看看