缓存子系统如何设计

摘要:
主要的思路分2个:模型端:通过代理来嵌入AOP方法,来判断是否需要缓存,有缓存value则直接返回value;缓存value的写入是通过AOP的后置方法写入的,因此不需要在业务函数中写代码,当然也支持代码调用。
缓存子系统如何设计

大家对这段代码肯定很熟悉吧:

复制代码
public List<UserInfo> SearchUsers(stringuserName)
        {
            string cacheKey=string.Format("SearchUsers_{0}", userName);
            List<UserInfo>  users = cache.Find(cacheKey) as List<UserInfo>;
            if (users == null)
            {
                users =repository.GetUsersByUserName(userName);
                cache.Set(cacheKey, users);
            }
            returnusers;
        }
classHttpRuntimeCache
    {
        public object Find(stringkey)
        {
            returnHttpRuntime.Cache[key];
        }
        public void Set(string key, objectvalue)
        {
            HttpRuntime.Cache[key] =value;
        }
    }
复制代码

导致了如下这些问题:

  1. 业务逻辑函数中引入了很多无关的缓存代码,导致DDD模型不够纯
  2. 更换缓存Provider不方便
  3. 加入缓存冗余机制不方便
  4. 没办法同时使用多个缓存系统
  5. 缓存大对象出现异常,比如Memcache有1M的value限制

有诸多问题,因此我们需要引入缓存子系统来解决上述问题,带来的好处:

  1. DDD模型更加纯
  2. 具体的Cache实现机制可以很灵活,比如HttpRuntimeCache, Memcache, Redis可以同时使用
  3. 加入了Cache冗余机制,不会由于某一台Memcache或者Redis down机导致系统速度很慢,实际上,系统还是会保持飞快(除非backup也down了的情况)
  4. 开发人员更加致力于核心业务,不会分散注意力
  5. 缓存位置透明化,都会在xml配置文件中进行配置

解决方案,要用到这2篇文章的技术:C# 代理应用 - Cachable聊聊Memcached的应用

主要的思路分2个:

模型端:通过代理来嵌入AOP方法,来判断是否需要缓存,有缓存value则直接返回value;缓存value的写入是通过AOP的后置方法写入的,因此不需要在业务函数中写代码,当然也支持代码调用。

Cache核心对象:这个对象要解决一致性hash算法、cache value大对象分解功能、冗余机制

代理嵌入AOP的方法,已经在这篇文章中说明了C# 代理应用 - Cachable,有兴趣的看看,这里就不说了,我们来主要看看CacheCoordinator对象的实现

结构图如下:

缓存子系统如何设计第3张

先来看看UML图:

缓存子系统如何设计第4张

CacheCore代码(算法核心):

复制代码
public classCacheCore
    {
        private ICacheCoordinator cacheProvider = null;
        publicCacheCore(ICacheCoordinator cacheProvider)
        {
            this.cacheProvider =cacheProvider;
        }
        public void Set(string location, string key, objectvalue)
        {
            AssureSerializable(value);
            string xml =Serializer2XMLConvert(value);
            CacheParsedObject parsedObj = newCacheParsedObject();
            string classType = string.Format("{0}", value.GetType().FullName);
            if (xml.Length >CacheConfig.CacheConfiguration.MaxCacheEntitySize)
            {
                /*key:1@3@ConcreteType
                    key_1:subvalue1
                    key_2:subvalue2
                    key_3:subvalue3
                */
                //拆分成更小的单元
                int splitCount = xml.Length /CacheConfig.CacheConfiguration.MaxCacheEntitySize;
                if (CacheConfig.CacheConfiguration.MaxCacheEntitySize * splitCount <xml.Length)
                    splitCount++;
                parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@{0}@{1}", splitCount, classType));
                for (int i = 0; i < splitCount;i++)
                {
                    if (i == splitCount - 1)  //最后一段,直接截取到最后,不用给出长度
                        parsedObj.SplittedElements.Add(xml.Substring(i *CacheConfig.CacheConfiguration.MaxCacheEntitySize));
                    else                      //其他,要给出长度
                        parsedObj.SplittedElements.Add(xml.Substring(i *CacheConfig.CacheConfiguration.MaxCacheEntitySize, CacheConfig.CacheConfiguration.MaxCacheEntitySize));
                }
            }
            else{
                /*key:1@1@ConcreteType
                    key_1:value
                */parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@1@{0}", classType));
                parsedObj.SplittedElements.Add(xml);
            }
            //针对CacheParsedObject进行逐项保存
            this.cacheProvider.Put(parsedObj.MainObject.Key, parsedObj.MainObject.Value);
            int curIndex = 0;
            foreach(string xmlValue inparsedObj.SplittedElements)
            {
                curIndex++;
                string tkey=string.Format("{0}_{1}", parsedObj.MainObject.Key, curIndex);
                this.cacheProvider.Put(tkey, xmlValue);
            }
        }
        public object Get(string location, stringkey)
        {
            string mainObjKeySetting = (string)cacheProvider.Get(key);
            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)
                return null;
            stringclassType;
            CacheParsedObject parsedObj;
            GetParsedObject(key, mainObjKeySetting, out classType, outparsedObj);
            string xmlValue=string.Empty;
            parsedObj.SplittedElements.ForEach(t=>xmlValue+=t);
            using (StringReader rdr = newStringReader(xmlValue))
            {
                //Assembly.Load("Core");
                Type t =Type.GetType(classType);
                XmlSerializer serializer = newXmlSerializer(t);
                returnserializer.Deserialize(rdr);
            }
        }
        public void Remove(string location, stringkey)
        {
            string mainObjKeySetting = (string)cacheProvider.Get(key);
            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)
                return;
            stringclassType;
            CacheParsedObject parsedObj;
            GetParsedObject(key, mainObjKeySetting, out classType, outparsedObj);
            int i = 1;
            parsedObj.SplittedElements.ForEach(t => this.cacheProvider.Remove(string.Format("{0}_{1}", parsedObj.MainObject.Key, i++)));
            this.cacheProvider.Remove(parsedObj.MainObject.Key);
        }
        private void GetParsedObject(string key, string mainObjKeySetting, out string classType, outCacheParsedObject parsedObj)
        {
            int from = 1, end = 1;
            classType = string.Empty;
            if (mainObjKeySetting.IndexOf('@') > 0)
            {
                end = int.Parse(mainObjKeySetting.Split('@')[1]);
                classType = mainObjKeySetting.Split('@')[2];
            }
            parsedObj = newCacheParsedObject();
            parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@{0}@{1}", end, classType));
            for (int i = from; i <= end; i++)
                parsedObj.SplittedElements.Add((string)this.cacheProvider.Get(string.Format("{0}_{1}", parsedObj.MainObject.Key, i)));
        }
        private string Serializer2XMLConvert(objectvalue)
        {
            using (StringWriter sw = newStringWriter())
            {
                XmlSerializer xz = newXmlSerializer(value.GetType());
                xz.Serialize(sw, value);
                returnsw.ToString();
            } 
        }
        private void AssureSerializable(objectvalue)
        {
            if (value == null)
                throw new Exception("cache object must be Serializable");
            if (value.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Count()<=0)
                throw new Exception("cache object must be Serializable");
        }
    }
复制代码

下面是CacheCoordinator的代码,这个类的加入目的是要加入缓存的冗余机制:

复制代码
classCacheCoordinator : ICacheCoordinator
    {
        CacheServerWrapper backupCacheServer = newCacheServerWrapper(CacheConfig.CacheConfiguration.BackupCacheServer);
        CacheServersWrapper peerCacheServer = newCacheServersWrapper(CacheConfig.CacheConfiguration.PeerCacheServers);
        public void Put(string key, objectvalue)
        {
            peerCacheServer.Put(key, value); 
            backupCacheServer.Put(key, value); //缓存冗余
        }
        public object Get(stringkey)
        {
            object o=peerCacheServer.Get(key);
            if (o != null)
                returno;
            returnbackupCacheServer.Get(key);
        }
        public void Remove(stringkey)
        {
            peerCacheServer.Remove(key);
            backupCacheServer.Remove(key);
        }
    }
复制代码

剩下的就是具体的CacheProvider和CacheProviderWrapper类了:

复制代码
public classCacheServerWrapper : ICacheExecutor
    {
        ICacheExecutor executor = null;
        privateCacheServerInfo configInfo;
        publicCacheServerWrapper(CacheServerInfo configInfo)
        {
            this.configInfo =configInfo;
            ICacheExecutor tmpExecutor = null;
            switch(this.configInfo.ServerType)
            {
                caseCacheServerType.HttpRuntime:
                    tmpExecutor = newCacheProvider.HttpRuntimeCacheProvider(configInfo);
                    break;
                caseCacheServerType.InMemory:
                    tmpExecutor = newCacheProvider.InMemoryCacheProvider(configInfo);
                    break;
                caseCacheServerType.Memcached:
                    tmpExecutor = newCacheProvider.MemcachedCacheProvider(configInfo);
                    break;
                caseCacheServerType.Redis:
                    tmpExecutor = newCacheProvider.RedisCacheProvider(configInfo);
                    break;
                default:
                    tmpExecutor = newCacheProvider.HttpRuntimeCacheProvider(configInfo);
                    break;
            }
            executor =tmpExecutor;
        }
        public stringFullServerAddress
        {
            get{
                return this.configInfo.FullServerAddress;
            }
        }
        public void Put(string key, objectvalue)
        {
            executor.Put(key, value);
        }
        public object Get(stringkey)
        {
            returnexecutor.Get(key);
        }
        public void Remove(stringkey)
        {
            executor.Remove(key);
        }
    }
复制代码

只贴出Memcache的操作类

复制代码
classMemcachedCacheProvider : ICacheExecutor
    {
        private MemcachedClient mc = newMemcachedClient();
        privateCacheServerInfo configInfo;
        publicMemcachedCacheProvider(CacheServerInfo configInfo)
        {
            this.configInfo =configInfo;
            //初始化池  
            SockIOPool pool =SockIOPool.GetInstance();
            pool.SetServers(new string[] { string.Format("{0}:{1}", configInfo.ServerAddress, configInfo.ServerPort) });//设置连接池可用的cache服务器列表,server的构成形式是IP:PORT(如:127.0.0.1:11211)  
            pool.InitConnections = 3;//初始连接数  
            pool.MinConnections = 3;//最小连接数  
            pool.MaxConnections = 5;//最大连接数  
            pool.SocketConnectTimeout = 1000;//设置连接的套接字超时  
            pool.SocketTimeout = 3000;//设置套接字超时读取  
            pool.MaintenanceSleep = 30;//设置维护线程运行的睡眠时间。如果设置为0,那么维护线程将不会启动,30就是每隔30秒醒来一次  
            //获取或设置池的故障标志。  
            //如果这个标志被设置为true则socket连接失败,将试图从另一台服务器返回一个套接字如果存在的话。  
            //如果设置为false,则得到一个套接字如果存在的话。否则返回NULL,如果它无法连接到请求的服务器。  
            pool.Failover = true;
            pool.Nagle = false;//如果为false,对所有创建的套接字关闭Nagle的算法  
pool.Initialize();
        }
        public void Put(string key, objectvalue)
        {
            mc.Set(key, value);
        }
        public object Get(stringkey)
        {
            returnmc.Get(key);
        }
        public void Remove(stringkey)
        {
            mc.Delete(key);
        }
    }
复制代码

不能忘了可配置性,xml定义及代码如下:

复制代码
<?xml version="1.0" encoding="utf-8" ?>
<CacheConfig>
  <MaxCacheEntitySize>1048576</MaxCacheEntitySize><!--1*1024*1024-->
  <PeerCacheServers>
    <CacheServer>
      <ServerType>InMemory</ServerType>
      <ServerAddress>127.0.0.1</ServerAddress>
      <ServerPort>11211</ServerPort>
    </CacheServer>
    <CacheServer>
      <ServerType>InMemory</ServerType>
      <ServerAddress>127.0.0.1</ServerAddress>
      <ServerPort>11212</ServerPort>
    </CacheServer>
  </PeerCacheServers>
  <BackupCacheServer>
    <CacheServer>
      <ServerType>InMemory</ServerType>
      <ServerAddress>127.0.0.1</ServerAddress>
      <ServerPort>11213</ServerPort>
    </CacheServer>
  </BackupCacheServer>
</CacheConfig>
复制代码

读取配置信息的代码:

复制代码
public static classCacheConfiguration
    {
        staticCacheConfiguration()
        {
            Load();
        }
        private static voidLoad()
        {
            PeerCacheServers = new List<CacheServerInfo>();
            BackupCacheServer = null;
            XElement root = XElement.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CacheConfig.xml"));
            MaxCacheEntitySize = int.Parse(root.Element("MaxCacheEntitySize").Value);
            foreach (var elm in root.Element("PeerCacheServers").Elements("CacheServer"))
            {
                CacheServerInfo srv = newCacheServerInfo();
                srv.ServerAddress = elm.Element("ServerAddress").Value;
                srv.ServerPort = int.Parse(elm.Element("ServerPort").Value);
                srv.ServerType = (CacheServerType)Enum.Parse(typeof(CacheServerType), elm.Element("ServerType").Value);
                PeerCacheServers.Add(srv);
            }
            foreach (var elm in root.Element("BackupCacheServer").Elements("CacheServer"))
            {
                CacheServerInfo srv = newCacheServerInfo();
                srv.ServerAddress = elm.Element("ServerAddress").Value;
                srv.ServerPort = int.Parse(elm.Element("ServerPort").Value);
                srv.ServerType = (CacheServerType)Enum.Parse(typeof(CacheServerType), elm.Element("ServerType").Value);
                BackupCacheServer =srv;
                break;
            }
            if (PeerCacheServers.Count <= 0)
                throw new Exception("Peer cache servers not found.");
            if (BackupCacheServer == null)
                throw new Exception("Backup cache server not found.");
            AssureDistinctFullServerAddress(PeerCacheServers);
        }
        private static void AssureDistinctFullServerAddress(List<CacheServerInfo>css)
        {
            Dictionary<string, int> map = new Dictionary<string, int>();
            foreach(CacheServerInfo csInfo incss)
            {
                if(map.ContainsKey(csInfo.FullServerAddress))
                    throw new Exception(string.Format("Duplicated server address found [{0}].", csInfo.FullServerAddress));
                elsemap[csInfo.FullServerAddress] = 1;
            }
        }
        public static int MaxCacheEntitySize { get; set; }
        public static List<CacheServerInfo> PeerCacheServers { get; set; }
        public static CacheServerInfo BackupCacheServer { get; set; }
    }
复制代码

代码下载

自省推动进步,视野决定未来。
心怀远大理想。
为了家庭幸福而努力。
用A2D科技,服务社会。

免责声明:文章转载自《缓存子系统如何设计》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C语言中怎么求动态数组大小ROS学习之参数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

string易错点整理总结

简单说 string 就是char[],本质是一个16位Unicode字符数组,在托管堆,不在GC堆 string 和System.String string 是C#语言的基元类型,类似于int,long等等,简化了语言代码,带来便捷可读性,System.String是FCL的基本类型,和有直接的映射关系,从IL角度看,两者之间没有任何不同 恒定性:...

Json与List的相互转换

问题由来: 最近由于做一个项目,项目的一个功能就是根据Listview的内容生成一个二维码,然后扫描二维码获取list,再重新显示listview。 核心就是: list—->生成二维码——>获取二维码—–>获取list 生成二维码的方法: http://blog.csdn.net/demonliuhui/article/details/...

FTP文件上传下载(C#)

下面是ftp上传下载工具,不能直接运行,请删除不必要的代码。 /// <summary> /// ftp文件上传下载 /// </summary> public class FtpHelper { private string FtpServer; private st...

Java用SAX解析XML

要解析的XML文件:myClass.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <class> 3 <stu id="001"> 4 <name>Allen</name> 5 <sex>男</sex> 6 &l...

Spring Boot 异步请求和异步调用,一文搞定

一、Spring Boot中异步请求的使用 1、异步请求与同步请求     特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。 一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通...

C++中stringstream的使用方法和样例

  之前在leetcode中进行string和int的转化时使用过istringstream,现在大致总结一下用法和测试用例。     介绍:C++引入了ostringstream、istringstream、stringstream这三个类,要使用他们创建对象就必须包含sstream.h头文件。 istringstream类用于执行C++风格的串流的输...