• 缓存子系统如何设计


    缓存子系统如何设计

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

    复制代码
    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; }
        }
    复制代码

    代码下载

    自省推动进步,视野决定未来。
    心怀远大理想。
    为了家庭幸福而努力。
    用A2D科技,服务社会。
     
    分类: AOP架构可扩展
  • 相关阅读:
    HDU 1850 Being a Good Boy in Spring Festival
    UESTC 1080 空心矩阵
    HDU 2491 Priest John's Busiest Day
    UVALive 6181
    ZOJ 2674 Strange Limit
    UVA 12532 Interval Product
    UESTC 1237 质因子分解
    UESTC 1014 Shot
    xe5 android listbox的 TMetropolisUIListBoxItem
    xe5 android tts(Text To Speech)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3225933.html
Copyright © 2020-2023  润新知