第一节 Memcached分布式缓存入门

关于Memcached的博文太多了,以下是个人学习的收集整理。

本节讨论问题:

  • 简单介绍与应用
  • 下载安装注意事项
  • 简单测试
  • Memcached分布式原理

一、介绍与应用

     在常规的WEB开发下,基本都会利用到缓存用以降低对数据库的压力,提高访问速度。有时候缓存的数据多了,并且其它站点也想获取这些缓存数据时就出现在了问题。通常IIS站点都是以应用程序池划分管理,同一个池下又可划分多个应用程序域,不管是不同的应用程序域或是不同应用程序池,其之间的缓存都是无法相互访问的。因此很多站点就会重复建立相同的缓存,以便访问。但是,一旦一个站点的缓存被更新了,又如何通知其它站点更新呢。我记得Discuz.net中做法,是通过监控配置文件的修改来实现的,其原理就是一个站点缓存更新了,就去修改对应的配置文件中的项。其它站点监控到配置文件被修改,就去检查哪一项目被改了,然后重新加载缓存。是不是不太灵活?如果缓存的数据要分布到其它服务器上,以降低对同一台服务器的压力,如何实现呢?缓存服务器又如何实现扩展呢?这便是我们这篇Memcached引入的原因。有做即时通讯,游戏大厅的还可以采用一下shuttler.net

关于Memcached以下为摘自博文http://www.cnblogs.com/zjneter/archive/2007/07/19/822780.html

Memcached是什么?
      Memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
Memcached能缓存什么?
     通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
Memcached快么?
     非常快。Memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O,对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配器和哈希表, 因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1).。
Memcached的特点?
     Memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问, 因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。

二、下载与安装

服务端与For .net开发下载 可以参照这篇博文http://blog.csdn.net/cnkiminzhuhu/archive/2009/10/28/4739859.aspx
客户端的版本比较多,并且不能互用,因为采用了压缩机制,日志等功能,所以在选择客户端时要注意这些。

服务端源码  下载

a.windows下 直接使用memcached.exe 程序就可以了,也可以将此程序安装为windows服务。安装为windows服务后要通过telnet命令来操作服务端

命令行输入 'c:\memcached\memcached.exe -d install'
命令行输入 'c:\memcached\memcached.exe -d start' ,该命令启动 Memcached ,默认监听端口为 11211

b.安装为单一服务不方便管理,这里有借助于memcacheddotnet_clientlib开发的一款服务端管理工具

服务端管理工具 下载  解压安装后工具里的memcached.exe比较老,可直接用最新的替换掉

我们来看一下服务端工具安装后文件结构

运行服务端管理工具,创建memcached服务端,以下为演示步骤

1 服务端配置

2 添加Memcached服务

3 状态观察

4.查看Generate配置信息,提供给客户端配置文件使用.

借助服务端管理工具可以方便的观察,或者你也可以采用telnet方式访问查看了。这样服务端工作就进行了完了,接下来就是要选择一款合的客户端开发了

Windows / .NET

a. .Net memcached client    1.1.5版本测试一下。
    https://sourceforge.net/projects/memcacheddotnet 
b. .Net 2.0 memcached client 这款应用比较广泛,不过好长时间没有更新了,最后一次更新是在2009.10 (网上其它链接提供的下载版本太老了)
    http://www.codeplex.com/EnyimMemcached
    Client developed in .NET 2.0 keeping performance and extensibility in mind. (Supports consistent hashing.)
    http://www.codeplex.com/memcachedproviders   (PDF 文档)

   Current Release Memcached Providers 1.2(最后一版正式版)(1.2以后到1.4.4 for win32 应该都不是正式版了)
  - Walkthoughs on how to setup and use Memcached Cache Provider and Session State Provider is added to the Memcached Providers 1.2
   按照上面的意思Session State Provider is added 会话状态保存的功能已经有了? 经过下载解压后确认是有了这个功能,从1.2版本以后增加了SQL 脚本,即将Session保存到数据库中了-_-|||。很多人担心的Session问题终于有着落了。
c. BeIT Memcached Client (optimized C# 2.0)   这款最后一次更新时间是2010.8.4
    http://code.google.com/p/beitmemcached 
d. jehiah
    http://jehiah.cz/projects/memcached-win32 



经过一番比较,我还是比较看中a,b,c三款,分别下载下来测试一下吧。(鉴于服务端管理工具匹配还是推荐enyim比较好)

三、简单测试

1.memcacheddonet client测试 1.1.5版本 源码有SRC包

public class MemcachedBench 
    {
        /// <summary>
        /// Arguments: 
        ///        arg[0] = the number of runs to do
        ///        arg[1] = the run at which to start benchmarking
        /// </summary>
        /// <param name="args"></param>
        [STAThread]
        public static void Main(String[] args) 
        {
            int runs = 100;
            int start = 200;
            if(args.Length > 1)
            {
                runs = int.Parse(args[0]);
                start = int.Parse(args[1]);
            }

            //设置服务器列表
            string[] serverlist = { "172.16.76.98:11211", "172.16.0.21:11211", "172.16.125.76:11211", "172.16.125.76:11212" };

            // initialize the pool for memcache servers 创建连接池
            SockIOPool pool = SockIOPool.GetInstance();
            //设置服务器列表
            pool.SetServers(serverlist);
            //初始化
            pool.InitConnections = 3;
            pool.MinConnections = 3;
            pool.MaxConnections = 5;
            pool.SocketConnectTimeout = 1000;
            pool.SocketTimeout = 3000;
            pool.MaintenanceSleep = 30;
            pool.Failover = true;
            pool.Nagle = false;
            pool.Initialize();
            
            // initialize the pool for memcache servers 全属性注入
//            SockIOPool pool = SockIOPool.Instance;
//            pool.Servers = serverlist;  //属性方式配置//
//            pool.InitConn = 5;
//            pool.MinConn = 5;
//            pool.MaxConn = 50;
//            pool.MaintSleep = 30;
//            pool.SocketTO = 1000;//
//            pool.Nagle = false;
//            pool.Initialize();

//            // get client instance
            MemcachedClient mc = new MemcachedClient();
            mc.EnableCompression = false;  //是否启用压缩(通过ICSharpCode.SharpZipLib对存储的数据压缩)
          
//            MemcachedClient mc = new MemcachedClient();
//            mc.CompressEnable = false;
//            mc.CompressThreshold = 0;
//            mc.Serialize = true;    //新的类中已没有此属性了,是默认帮你序列了?还是要自己实现呢?           

            string keyBase = "testKey";
            string obj = "This is a test of an object blah blah es, serialization does not seem to slow things down so much.  The gzip compression is horrible horrible performance, so we only use it for very large objects.  I have not done any heavy benchmarking recently";

            //循环记时往服务器缓存上插入数据  等会我们要观察一下数据都存到哪个服务器上的Memcached server上了
            long begin = DateTime.Now.Ticks;
            for(int i = start; i < start+runs; i++) 
            {
                mc.Set(keyBase + i, obj);
            }
            long end = DateTime.Now.Ticks;
            long time = end - begin;

            //计算存储这些数据花了多长时间
            Console.WriteLine(runs + " sets: " + new TimeSpan(time).ToString() + "ms");

            //开始取数据,并记时
            begin = DateTime.Now.Ticks;
            int hits = 0;
            int misses = 0;
            for(int i = start; i < start+runs; i++) 
            {
                string str = (string) mc.Get(keyBase + i);
                if(str != null)
                    ++hits;    //成功取到数据
                else
                    ++misses;  //丢失次数
            }
            end = DateTime.Now.Ticks;
            time = end - begin;

            //获取这些数据花了多长时间
            Console.WriteLine(runs + " gets: " + new TimeSpan(time).ToString() + "ms");
            Console.WriteLine("Cache hits: " + hits.ToString());
            Console.WriteLine("Cache misses: " + misses.ToString());
            Console.WriteLine("--------------------------------------------------------\r\n");

            Console.WriteLine("各服务器状态:");
            Console.WriteLine("-------------------------------------------------------");
            IDictionary stats = mc.Stats();
            foreach(string key1 in stats.Keys)
            {
                Console.WriteLine(key1);
                Hashtable values = (Hashtable)stats[key1];
                foreach(string key2 in values.Keys)
                {
                    Console.WriteLine(key2 + ":" + values[key2]);
                }
                Console.WriteLine("-------------------------------------------------------");
            }
            //从这里可以看出SockIOPool应该建立了一张HashTable去管理所有连接池实例
            SockIOPool.GetInstance().Shutdown();

            Console.ReadLine();
        }


 

分别设置了3台,4台,5台服务器测试了3次,测试结果如下:

100条数据都确实存上去成功了,但是取数据命中率会随着服务器增多急剧下降!才几台测试服务器,结果就如此差!问题出在哪里了呢? 服务器的memcached.exe太老了? 还是看一下memcached的实现原理了

这里有我之前下载的一版memcached的原理介绍 下载

里面提到查找服务器端数据的算法 是求余算法 ,而这中算法的命中率很差,并且随便服务器节点的变动(增加或删除节点)命中率急剧下降。

难道是这个原因? 了解到memcached的客户端算法已经修改为Consistent Hashing算法,难道是我下载的客户端版本确实很老了? 顺便说一下这个Consistent Hashing算法是将服务器按环形分布在一个圆上,按我个人理解,服务器数量越多,环形分布越相对稳定,这个时候增删服务器对定位的影响都比较小。在之后两个客户端的版本再测试一次看看结果,这节就到这里吧。附一张用MemcacheD Manager监控图

  • Memcached分布式原理

    由于测试读取数据的命中率太差,去查阅了一下Memcached的分布式原理。

    先看一下应用场景

    第一次访问先从数据库中得到数据并保存到缓存中,第二次再读数据就从缓存中获取,这是正常情情,当第二次没有命中数据?这个时候你是否回数据库中读取数据呢?读取数据后,你是否还要保存到缓存中呢,但这个数据缓存中又是存在的,如何处理呢? 这就是Memcached客户端没有命中数据导致的后果。

    我们来看一下Memcached的原理:

    memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能,而是完全由客户端程序库实现的。服务端之间没有任何联系,数据存取都是通过客户端的算法实现的。客户端初始化的获取所有服务器的哈希列表,当需要存取数据就会检索这个哈希列表查找到对应的服务器。看下图

    当客户端要存取数据时,首先会通过算法查找自己维护到的服务器哈希列表,找到对应的服务器后,再将数据存往指定服务器。这里关键点是使用了什么算法!

    这个问题也不追查,接着往下看,我来再去取原先这个数据

    查找数据的原理和存取的原理是一样,首先通过算法在维护的哈希列表查到对应服务器,然后再去指定服务器读取数据。那问题来了,他是如何准确的找到这台服务器的呢?算法,算法就是他的原因,只要你在存和取的时候使用的算法是一样的,那算法计算的结果也是一致的,所以就可以正确的找到服务器了。

    那为什么我们在第三节的测试中,命中率会如此之差呢?

    我们使用的Memcachedonet client 老版本使用的是求余算法,我们来看看这个求余算法的定义--“根据服务器台数的余数进行分散”。即求得键的整
    数哈希值,再除以服务器台数,根据其余数来选择服务器。我们在存数据的时候,计算出这个数据键的CRC值,用这个值除以服务器台数求得余数来存往指定的服务器。那反过来取数据依然是这个算法,那结果肯定是一致的。问题是,为什么不同台数的服务器测试中,命中率会变化这么大呢。余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服
    务器,从而影响缓存的命中率。因为增删服务器后,数据键的CRC值是不变的,但是服务器的台数变了,导致求余的结果也发生变化了,从而影响了命中率。

    我们再来看一下改进的Consistent Hashing算法,可以确定是的,Memcachedonet的版本是没有采用这个算法。

    Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值,并将其配置到0~2的32次方的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过2的32次方仍然找不到服务器,就会保存到第一台memcached服务器上。

    当从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。如下图

    从上图可以看到,添加新的节点5时并不会重新分布所有节点,而是在之前的基础上某个位置插入新的节点,这样保证了整体的分布没有发生太大变化,并且顺时针方向的没有影响,逆时针方向的从第一台就开始有影响了。因此,Consistent Hashing最大限度地抑制了键的重新分布。但是这样的误差还是有的,因为根据服务器的哈希值来分布本身就是不均匀的。后面有提到改进的Consistent Hashing算法,即在圆环上预先分布为每台服务器分布一定数量的虚定拟节点,相当于我们均匀分布了圆环上的节点,当有节点增加或删除时都是在指定的位置上进行的就抑制了分布不均匀,最大限度地减小服务器增减时的缓存重新分。使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是,由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:(1 n/(n+m)) * 100

    (参考之前提供下载的PDF原理一文)

         memcachedonet 使用的是余数算法,可能是导致误差的原因。在下一节中,我们来学习一下enyim.client(memcachedproviders),并进行相关测试。

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值