缓存子系统如何设计

来源:互联网 发布:linux系统扫描工具 编辑:程序博客网 时间:2024/05/16 09:14

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

public List<UserInfo> SearchUsers(string userName)        {            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);            }            return users;        }class HttpRuntimeCache    {        public object Find(string key)        {            return HttpRuntime.Cache[key];        }        public void Set(string key, object value)        {            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对象的实现

结构图如下:

先来看看UML图:

CacheCore代码(算法核心):

public class CacheCore    {        private ICacheCoordinator cacheProvider = null;        public CacheCore(ICacheCoordinator cacheProvider)        {            this.cacheProvider = cacheProvider;        }        public void Set(string location, string key, object value)        {            AssureSerializable(value);            string xml = Serializer2XMLConvert(value);            CacheParsedObject parsedObj = new CacheParsedObject();            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 in parsedObj.SplittedElements)            {                curIndex++;                string tkey=string.Format("{0}_{1}", parsedObj.MainObject.Key, curIndex);                this.cacheProvider.Put(tkey, xmlValue);            }        }        public object Get(string location, string key)        {            string mainObjKeySetting = (string)cacheProvider.Get(key);            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)                return null;            string classType;            CacheParsedObject parsedObj;            GetParsedObject(key, mainObjKeySetting, out classType, out parsedObj);            string xmlValue=string.Empty;            parsedObj.SplittedElements.ForEach(t=>xmlValue+=t);            using (StringReader rdr = new StringReader(xmlValue))            {                //Assembly.Load("Core");                Type t = Type.GetType(classType);                XmlSerializer serializer = new XmlSerializer(t);                return serializer.Deserialize(rdr);            }        }        public void Remove(string location, string key)        {            string mainObjKeySetting = (string)cacheProvider.Get(key);            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)                return;            string classType;            CacheParsedObject parsedObj;            GetParsedObject(key, mainObjKeySetting, out classType, out parsedObj);            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, out CacheParsedObject parsedObj)        {            int from = 1, end = 1;            classType = string.Empty;            if (mainObjKeySetting.IndexOf('@') > 0)            {                end = int.Parse(mainObjKeySetting.Split('@')[1]);                classType = mainObjKeySetting.Split('@')[2];            }            parsedObj = new CacheParsedObject();            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(object value)        {            using (StringWriter sw = new StringWriter())            {                XmlSerializer xz = new XmlSerializer(value.GetType());                xz.Serialize(sw, value);                return sw.ToString();            }         }        private void AssureSerializable(object value)        {            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的代码,这个类的加入目的是要加入缓存的冗余机制:

class CacheCoordinator : ICacheCoordinator    {        CacheServerWrapper backupCacheServer = new CacheServerWrapper(CacheConfig.CacheConfiguration.BackupCacheServer);        CacheServersWrapper peerCacheServer = new CacheServersWrapper(CacheConfig.CacheConfiguration.PeerCacheServers);        public void Put(string key, object value)        {            peerCacheServer.Put(key, value);             backupCacheServer.Put(key, value); //缓存冗余        }        public object Get(string key)        {            object o=peerCacheServer.Get(key);            if (o != null)                return o;            return backupCacheServer.Get(key);        }        public void Remove(string key)        {            peerCacheServer.Remove(key);            backupCacheServer.Remove(key);        }    }

 

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

public class CacheServerWrapper : ICacheExecutor    {        ICacheExecutor executor = null;        private CacheServerInfo configInfo;        public CacheServerWrapper(CacheServerInfo configInfo)        {            this.configInfo = configInfo;            ICacheExecutor tmpExecutor = null;            switch(this.configInfo.ServerType)            {                case CacheServerType.HttpRuntime:                    tmpExecutor = new CacheProvider.HttpRuntimeCacheProvider(configInfo);                    break;                case CacheServerType.InMemory:                    tmpExecutor = new CacheProvider.InMemoryCacheProvider(configInfo);                    break;                case CacheServerType.Memcached:                    tmpExecutor = new CacheProvider.MemcachedCacheProvider(configInfo);                    break;                case CacheServerType.Redis:                    tmpExecutor = new CacheProvider.RedisCacheProvider(configInfo);                    break;                default:                    tmpExecutor = new CacheProvider.HttpRuntimeCacheProvider(configInfo);                    break;            }            executor = tmpExecutor;        }        public string FullServerAddress        {            get            {                return this.configInfo.FullServerAddress;            }        }        public void Put(string key, object value)        {            executor.Put(key, value);        }        public object Get(string key)        {            return executor.Get(key);        }        public void Remove(string key)        {            executor.Remove(key);        }    }

 

只贴出Memcache的操作类

class MemcachedCacheProvider : ICacheExecutor    {        private MemcachedClient mc = new MemcachedClient();        private CacheServerInfo configInfo;        public MemcachedCacheProvider(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, object value)        {            mc.Set(key, value);        }        public object Get(string key)        {            return mc.Get(key);        }        public void Remove(string key)        {            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 class CacheConfiguration    {        static CacheConfiguration()        {            Load();        }        private static void Load()        {            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 = new CacheServerInfo();                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 = new CacheServerInfo();                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 in css)            {                if (map.ContainsKey(csInfo.FullServerAddress))                    throw new Exception(string.Format("Duplicated server address found [{0}].", csInfo.FullServerAddress));                else                    map[csInfo.FullServerAddress] = 1;            }        }        public static int MaxCacheEntitySize { get; set; }        public static List<CacheServerInfo> PeerCacheServers { get; set; }        public static CacheServerInfo BackupCacheServer { get; set; }    }

 

 

代码下载

 

 


<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 平板电脑忘记开机密码怎么办 平板电脑忘了开机密码怎么办 qq文件已被损坏怎么办 斗地主没痘了怎么办 熹妃q传金币不够用怎么办 苹果手机玩王者卡怎么办 苹果6玩王者荣耀卡怎么办 苹果macbook开不了机怎么办 苹果7震动像拖拉机一样怎么办 win10笔记本玩lol卡怎么办 苹果笔记本密码忘了怎么办 苹果笔记本系统密码忘记了怎么办 qq加好友频繁了怎么办 淘宝买食品有问题怎么办 手机的设置图标没有了怎么办 国家创业贷款还不了会怎么办 手机mac显示:不好使.怎么办? 英雄联盟买皮肤重复怎么办 皮肤很油毛孔又粗怎么办 笔记本电脑玩英雄联盟卡怎么办 win10系统更新不动了怎么办 win7任务栏时间没了怎么办 win10桌面图标都没了怎么办 win10软件图标没了怎么办 电脑内存插板没用了怎么办 win10笔记本开不了机怎么办 cad复制东西变卡怎么办 企业网银证书过期怎么办 游戏更新网页无法正常打开怎么办 网页游戏打开说脚本错误怎么办 电脑玩游戏出现闪屏怎么办 玩游戏时出现窗口化怎么办 玩游戏时出现输入不支持怎么办 电脑玩游戏出现蓝屏怎么办 谷歌商店网页版进不去怎么办 谷歌商店为什么打不开怎么办 玩lol突然卡顿怎么办 手机上路由器管理页面打不开怎么办 苹果电脑开机页面密码打不开怎么办 逆战活动页面打不开怎么办 电脑玩lol网络卡怎么办