设为首页 收藏本站
查看: 1349|回复: 0

[经验分享] 在Discuz!NT中进行缓存分层(本地缓存+memcached)

[复制链接]

尚未签到

发表于 2018-12-25 13:19:09 | 显示全部楼层 |阅读模式
在以前的两篇文章(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获取数据。
     原理解释完了之后,我们来看看如何实现这个方案.
     首先,我们要看一下默认的本地缓存策略文件,其功能也就是两年前所说的那个本地缓存策略功能,如下:
   
    ///
   
/// 默认缓存管理类
   
///
    public class  DefaultCacheStrategy : ICacheStrategy
    {
        
private static readonly DefaultCacheStrategy instance = new DefaultCacheStrategy();

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

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

        
private static object syncObj = new object();

        
///
        
/// 构造函数
        
///
        static DefaultCacheStrategy()
        {}


        
///
        
/// 设置到期相对时间[单位: 秒]
        
///
        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; }
        }


        
///
        
/// 加入当前对象到缓存中
        
///
        
/// 对象的键值
        
/// 缓存的对象
        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);
            }
        }

        
///
        
/// 加入当前对象到缓存中
        
///
        
/// 对象的键值
        
/// 缓存的对象
        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);
        }

        
///
        
/// 加入当前对象到缓存中,并对相关文件建立依赖
        
///
        
/// 对象的键值
        
/// 缓存的对象
        
/// 监视的路径文件
        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);
        }

        
///
        
/// 加入当前对象到缓存中,并使用依赖键
        
///
        
/// 对象的键值
        
/// 缓存的对象
        
/// 依赖关联的键值
        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);
        }

        
///
        
/// 建立回调委托的一个实例
        
///
        
///
        
///
        
///
        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;
            }
        }

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

        
///
        
/// 返回一个指定的对象
        
///
        
/// 对象的关键字
        
/// 对象
        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,使其支持上面所说的缓存分布方案即可,请看下面的代码:
  
   ///
   
/// 企业级MemCache缓存策略类,只能使用一个web园程序
   
///
   public class MemCachedStrategy : DefaultCacheStrategy
   {
      
///
      
/// 添加指定ID的对象
      
///
      
///
      
///
       public override void AddObject(string objId, object o)
       {
           
//先向本地cached加入,然后再加到memcached
           RemoveObject(objId);

           
base.AddObject(objId, o);

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

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

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

      
///
      
/// 移除指定ID的对象
      
///
      
///
       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);
       }

      
///
      
/// 返回指定ID的对象
      
///
      
///
      
///
       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;
       }

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

          注:MemCachedStrategy 原来已实现了ICacheStrategy接口,参见这篇文章。       这样,我们还是可以通过memcached.config中的ApplyMemCached来判断是否使用本地缓存方案还是当前的缓存分层方案。当然原有的memcache.config中还有添加一下属性用于记录当使用缓存分层方案之后的本地缓存的缓存数据时间,以向上面的类属性TimeOut注入相应参数信息。 这样memcached.config的内容就会变成这个样子(本地测试配置):  


   
true  
   
10.0.2.137:11211
   
DiscuzNT_MemCache
   
128
   
128
   
512
   
1000
   
3000
   
30
   
true
   
true
   
60

     这样,当使用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,所以这种分层方案可以有效的解决这个问题,当然这种做法还有一些其它方面的好处,就不一一说明了。






运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-655734-1-1.html 上篇帖子: [缓存]关于memcached的详细介绍以及用法 下篇帖子: memcached 异常 : 单数据项超过默认值1m
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表