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

大约在两年前我写过一篇关于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):

/// <summary>
/// MemCache缓存策略类
/// </summary>
public class MemCachedStrategy : Discuz.Cache.ICacheStrategy
{

/// <summary>
    /// 添加指定ID的对象
    /// </summary>
    /// <param name="objId"></param>
    /// <param name="o"></param>
    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);
        }
    }

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

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

/// <summary>
    /// 移除指定ID的对象
    /// </summary>
    /// <param name="objId"></param>
    public void RemoveObject(string objId)
    {
        if (MemCachedManager.CacheClient.KeyExists(objId))
            MemCachedManager.CacheClient.Delete(objId);
    }

/// <summary>
    /// 返回指定ID的对象
    /// </summary>
    /// <param name="objId"></param>
    /// <returns></returns>
    public object RetrieveObject(string objId)
    {
        return MemCachedManager.CacheClient.Get(objId);
    }

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

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

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

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

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

/// <summary>
     /// 移除指定ID的对象
     /// </summary>
     /// <param name="objId"></param>
     void RemoveObject(string objId);

/// <summary>
     /// 返回指定ID的对象
     /// </summary>
     /// <param name="objId"></param>
     /// <returns></returns>
     object RetrieveObject(string objId);

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

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

/// <summary>
/// MemCache管理操作类
/// </summary>
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;
    }

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

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

return mc;
        }
    }

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

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

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

/// <summary>
/// 获取当前缓存键值所存储在的服务器
/// </summary>
/// <param name="key">当前缓存键</param>
/// <returns>当前缓存键值所存储在的服务器</returns>
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;
}

/// <summary>
/// 获取有效的服务器地址
/// </summary>
/// <returns>有效的服务器地</returns>
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, ",");
}

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

/// <summary>
/// 获取服务器端缓存的数据信息
/// </summary>
/// <param name="serverArrayList">要访问的服务列表</param>
/// <returns>返回信息</returns>
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;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/// <summary>
    /// 构造函数
    /// </summary>
    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的提升

原文链接:http://www.cnblogs.com/daizhj/archive/2009/02/09/1386652.html

作者: daizhj, 代震军

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

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

Discuz!NT中集成Memcached分布式缓存(转)的相关文章

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

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

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

在以前的两篇文章(Discuz!NT 缓存设计简析, Discuz!NT中集成Memcached分布式缓存)中,介绍了Discuz!NT中的缓存设计思路以及如何引入Memcached,当然前者是IIS进程的缓存(本地缓存),后者是分布式内存对象缓存系统. 两者通过Discuz!NT中的memcached.config文件中的ApplyMemCached结点的值来决定使用哪一种缓存方式.不过在之后,有朋友反映当使用Memcached时,特别是在大并发来时,效率会打折扣,甚至有很多时间会消耗在soc

Discuz!NT中的Redis架构设计

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

memcached分布式缓存

1.memcached分布式简介 memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能.Memcache集群主机不能够相互通信传输数据,它的“分布式”是基于客户端的程序逻辑算法进一步实现的. 请看下面简图: 根据上图我们简述分析分布式memcached的set与get的过程 set过程: 1.首先通过应用程序set(‘key’,’value’) 2.进入程序,使用key通过逻辑算法得出这个key需要存储的节点位置 3.根据节点位置连接相应的memcached服务器,并发

Memcached分布式缓存初体验

1 Memcached简介/下载/安装 Memcached是一个高性能的不是内存对象缓存系统,用于动态Web应用以减轻数据库负载.Memcached基于一个存储键/值对的HashMap.其客户端可以使用任何语言进行编写,并通过Memcached协议与进行通信 下载memcached-win64-1.4.4-14 的windows稳定版 cmd命令(win+R),切换到解压包的指定目录,并且输入命令:memcached.exe -d install(与之对应memcached.exe -d uni

缓存应用--Memcached分布式缓存简介

一.   什么是Memcached Memcached 是一个高性能的分布式内存 对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象 来减少读取数据库的次数,从而提供动态.数据库驱动网站的速度. 相信很多人都用过缓存,在 .net 中也有内置的缓存机制,还有很多第三方工具如apache,nginx等可以做静态资源的缓存,同时我们也可 以制定自己的缓存机制,缓存数据库查询的数据以减少对数据库的频繁操作.但是很多时候我们总是感觉这些缓存总不尽人意, Memcached可以

memcached分布式缓存服务器学习总结

实验楼:https://www.shiyanlou.com/ 以下学习总结主要通过实验楼环境 memcached:高速运行的分布式缓存服务器 特点:(1)协议简单(2)基于libevent的事件处理(3)内置内存存储方式(4)不互相通信的分布式 适用场景:(1)网站包含了访问量很大的动态网页,因而数据库的负载将会很高,且大部分数据库请求都是读操作.(2)数据库服务器的负载比较低,CPU使用率较高;(3)小型需要共享的数据,如session等临时数据:(4)缓存一些很小但是被频繁访问的文件. 不适

Memcached 分布式缓存实现原理

摘要 在高并发环境下,大量的读.写请求涌向数据库,此时磁盘IO将成为瓶颈,从而导致过高的响应延迟,因此缓存应运而生.无论是单机缓存还是分布式缓存都有其适应场景和优缺点,当今存在的缓存产品也是数不胜数,最常见的有redis和memcached等,既然是分布式,那么他们是怎么实现分布式的呢?本文主要介绍分布式缓存服务mencached的分布式实现原理. 缓存本质 计算机体系缓存 什么是缓存,我们先看看计算机体系结构中的存储体系,根据冯·诺依曼计算机体系结构模型,计算机分为五大部分:运算器.控制器.存

memcached分布式缓存服务器学习总结(二)memcached状态查询

linux memcached状态查询如何查看memcached服务器端版本: ./memcached -h memcached的运行状态可以方便的用 stats 命令显示. 首先用telnet 127.0.0.1 11211这样的命令连接上memcache,然后直接输入stats就可以得到当前memcache的状态. 这些状态的说明如下: pid memcache服务器的进程IDuptime 服务器已经运行的秒数time 服务器当前的unix时间戳version memcache版本point