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

[经验分享] Discuz!NT中集成Memcached分布式缓存

[复制链接]

尚未签到

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

       我想真正等到Velocity能堪当重任还要等上一段时间。于是我就开始将注意力转移到了Memcached,必定有Facebook这只“超级小白鼠”使用它并且反响还不错。所以就开始尝试动手在产品中集成Memcached。

        其实在之前的那篇关于Discuz!NT缓存架构的文章中已提到过,使用了设计模式中的“策略模式”来构造。所以为了与以往使用缓存的代码格式相兼容,所以这里采用新添加MemCachedStrategy(MemCached策略)来构造一个缓存策略类以便于当管理后台开启“MemCached”时以“MemCached策略模式”来做为当前系统默认
的策略模式。

       其代码段如下(Discuz.Cache/MemCached.cs):
   
///
/// MemCache缓存策略类
///
public class MemCachedStrategy : Discuz.Cache.ICacheStrategy
{

   
///
   
/// 添加指定ID的对象
   
///
   
///
   
///
    public void AddObject(string objId, object o)
    {
        RemoveObject(objId);
        
if (TimeOut > 0)
        {
            MemCachedManager.CacheClient.Set(objId, o, System.DateTime.Now.AddMinutes(TimeOut));
        }
        
else
        {
            MemCachedManager.CacheClient.Set(objId, o);
        }
    }

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

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

   
///
   
/// 移除指定ID的对象
   
///
   
///
    public void RemoveObject(string objId)
    {
        
if (MemCachedManager.CacheClient.KeyExists(objId))
            MemCachedManager.CacheClient.Delete(objId);
    }

   
///
   
/// 返回指定ID的对象
   
///
   
///
   
///
    public object RetrieveObject(string objId)
    {
        
return MemCachedManager.CacheClient.Get(objId);
    }

   
///
   
/// 到期时间
   
///
    public int TimeOut { set; get; }
}


   

    上面类实现的接口Discuz.Cache.ICacheStrategy定义如下:
   

///
/// 公共缓存策略接口
///
public interface ICacheStrategy
{
     
///
     
/// 添加指定ID的对象
     
///
     
///
     
///
     void AddObject(string objId, object o);

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

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

     
///
     
/// 移除指定ID的对象
     
///
     
///
     void RemoveObject(string objId);

     
///
     
/// 返回指定ID的对象
     
///
     
///
     
///
     object RetrieveObject(string objId);

     
///
     
/// 到期时间
     
///
     int TimeOut { set;get;}
}



        当然在MemCachedStrategy类中还有一个对象要加以说明,就是MemCachedManager,该类主要是对Memcached一些常操作和相关初始化实例调用的“封装”,下面是是其变量定义和初始化构造方法的代码:

///
/// MemCache管理操作类
///
public sealed class MemCachedManager
{
   
#region 静态方法和属性
   
private static MemcachedClient mc = null;

   
private static SockIOPool pool = null;

   
private static MemCachedConfigInfo memCachedConfigInfo = MemCachedConfigs.GetConfig();

   
private static string [] serverList = null;

   
static MemCachedManager()
    {
        CreateManager();
    }

   
private static void CreateManager()
    {
        serverList
= Utils.SplitString(memCachedConfigInfo.ServerList, ""r"n");

        pool
= SockIOPool.GetInstance(memCachedConfigInfo.PoolName);
        pool.SetServers(serverList);
        pool.InitConnections
= memCachedConfigInfo.IntConnections;//初始化链接数
        pool.MinConnections = memCachedConfigInfo.MinConnections;//最少链接数
        pool.MaxConnections = memCachedConfigInfo.MaxConnections;//最大连接数
        pool.SocketConnectTimeout = memCachedConfigInfo.SocketConnectTimeout;//Socket链接超时时间
        pool.SocketTimeout = memCachedConfigInfo.SocketTimeout;// Socket超时时间
        pool.MaintenanceSleep = memCachedConfigInfo.MaintenanceSleep;//维护线程休息时间
        pool.Failover = memCachedConfigInfo.FailOver; //失效转移(一种备份操作模式)
        pool.Nagle = memCachedConfigInfo.Nagle;//是否用nagle算法启动socket
        pool.HashingAlgorithm = HashingAlgorithm.NewCompatibleHash;
        pool.Initialize();
        

        mc
= new MemcachedClient();
        mc.PoolName
= memCachedConfigInfo.PoolName;
        mc.EnableCompression
= false;
    }

   
///
   
/// 缓存服务器地址列表
   
///
    public static string[] ServerList
    {
        
set
        {
            
if (value != null)
                serverList
= value;
        }
        
get { return serverList; }
    }

   
///
   
/// 客户端缓存操作对象
   
///
    public static MemcachedClient CacheClient
    {
        
get
        {
            
if (mc == null)
                CreateManager();

            
return mc;
        }
    }

   
public static void Dispose()
    {
        
if (pool != null)
            pool.Shutdown();
    }
    DSC0000.gif


   
    上面代码中构造方法会初始化一个池来管理执行Socket链接,并提供静态属性CacheClient以便MemCachedStrategy
来调用。


    当然我还在这个管理操作类中添加了几个方法分别用于检测当前有效的分布式缓存服务器的列表,向指定(或全部)
缓存服务器发送特定stats命令来获取当前缓存服务器上的数据信息和内存分配信息等,相应的方法如下(详情见注释):

///
/// 获取当前缓存键值所存储在的服务器
///
/// 当前缓存键
/// 当前缓存键值所存储在的服务器
public static string GetSocketHost(string key)
{
   
string hostName = "";
    SockIO sock
= null;
   
try
    {
        sock
= SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetSock(key);
        
if (sock != null)
        {
            hostName
= sock.Host;
        }
    }
   
finally
    {
        
if (sock != null)
            sock.Close();
    }
   
return hostName;
}


///
/// 获取有效的服务器地址
///
/// 有效的服务器地
public static string[] GetConnectedSocketHost()
{
    SockIO sock
= null;
   
string connectedHost = null;
   
foreach (string hostName in serverList)
    {
        
if (!Discuz.Common.Utils.StrIsNullOrEmpty(hostName))
        {
            
try
            {
                sock
= SockIOPool.GetInstance(memCachedConfigInfo.PoolName).GetConnection(hostName);
               
if (sock != null)
                {
                    connectedHost
= Discuz.Common.Utils.MergeString(hostName, connectedHost);
                }
            }
            
finally
            {
               
if (sock != null)
                    sock.Close();
            }
        }
    }
   
return Discuz.Common.Utils.SplitString(connectedHost, ",");
}

///
/// 获取服务器端缓存的数据信息
///
/// 返回信息
public static ArrayList GetStats()
{
    ArrayList arrayList
= new ArrayList();
   
foreach (string server in serverList)
    {
        arrayList.Add(server);
    }
   
return GetStats(arrayList, Stats.Default, null);
}

///
/// 获取服务器端缓存的数据信息
///
/// 要访问的服务列表
/// 返回信息
public static ArrayList GetStats(ArrayList serverArrayList, Stats statsCommand, string param)
{
    ArrayList statsArray
= new ArrayList();
    param
=  Utils.StrIsNullOrEmpty(param) ? "" : param.Trim().ToLower();

   
string commandstr = "stats";
   
//转换stats命令参数
    switch (statsCommand)
    {
        
case Stats.Reset: { commandstr = "stats reset"; break; }
        
case Stats.Malloc: { commandstr = "stats malloc"; break; }
        
case Stats.Maps: { commandstr = "stats maps"; break; }
        
case Stats.Sizes: { commandstr = "stats sizes"; break; }
        
case Stats.Slabs: { commandstr = "stats slabs"; break; }
        
case Stats.Items: { commandstr = "stats"; break; }
        
case Stats.CachedDump:
        {
            
string[] statsparams = Utils.SplitString(param, " ");
            
if(statsparams.Length == 2)
               
if(Utils.IsNumericArray(statsparams))
                    commandstr
= "stats cachedump  " + param;

            
break;                     
        }
        
case Stats.Detail:
            {
               
if(string.Equals(param, "on") || string.Equals(param, "off") || string.Equals(param, "dump"))
                    commandstr
= "stats detail " + param.Trim();

               
break;
            }
        
default: { commandstr = "stats"; break; }
    }
   
//加载返回值
    Hashtable stats = MemCachedManager.CacheClient.Stats(serverArrayList, commandstr);
   
foreach (string key in stats.Keys)
    {
        statsArray.Add(key);
        Hashtable values
= (Hashtable)stats[key];
        
foreach (string key2 in values.Keys)
        {
            statsArray.Add(key2
+ ":" + values[key2]);
        }
    }
   
return statsArray;
}

///
/// Stats命令行参数
///
public enum Stats
{
   
///
   
/// stats : 显示服务器信息, 统计数据等
   
///
    Default = 0,
   
///
   
/// stats reset : 清空统计数据
   
///
    Reset = 1,
   
///
   
/// stats malloc : 显示内存分配数据
   
///
    Malloc = 2,
   
///
   
/// stats maps : 显示"/proc/self/maps"数据
   
///
    Maps =3,
   
///
   
/// stats sizes
   
///
    Sizes = 4,
   
///
   
/// stats slabs : 显示各个slab的信息,包括chunk的大小,数目,使用情况等
   
///
    Slabs = 5,
   
///
   
/// stats items : 显示各个slab中item的数目和最老item的年龄(最后一次访问距离现在的秒数)
   
///
    Items = 6,
   
///
   
/// stats cachedump slab_id limit_num : 显示某个slab中的前 limit_num 个 key 列表
   
///
    CachedDump =7,
   
///
   
/// stats detail [on|off|dump] : 设置或者显示详细操作记录   on:打开详细操作记录  off:关闭详细操作记录 dump: 显示详细操作记录(每一个键值get,set,hit,del的次数)
   
///
    Detail = 8
}




    当然在配置初始化缓存链接池时使用了配置文件方式(memcached.config)来管理相关参数,其info信息
类说明如下(Discuz.Config/MemCachedConfigInfo.cs):

///
/// MemCached配置信息类文件
///
public class MemCachedConfigInfo : IConfigInfo
{
   
private bool _applyMemCached;
   
///
   
/// 是否应用MemCached
   
///
    public bool ApplyMemCached
    {
        
get
        {
            
return _applyMemCached;
        }
        
set
        {
            _applyMemCached
= value;
        }
    }

   
private string _serverList;
   
///
   
/// 链接地址
   
///
    public string ServerList
    {
        
get
        {
            
return _serverList;
        }
        
set
        {
            _serverList
= value;
        }
    }

   
private string _poolName;
   
///
   
/// 链接池名称
   
///
    public string PoolName
    {
        
get
        {
            
return Utils.StrIsNullOrEmpty(_poolName) ? "DiscuzNT_MemCache" : _poolName;
        }
        
set
        {
            _poolName
= value;
        }
    }

   
private int _intConnections;
   
///
   
/// 初始化链接数
   
///
    public int IntConnections
    {
        
get
        {
            
return _intConnections > 0 ? _intConnections : 3;
        }
        
set
        {
            _intConnections
= value;
        }
    }

   
private int _minConnections;
   
///
   
/// 最少链接数
   
///
    public int MinConnections
    {
        
get
        {
            
return _minConnections > 0 ? _minConnections : 3;
        }
        
set
        {
            _minConnections
= value;
        }
    }

   
private int _maxConnections;
   
///
   
/// 最大连接数
   
///
    public int MaxConnections
    {
        
get
        {
            
return _maxConnections > 0 ?_maxConnections : 5;
        }
        
set
        {
            _maxConnections
= value;
        }
    }

   
private int _socketConnectTimeout;
   
///
   
/// Socket链接超时时间
   
///
    public int SocketConnectTimeout
    {
        
get
        {
            
return _socketConnectTimeout > 1000 ? _socketConnectTimeout : 1000;
        }
        
set
        {
            _socketConnectTimeout
= value;
        }
    }

   
private int _socketTimeout;
   
///
   
/// socket超时时间
   
///
    public int SocketTimeout
    {
        
get
        {
            
return _socketTimeout > 1000 ? _maintenanceSleep : 3000;
        }
        
set
        {
            _socketTimeout
= value;
        }
    }

   
private int _maintenanceSleep;
   
///
   
/// 维护线程休息时间
   
///
    public int MaintenanceSleep
    {
        
get
        {
            
return _maintenanceSleep > 0 ? _maintenanceSleep : 30;
        }
        
set
        {
            _maintenanceSleep
= value;
        }
    }

   
private bool _failOver;
   
///
   
/// 链接失败后是否重启,详情参见http://baike.baidu.com/view/1084309.htm
   
///
    public bool FailOver
    {
        
get
        {
            
return _failOver;
        }
        
set
        {
            _failOver
= value;
        }
    }

   
private bool _nagle;
   
///
   
/// 是否用nagle算法启动socket
   
///
    public bool Nagle
    {
        
get
        {
            
return _nagle;
        }
        
set
        {
            _nagle
= value;
        }
    }
}      




      这些参数我们通过注释应该有一些了解,可以说memcached的主要性能都是通过这些参数来决定的,大家
应根据自己公司产品和应用的实际情况配置相应的数值。

     当然,做完这一步之后就是对调用“缓存策略”的主体类进行修改来,使其根据对管理后台的设计来决定
加载什么样的缓存策略,如下:

///
/// Discuz!NT缓存类
/// 对Discuz!NT论坛缓存进行全局控制管理
///
public class DNTCache
{
    .
   
   
//通过该变量决定是否启用MemCached
    private static bool applyMemCached = MemCachedConfigs.GetConfig().ApplyMemCached;     

   
///
   
/// 构造函数
   
///
    private DNTCache()
    {
        
if (applyMemCached)
            cs
= new MemCachedStrategy();
        
else
        {
            cs
= new DefaultCacheStrategy();

            objectXmlMap
= rootXml.CreateElement("Cache");
            
//建立内部XML文档.
            rootXml.AppendChild(objectXmlMap);

            
//LogVisitor clv = new CacheLogVisitor();
            
//cs.Accept(clv);

            cacheConfigTimer.AutoReset
= true;
            cacheConfigTimer.Enabled
= true;
            cacheConfigTimer.Elapsed
+= new System.Timers.ElapsedEventHandler(Timer_Elapsed);
            cacheConfigTimer.Start();
        }
    }
   
   


   
         到这里,主要的开发和修改基本上就告一段落了。下面开始介绍一下如果使用Stats命令来查看缓存的分配和使用等情况。之前在枚举类型Stats中看到该命令有几个主要的参数,分别是:
  
    stats
    stats reset
    stats malloc
    stats maps
    stats sizes
    stats slabs
    stats items
    stats cachedump slab_id limit_num
    stats detail [on|off|dump]

   
    而JAVAEYE的 robbin 写过一篇文章:贴一段遍历memcached缓存对象的小脚本,来介绍如何使用其中的   “stats cachedump”来获取信息。受这篇文章的启发,我将MemCachedClient.cs文件中的Stats方法加以修改,添加了一个command参数(字符串型),这样就可以向缓存服务器发送上面所说的那几种类型的命令了。

    测试代码如下:
   
protected void Submit_Click(object sender, EventArgs e)
{
    ArrayList arrayList
= new ArrayList();
    arrayList.Add(
"10.0.1.52:11211");//缓存服务器的地址

    StateResult.DataSource
= MemCachedManager.GetStats(arrayList, (MemCachedManager.Stats)         
                                     Utils.StrToInt(StatsParam.SelectedValue,
0), Param.Text);
    StateResult.DataBind();            
}



   
     页面代码如下:
   
   
   
   
     我这样做的目的有两个,一个是避免每次都使用telnet协议远程登陆缓存服务器并输入相应的命令行参数(我记忆力不好,参数多了之后就爱忘)。二是将来会把这个页面功能内置到管理后台上,以便后台管理员可以动态监测每台缓存服务器上的数据。

     好了,到这里今天的内容就差不多了。在本文中我们看到了使用设计模式的好处,通过它我们可以让自己写的代码支持“变化”。这里不妨再多说几句,大家看到了velocity在使用上也是很方便,如果可以的话,未来可以也会将velocity做成一个“缓存策略”,这样站长或管理员就可以根据自己公司的实际情
况来加以灵活配置了。
   
   
      相关资料:   
      memcached 全面剖析.pdf   
      memcached 深度分析     
      Facebook 对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-655416-1-1.html 上篇帖子: Memcached实现Session共享 下篇帖子: memcached大数据内存读取
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

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

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

扫描微信二维码查看详情

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


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


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


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



合作伙伴: 青云cloud

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