在Discuz!NT中进行缓存分层(本地缓存+memcached)(转)

在以前的两篇文章(Discuz!NT 缓存设计简析Discuz!NT中集成Memcached分布式缓存)中,介绍了Discuz!NT中的缓存设计思路以及如何引入Memcached,当然前者是IIS进程的缓存(本地缓存),后者是分布式内存对象缓存系统。

两者通过Discuz!NT中的memcached.config文件中的ApplyMemCached结点的值来决定使用哪一种缓存方式。不过在之后,有朋友反映当使用Memcached时,特别是在大并发来时,效率会打折扣,甚至有很多时间会消耗在socket套接字(创建和传输方面)上。而事实上也的确如此,尽管Memcached在使用池化的方式初始化一定数量的套接字资源(之前测试时实始化为128个链接),在小并发(100左右)时,可能问题不大,但并发上了1000-2000时,其效率要比本地化缓存机制低1/3(loadrunner测试场景),比如loadrunner测试1000并发时,如果showtopic(显示主题),本地缓存处理时间为15秒,而使用memcached可能会达到25-35秒。

显然这是用户所不能忍受的,所以要想解决方案。也就有了今天的文章。

其实要解决这个问题的原理很简单,就是将之前的两种缓存方案(本地缓存和memcached)进行整合,原理如下:

首先在iis进程中会将要缓存的数据缓存一份,同时也将该数据放入memcached一份,当然本地缓存的数据生命周期要比memcached少。这就造成本地缓存数据到期后,当再次访问其则将memcached中的数据加载到本地缓存中并返回给应用程序。当缓存的数据更新时,则要更新memcached中的数据和本地缓存的数据(当然如果你要将应用程序布署的到多个站点时,因为不同的站点运行在不同的web园或主机上,这时你就不可以用最简单的方式来更新其它进程和主机上的应用程序了,因为当前缓存的数据只保存在当前web园进程中),这也就是为什么要给本地缓存数据设置到期时间这个值,让其在到期后来自动从memcached获取数据。

原理解释完了之后,我们来看看如何实现这个方案.

首先,我们要看一下默认的本地缓存策略文件,其功能也就是两年前所说的那个本地缓存策略功能,如下:

/// <summary>
    /// 默认缓存管理类
    /// </summary>
    public class  DefaultCacheStrategy : ICacheStrategy
    {
        private static readonly DefaultCacheStrategy instance = new DefaultCacheStrategy();

protected static volatile System.Web.Caching.Cache webCache = System.Web.HttpRuntime.Cache;

/// <summary>
        /// 默认缓存存活期为3600秒(1小时)
        /// </summary>
        protected int _timeOut = 3600;

private static object syncObj = new object();

/// <summary>
        /// 构造函数
        /// </summary>
        static DefaultCacheStrategy()
        {}

/// <summary>
        /// 设置到期相对时间[单位: 秒] 
        /// </summary>
        public virtual int TimeOut
        {
            set { _timeOut = value > 0 ? value : 3600; }
            get { return _timeOut > 0 ? _timeOut : 3600; }
        }

public static System.Web.Caching.Cache GetWebCacheObj
        {
            get { return webCache; }
        }

/// <summary>
        /// 加入当前对象到缓存中
        /// </summary>
        /// <param name="objId">对象的键值</param>
        /// <param name="o">缓存的对象</param>
        public virtual void AddObject(string objId, object o)
        {    
            if (objId == null || objId.Length == 0 || o == null)
            {
                return;
            }

CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(onRemove);

if (TimeOut == 7200)
            {
                webCache.Insert(objId, o, null, DateTime.MaxValue, TimeSpan.Zero, System.Web.Caching.CacheItemPriority.High, callBack);
            }
            else
            {
                webCache.Insert(objId, o, null, DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
            }
        }

/// <summary>
        /// 加入当前对象到缓存中
        /// </summary>
        /// <param name="objId">对象的键值</param>
        /// <param name="o">缓存的对象</param>
        public virtual void AddObjectWith(string objId, object o)
        {
            if (objId == null || objId.Length == 0 || o == null)
            {
                return;
            }

CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(onRemove);

webCache.Insert(objId, o, null, System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        }

/// <summary>
        /// 加入当前对象到缓存中,并对相关文件建立依赖
        /// </summary>
        /// <param name="objId">对象的键值</param>
        /// <param name="o">缓存的对象</param>
        /// <param name="files">监视的路径文件</param>
        public virtual void AddObjectWithFileChange(string objId, object o, string[] files)
        {
            if (objId == null || objId.Length == 0 || o == null)
            {
                return;
            }

CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(onRemove);

CacheDependency dep = new CacheDependency(files, DateTime.Now);

webCache.Insert(objId, o, dep, System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        }

/// <summary>
        /// 加入当前对象到缓存中,并使用依赖键
        /// </summary>
        /// <param name="objId">对象的键值</param>
        /// <param name="o">缓存的对象</param>
        /// <param name="dependKey">依赖关联的键值</param>
        public virtual void AddObjectWithDepend(string objId, object o, string[] dependKey)
        {
            if (objId == null || objId.Length == 0 || o == null)
            {
                return;
            }

CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(onRemove);

CacheDependency dep = new CacheDependency(null, dependKey, DateTime.Now);

webCache.Insert(objId, o, dep, System.DateTime.Now.AddSeconds(TimeOut), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, callBack);
        }

/// <summary>
        /// 建立回调委托的一个实例
        /// </summary>
        /// <param name="key"></param>
        /// <param name="val"></param>
        /// <param name="reason"></param>
        public void onRemove(string key, object val, CacheItemRemovedReason reason)
        {
            switch (reason)
            {
                case CacheItemRemovedReason.DependencyChanged:
                    break;
                case CacheItemRemovedReason.Expired:
                    {
                        //CacheItemRemovedCallback callBack = new CacheItemRemovedCallback(this.onRemove);

//webCache.Insert(key, val, null, System.DateTime.Now.AddMinutes(TimeOut),
                        //    System.Web.Caching.Cache.NoSlidingExpiration,
                        //    System.Web.Caching.CacheItemPriority.High,
                        //    callBack);
                        break;
                    }
                case CacheItemRemovedReason.Removed:
                    {
                        break;
                    }
                case CacheItemRemovedReason.Underused:
                    {
                        break;
                    }
                default: break;
            }
        }

/// <summary>
        /// 删除缓存对象
        /// </summary>
        /// <param name="objId">对象的关键字</param>
        public virtual void RemoveObject(string objId)
        {
            if (objId == null || objId.Length == 0)
            {
                return;
            }
            webCache.Remove(objId);
        }

/// <summary>
        /// 返回一个指定的对象
        /// </summary>
        /// <param name="objId">对象的关键字</param>
        /// <returns>对象</returns>
        public virtual object RetrieveObject(string objId)
        {
            if (objId == null || objId.Length == 0)
            {
                return null;
            }            
            return webCache.Get(objId);
        } 
}

因为在一开始设计Discuz!NT缓存方案时,就使用了Strategy(策略)模式,所以这里我们只要将上面所说的改动方案以继承的方式继承自上面的

DefaultCacheStrategy 之后,就可以在DNTCache中使用它了。因为之前我已经将memcached引入到了discuznt产品中,所以这里只要改动一下已有的那个MemCachedStrategy,使其支持上面所说的缓存分布方案即可,请看下面的代码:

/// <summary>
   /// 企业级MemCache缓存策略类,只能使用一个web园程序
   /// </summary>
   public class MemCachedStrategy : DefaultCacheStrategy
   {
       /// <summary>
       /// 添加指定ID的对象
       /// </summary>
       /// <param name="objId"></param>
       /// <param name="o"></param>
       public override void AddObject(string objId, object o)
       {
           //先向本地cached加入,然后再加到memcached
           RemoveObject(objId);

base.AddObject(objId, o);

MemCachedManager.CacheClient.Set(objId, o);
       }

/// <summary>
       /// 添加指定ID的对象(关联指定文件组)
       /// </summary>
       /// <param name="objId"></param>
       /// <param name="o"></param>
       /// <param name="files"></param>
       public override void AddObjectWithFileChange(string objId, object o, string[] files)
       {
           ;
       }

/// <summary>
       /// 添加指定ID的对象(关联指定键值组)
       /// </summary>
       /// <param name="objId"></param>
       /// <param name="o"></param>
       /// <param name="dependKey"></param>
       public override void AddObjectWithDepend(string objId, object o, string[] dependKey)
       {
           ;
       }

/// <summary>
       /// 移除指定ID的对象
       /// </summary>
       /// <param name="objId"></param>
       public override void RemoveObject(string objId)
       {
           //先移除本地cached,然后再移除memcached中的相应数据
           if (base.RetrieveObject(objId) != null)
               base.RemoveObject(objId);

if (MemCachedManager.CacheClient.KeyExists(objId))
               MemCachedManager.CacheClient.Delete(objId);
       }

/// <summary>
       /// 返回指定ID的对象
       /// </summary>
       /// <param name="objId"></param>
       /// <returns></returns>
       public override object RetrieveObject(string objId)
       {
           object obj = base.RetrieveObject(objId);
           if (obj == null)
           {
               obj = MemCachedManager.CacheClient.Get(objId);
               if (obj != null)
                   base.AddObject(objId, obj);
           }

return obj;
       }

/// <summary>
       /// 到期时间
       /// </summary>
       public override int TimeOut 
       { 
           get 
           { 
               return MemCachedConfigs.GetConfig().LocalCacheTime; 
           } 
       }
   }

注:MemCachedStrategy 原来已实现了ICacheStrategy接口,参见这篇文章

这样,我们还是可以通过memcached.config中的ApplyMemCached来判断是否使用本地缓存方案还是当前的缓存分层方案。当然原有的memcache.config中还有添加一下属性用于记录当使用缓存分层方案之后的本地缓存的缓存数据时间,以向上面的类属性TimeOut注入相应参数信息。

这样memcached.config的内容就会变成这个样子(本地测试配置):

<?xml version="1.0"?>
<MemCachedConfigInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22 xmlns:xsd="http://www.w3.org/2001/XMLSchema%22>
    <ApplyMemCached>true</ApplyMemCached>  
    <ServerList>10.0.2.137:11211</ServerList>
    <PoolName>DiscuzNT_MemCache</PoolName>
    <IntConnections>128</IntConnections>
    <MinConnections>128</MinConnections>
    <MaxConnections>512</MaxConnections>
    <SocketConnectTimeout>1000</SocketConnectTimeout>
    <SocketTimeout>3000</SocketTimeout>
    <MaintenanceSleep>30</MaintenanceSleep>
    <FailOver>true</FailOver>
    <Nagle>true</Nagle>
    <LocalCacheTime>60</LocalCacheTime>
</MemCachedConfigInfo>

这样,当使用Lr测试时,其在并发1000的情况下与使用本地缓存方案的响应时间基本稳定在15秒左右,想一下大家就会明白了,因为在数据首次加载并进行缓存时(本地和memcached都会缓存一份,参见上面的实现代码)。当再次访问时,如在60秒的数据有效期内,仅访问本地缓存,只有在数据过期时间,才会运行再次加载数据的工作,而这种加载也只是从memcached中获得数据,这里我们可以暂时将memcached中的数据想像是永不过期,这样就可以减少对database的访问压力,因为这时相对于本地缓存而言,memcached已经变成了一个‘缓存数据库’了:

public override object RetrieveObject(string objId)
       {
           object obj = base.RetrieveObject(objId);
           if (obj == null)
           {
               obj = MemCachedManager.CacheClient.Get(objId);
               if (obj != null)
                   base.AddObject(objId, obj);
           }

return obj;
       }

现在用两张图再对比说明之前的memcached与现在的缓存分层方案:

改进后:

总结:其实在大网站的数据缓存方案中,往往会将大量的数据(不经常变化或对时效性要求不强,但却需频繁访问的数据)放入到缓存中,以此来降低数据库的负载。本地缓存数据的时效性和稳定性受制于IIS进程中线程的运行情况,资源的占用等因素影响,可以说数据的稳定性(不易丢失)远不如memcached,所以这种分层方案可以有效的解决这个问题,当然这种做法还有一些其它方面的好处,就不一一说明了。

原文链接: http://www.cnblogs.com/daizhj/archive/2009/11/17/1604436.html

作者: daizhj, 代震军

原文地址:https://www.cnblogs.com/LiZhongZhongY/p/10954580.html

时间: 2024-11-08 00:08:39

在Discuz!NT中进行缓存分层(本地缓存+memcached)(转)的相关文章

安卓网络请求图片到图片的三级缓存技术(内存缓存,本地缓存,网络缓存)

安卓网络请求图片,对于我们来说并不陌生,因为每个应用都有可能会用到这一技术.通常情况下,我们第一次都是从网络上请求图片资源,然后将 图片资源保存到内存和本地,下一次动态显示图片的时候就不需要再从网络上请求图片资源了,直接从本地或者内存中获取就可以了.这就涉及到图片 的三级缓存技术,分别是内存缓存,本地缓存,网络缓存. 缓存的流程图: 首先我们定义一个类叫ClassLoader: package com.jsako.showprodinfodemo; import java.io.FileOutp

中央缓存结合本地缓存-本地缓存数据刷新方案

互联网应用通常都需要应付大并发量,为了提高QPS,通常会使用中央缓存(例如memcache)和本地缓存的方式.请求先经过本地缓存,如果不命 中,则 请求穿透到中央缓存,如果还是不命中,则会直接查询数据库,并把查询到的数据刷新到中央缓存中.如果采用这种方式的话,必须要解决一个问题,如何刷新本地 缓存的数据. 详细请看我的csdn博客: 中央缓存结合本地缓存-本地缓存数据刷新方案

Discuz!NT中集成Memcached分布式缓存

大约在两年前我写过一篇关于Discuz!NT缓存架构的文章,在那篇文章的结尾介绍了在IIS中如果开启多个应用程序池会造成多个缓存实例之间数据同步的问题.虽然给出了一个解决方案,但无形中却把压力转移到了磁盘I/O上(多个进程并发访问cache.config文件).其实从那时起我就开始关注有什么更好的方案,当然今天本文中所说的Memcached,以及Velocity等这类的分布式缓存方案之前都考虑过,但一直未能决定该使用那个.起码Velocity要在.net 4.0之后才会提供,虽然是原生态,但有些

Discuz!NT中集成Memcached分布式缓存(转)

大约在两年前我写过一篇关于Discuz!NT缓存架构的文章,在那篇文章的结尾介绍了在IIS中如果开启多个应用程序池会造成多个缓存实例之间数据同步的问题.虽然给出了一个解决方案,但无形中却把压力转移到了磁盘I/O上(多个进程并发访问cache.config文件).其实从那时起我就开始关注有什么更好的方案,当然今天本文中所说的Memcached,以及Velocity等这类的分布式缓存方案之前都考虑过,但一直未能决定该使用那个.起码Velocity要在.net 4.0之后才会提供,虽然是原生态,但有些

Discuz!NT中的Redis架构设计

在之前的Discuz!NT缓存的架构方案中,曾说过Discuz!NT采用了两级缓存方式,即本地缓存+memcached方式.在近半年多的实际运行环境下,该方案经受住了检验.现在为了提供多样式的解决方案,我在企业版里引入了Redis这个目前炙手可热的缓存架构产品,即将memcached与Redis作为可选插件方式来提供了最终用户,尽管目前测试的结果两者的差异不是很大(毫秒级),但我想多一种选择对用户来说也是好的. 闲话不多说了,开始今天的正文吧.         熟悉我们产品的开发者都知道,我们的

缓存:本地缓存和分布式缓存及缓存过期时间设置

1.首先对于本地内存缓存,就是把数据缓存在本机的内存中,如下图1所示: 2. 分布式缓存机制:可能存在跨进程,跨域访问缓存数据 对于分布式的缓存,此时因为缓存的数据是放在缓存服务器中的,或者说,此时应用程序需要跨进程的去访问分布式缓存服务器,如图2: 当我们在应用中使用跨进程的缓存机制,例如分布式缓存memcached或者微软的AppFabric,此时数据被缓存在应用程序之外的进程中.每次,当我们要把一些数据缓存起来的时候,缓存的API就会把数据首先序列化为字节的形式,然后把这些字节发送给缓存服

分布式缓存和本地缓存

1:本地缓存:  内存,encache 2:分布式缓存:redis缓存 本地缓存,存储在本机内存上,直接从内存中取数据,速度快.  但是容量较小,数据结构不丰富. 分布式缓存:存储在很多机器上,可以从其它机器上取数据,容量大.  数据结构丰富等. https://blog.csdn.net/baiyunpeng42/article/details/53694430 原文地址:https://www.cnblogs.com/liyafei/p/9392083.html

Discuz!NT Flash无法上传头像,点击上传后无任何反应

最近在对一个Discuz!NT论坛的老项目进行维护和二次开发,遇到了论坛无法上传头像的问题.在网上找了相当多的资料,发现解决的方法基本是无效的.虽然有的状况一样,但是没有解决方法,后来自己研究了下也总算是解决了这个问题. 首先说明下出现无法上传头像的具体症状,大概的症状有下面这几点: 使用Flash头像上传时,点击上传图片,然后显示“图片载入中,请稍后的提示信息”和上传进度的百分比,最后却是没有任何的反应. 网上有部分网友说到进度到10%就没有任何反应,其实只是部分情况,如果传大图片的话会显示其

故障排除 Mybatis ORA-01000 和 本地缓存问题

※异常信息 环境 MyBatis Oracle11.2c Terasoluna BatchCaused by: org.springframework.jdbc.UncategorizedSQLException: ### Error updating database. Cause: java.sql.SQLException: ORA-00604: 再帰SQLレベル1でエラーが発生しました.ORA-01000: 最大オープン?カーソル数を超えました.ORA-00604: 再帰SQLレベル1で