热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作

首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl、PHP为主。仅仅memcached网站上列出的语言就有:Perl、PHP、Python、Ruby、C#、C/C++以及Lua等。
一、Memcached ClientLib For .Net

  首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl、PHP为主。 仅仅memcached网站上列出的语言就有:Perl、PHP、Python、Ruby、C#、C/C++以及Lua等。

  那么,我们作为.Net码农,自然是使用C#。既然Memcached客户端有.Net版,那我们就去下载一个来试试。

  下载文件:http://pan.baidu.com/s/1w9Q8I

  memcached clientlib项目地址:http://sourceforge.net/projects/memcacheddotnet/

  解压该包,里面有1.1和2.0两个版本的,这里我们使用2.0版本的。(在压缩包中的目录地址为:\memcacheddotnet_clientlib-1.1.5\memcacheddotnet\trunk\clientlib\src\clientlib\bin\2.0\Release)

  上面的这四个dll就是我们需要引入项目中的程序集,有了他们,我们就可以和Memcached服务器进行通信了,爽歪歪啊。

二、在.Net中进行Memcached基本操作

2.1 基本的Memcached客户端操作

  (1)首先,打开Windows Server 2003虚拟机,开启Memcached服务;(非必要操作,如果您是在本机,则可跳过这一步,只需开启Memcached服务即可)

  (2)①打开VS,新建一个C#的控制台应用程序,取名为:MemcachedClientDemo。

    ②新建一个文件夹,取名为Lib,然后将上面下载的客户端程序集dll拷贝到这个文件夹中,并添加对这几个dll的引用。

  (3)开始写代码,通过Memcached客户端与服务器进行通信,请参阅下面的代码:

 [STAThread] static void Main(string[] args)
        { // Memcached服务器列表 // 如果有多台服务器,则以逗号分隔,例如:"192.168.80.10:11211","192.168.80.11:11211" string[] serverList = { "192.168.80.10:11211" }; // 初始化SocketIO池 string poolName = "MyPool";
            SockIOPool sockIOPool = SockIOPool.GetInstance(poolName); // 添加服务器列表  sockIOPool.SetServers(serverList); // 设置连接池初始数目 sockIOPool.InitCOnnections= 3; // 设置连接池最小连接数目 sockIOPool.MinCOnnections= 3; // 设置连接池最大连接数目 sockIOPool.MaxCOnnections= 5; // 设置连接的套接字超时时间(单位:毫秒) sockIOPool.SocketCOnnectTimeout= 1000; // 设置套接字超时时间(单位:毫秒) sockIOPool.SocketTimeout = 3000; // 设置维护线程运行的睡眠时间:如果设置为0,那么维护线程将不会启动 sockIOPool.MaintenanceSleep = 30; // 设置SockIO池的故障标志 sockIOPool.Failover = true; // 是否用nagle算法启动 sockIOPool.Nagle = false; // 正式初始化容器  sockIOPool.Initialize(); // 获取Memcached客户端实例 MemcachedClient memClient = new MemcachedClient(); // 指定客户端访问的SockIO池 memClient.PoolName = poolName; // 是否启用压缩数据:如果启用了压缩,数据压缩长于门槛的数据将被储存在压缩的形式 memClient.EnableCompression = false;

            Console.WriteLine("----------------------------测试开始----------------------------"); // 01.简单的添加与读取操作 memClient.Set("test1", "edisonchou");
            Console.WriteLine("test1:{0}", memClient.Get("test1")); // 02.先添加后修改再读取操作 memClient.Set("test2", "jacky");
            Console.WriteLine("test2:{0}", memClient.Get("test2"));
            memClient.Set("test2", "edwin");
            Console.WriteLine("test2:{0}", memClient.Get("test2"));
            memClient.Replace("test2", "lousie");
            Console.WriteLine("test2:{0}", memClient.Get("test2")); // 03.判断Key值是否存在 if (memClient.KeyExists("test2"))
            {
                Console.WriteLine("Key:test2 is existed");
            } // 04.删除指定Key值的数据 memClient.Add("test3", "memcached");
            Console.WriteLine("test3:{0}", memClient.Get("test3"));
            memClient.Delete("test3"); if (!memClient.KeyExists("test3"))
            {
                Console.WriteLine("Key:test3 is not existed");
            } // 05.设置数据过期时间:5秒后过期 memClient.Add("test4", "expired", DateTime.Now.AddSeconds(5));
            Console.WriteLine("test4:{0}", memClient.Get("test4"));
            Console.WriteLine("Please waiting the sleeping time");
            System.Threading.Thread.Sleep(6000); if(!memClient.KeyExists("test4"))
            {
                Console.WriteLine("test4 is expired");
            }
            Console.WriteLine("----------------------------测试完成----------------------------"); // 关闭SockIO池  sockIOPool.Shutdown();

            Console.ReadKey();
        }

   这里,我们来细细分析下这段神奇的代码:

  ①首先定义了一个string类型的数组来记录Memcached服务器的IP与端口信息,这里需要注意的是如果有多台Memcached服务器,需要使用逗号分隔开,例如:"192.168.80.10:11211","192.168.80.11:11211","192.168.80.12:11211";

  ②SockIOPool是一个基于Socket(套接字)的连接池,换个方式理解:Memcached其实就是一个Socket的服务器端,它不停地接收Memcached客户端发来的读写请求命令。这里使用了SockIOPool.GetInstance("MyPool")来获取一个名为MyPool的连接池实例,看到GetInstance()这个静态方法,我们便知道这是采用了单例模式。后面我们为其配置了可访问的Memcached服务器列表、连接数、套接字超时时间等配置,最后调用Initialize()方法正式地初始化连接池,等待后面客户端的连接;

PS:神马是Socket?我们可以通过一个生活中的场景来理解:假如你要打电话给一个朋友,拿起电话先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等到你们的交流结束,挂断电话以结束此次交谈。So,这里的电话就是一个Socket,你打电话相当于申请了一个Socket,告诉了Socket你要打给谁(对方的电话号码你事先知道)。然后,你和对方进行聊天通话,相当于在向Socket发送数据和从Socket接收数据。最后,通话结束后,一方挂掉电话则相当于关闭Socket,撤销连接。

  在计算机网络的连接过程中,客户端Socket一般会记录服务器主机的IP地址、端口号,然后向服务器端进行连接并发送和接受数据。而服务器端开启一个监听的服务,则是相当于使用Socket指定监听的端口,然后等待客户端的连接,客户端连接后则产生一个会话。会话完成后,则关闭连接。

  ③创建一个新的MemcachedClient(Memcached客户端)对象,并指定要连接的套接字连接池的名称,设置是否启用压缩(这里设置为false)。这里我们了解一下为什么要设置是否起用压缩: 在Memcached中,数据是以Key/Value对的形式进行存储,Key的长度是有限制的,Memcached服务端内部限制Key为250字符,这个长度绝对是够用了,建议不要超过最大长度,尽量控制在200个字符以下。其实,我们最关心的还是Value的限制长度,Value的限制大小为1MB,那么如果有时候超过了1MB怎么办呢?这时候也许就可以使用压缩了,使用压缩后如果小于1Mb还是可以存储到该Key中。但如果即使压缩后还是超过1Mb,那可能会拆分到多个Key中去了。

PS:Key不能有空格和控制字符。推荐使用较短的Key,可以节省服务器内存和网络带宽。另外,最重要的一点是:Key不能重复!

  ④使用客户端为我们提供的各种读写API方法进行读写测试,如Set、Get、Replace、Add可以进行数据的添加和修改,而KeyExists则可以判断服务器中是否含有指定Key的数据,Delete则提供了删除指定Key的接口。这里,大家可以通过看代码就可以理解,我就不多废话了。大家可以注意到有个数据过期时间的可选参数,当数据在服务器中存储了一定时间后就会失效,这个参数相当有用。

  (4)现在我们通过调试,查看这段代码的结果:

2.2 进阶的Memcached客户端操作

  (1)在虚拟机中克隆已存在的Windows Server,并设置这两台服务器名称为:MemcacheServer1和MemcachedServer2,IP地址设置为:192.168.80.10与192.168.80.11,测试两台虚拟机与宿主机是否能够互相Ping通,为构建Memcached服务器集群做一个最小化的准备;

  (2)既然我们有了两台Memcached服务器,那我们得试试Memcached集群啊,由于Memcached的集群是在客户端实现,所以我们只需要将服务器的IP地址和端口号加入服务器列表的string数组就可以了。于是,我们修改上面的代码:

  ①首先新建一个App.config文件,新增一个AppSetting项如下:一般来说,服务器的地址信息都是写在配置文件中的,为了追求标准,我们也写在配置文件里边

  ②将serverList重新定义:使用配置文件里边的Value;这里需要注意的是,要使用ConfigurationManager这个类,需要在引用中添加对System.Configuration这个dll的引用;

    string[] serverList = ConfigurationManager.AppSettings["MemcachedServers"].Split(',');

  (3)现在我们先重启Memcached1(192.168.80.10)的Memcached服务,清空已经缓存的数据内容,确保两台服务器现在都没有数据;然后,重新运行代码,再次完成代码测试,测试结果还是如下图,说明我们配置的两台Memcached集群已经配置成功。

result

  (4)在虚拟机中使用telnet查看每台服务器具体保存了哪个Key/Value对,这里由于test3和test4均被删除或已失效,所以只需查看前两个Key/Value对:

  ①MemcacheServer1(192.168.80.10):保存了第二个Key/Value对,

  ②MemcacheServer2(192.168.80.11):保存了第一个Key/Value对,

  (5)到此,我们已经完成了一个最小化的memcached集群读写测试Demo。但是,在实际的开发场景中,远不仅仅是存储一个字符串,更多的是存储一个自定义的类的实例对象。这就需要使用到序列化,下面我们来新加一个类MyObject,让其作为可序列化的对象来存储进Memcached中。注意:需要为该类加上[Serializable]的特性!

 [Serializable] public class MyObject
    { public int ID
        { get; set;
        } public string Name
        { get; set;
        }
    }

  然后,在主代码中添加以下几行代码,增加对自定义对象的读写测试:

            // 06.自定义对象存储 MyObject myObj = new MyObject();
            myObj.ID = 12138;
            myObj.Name = "爱迪生周";
            memClient.Set("test5", myObj);
            MyObject newMyObj = memClient.Get("test5") as MyObject;
            Console.WriteLine("Hello,My ID is {0} and Name is {1}", newMyObj.ID, newMyObj.Name);

  最后,运行代码,查看结果如下:

  (6)怎么样,圆满完成对自定义对象的读写操作吧?现在,我们再看看这个自定义对象是存到了哪台服务器上:经查询,test5是存储到了MemcacheServer2(192.168.80.11)上。

三、回头再看Memcached数据访问模型

  经过了刚刚一系列的实践操作,我们在一个最小化的由两台Windows Server搭建的Memcached集群上进行了读写操作测试。那么,我们不由得想要去看看到底Memcached是怎样进行数据访问的呢?别急,现在我们就来看看,由实践到理论,深入理解一下。

  (1)添加新的键值对数据

基于客户端的分布式

  从图中可以看出,Memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能,而是完全由客户端程序库实现的。服务端之间没有任何联系,数据存取都是通过客户端的算法实现的。当客户端要存取数据时,首先会通过算法查找自己维护的服务器哈希列表,找到对应的服务器后,再将数据存往指定服务器。例如:上图中应用程序要新增一个<'tokyo',data>的键值对,它同过set操作提交给Memcached客户端,客户端通过一定的哈希算法(比如:一般的求余函数或者强大的一致性Hash算法)从服务器列表中计算出一个要存储的服务器地址,最后将该键值对存储到计算出来的服务器里边。

  (2)获取已存在的键值对数据

Get

  上图中应用程序想要获取Key为‘tokyo’(东京这么热,还要取它的值是干神马呢?)的Value,于是它向Memcached客户端提交了一个Get请求,Memcached客户端还是通过算法从服务器列表查询哪台服务器存有Key为‘tokyo’的Value(即选择刚刚Set到了哪台服务器),如果查到,则向查到的服务器请求返回Key为‘tokyo’的数据。

  (3)Memcached分布式的核心—一致性Hash算法

  一致性Hash算法是分布式缓存的核心理论,我也学习得不深入,也只是刚刚了解了一下,后面我有空深入学习一下,再单独写一篇博文来介绍它,并使用C#来粗略地实现一下这个算法。现在我就简单地介绍一下,其实这部分内容我之前写入了我的另一篇博文《大型网站技术架构读书笔记之网站的可伸缩架构》中,有兴趣的朋友也可以去看看这篇文章。

  首先,简单的路由算法(通过使用余数Hash)无法满足业务发展时服务器扩容的需要:缓存命中率下降。例如:当3台服务器扩容至4台时,采用普通的余数Hash算法会导致大约75%(3/4)被缓存了的数据无法正确命中,随着服务器集群规模的增大,这个比例会线性地上升。那么,可以想象,当100台服务器的集群中加入一台服务器,不能命中的概率大概是99%(N/N+1),这个结果显然是无法接受的。那么,能否通过改进路由算法,使得新加入的服务器不影响大部分缓存数据的正确性呢?请看下面的一致性Hash算法。

  一致性Hash算法通过一个叫做一致性Hash环的数据结构实现KEY到缓存服务器的Hash映射,如下图所示:

一致性Hash

  具体算法过程是:

  ①先构造一个长度为0~2^32(2的32次幂)个的整数环(又称:一致性Hash环),根据节点名称的Hash值将缓存服务器节点放置在这个Hash环中,如上图中的node1,node2等;

  ②根据需要缓存的数据的KEY值计算得到其Hash值,如上图中右半部分的“键”,计算其Hash值后离node2很近;

  ③在Hash环上顺时针查找距离这个KEY的Hash值最近的缓存服务器节点,完成KEY到服务器的Hash映射查找,如上图中离右边这个键的Hash值最近的顺时针方向的服务器节点是node2,因此这个KEY会到node2中读取数据;

  当缓存服务器集群需要扩容的时候,只需要将新加入的节点名称(如node5)的Hash值放入一致性Hash环中,由于KEY总是顺时针查找距离其最近的节点,因此新加入的节点只影响整个环中的一部分。如下图中所示,添加node5后,只影响右边逆时针方向的三个Key/Value对数据,只占整个Hash环中的一小部分。

node5

  因此,我们可以与之前的普通余数Hash作对比:采用一致性Hash算法时,当3台服务器扩容到4台时,可以继续命中原有缓存数据的概率为75%,远高于普通余数Hash的25%,而且随着集群规模越大,继续命中原有缓存数据的概率也会随之增大。当100台服务器增加1台时,继续命中的概率是99%。虽然,仍有小部分数据缓存在服务器中无法被读取到,但是这个比例足够小,通过访问数据库也不会对数据库造成致命的负载压力。

四、学习小结

  在本篇我首先花了大力气来介绍如何使用Memcached客户端在.Net中进行常用的基础读写操作,并通过VMWare Workstation构建了一个由两台Windows Server组成的最小化的Memcached服务器集群。其次,我通过使用C#调用Memcached客户端,将数据保存到Memcached服务器集群中,并验证了是否保存于集群中。最后,返回到Memcached的数据访问模型上,从理论到实践,再从实践返回到理论,理解Memcached的互不通信的集群模式与数据读写流程,并简单了解了一下分布式技术中最核心的算法:一致性Hash算法。

  不知不觉都快1:20了,今天就到此停笔关机,洗洗睡了。后面,我会介绍在ASP.NET MVC中应用Memcached来解决登录状态的案例,也就是Session会话对象的分布式存储。如果大家觉得有用或者有兴趣,那就敬请期待了,也请麻烦点个“推荐”,让我更有动力写下去,谢谢!

参考文献

  (1)传智播客马伦,《Memcached公开课》,http://bbs.itcast.cn/thread-14836-1-1.html

  (2)charlee,《Memcached完全剖析》,http://kb.cnblogs.com/page/42731/

  (3)小城岁月,《分布式缓存Memcached入门》,http://www.cnblogs.com/mecity/archive/2011/06/13/Memcached.html

  (4)吸水的技术点点,《分布式缓存系统Memcached简介与实践》,http://www.cnblogs.com/zjneter/archive/2007/07/19/822780.html

  (5)源码工作室,《揭开Socket编程的面纱》,http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx

附件下载

  (1)Memcached ClientLib:http://pan.baidu.com/s/1w9Q8I

  (2)MemcachedClientDemo:http://pan.baidu.com/s/1hqrDUss

 


推荐阅读
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • php课程Json格式规范需要注意的小细节
    JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgramming ... [详细]
  • 1、概述首先和大家一起回顾一下Java消息服务,在我之前的博客《Java消息队列-JMS概述》中,我为大家分析了:然后在另一篇博客《Java消息队列-ActiveMq实战》中 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 本文是关于C#类型系统、值类型和引用类型的概念性笔记。介绍了C#1系统类型的三个特性,静态类型的含义,显式类型和隐式类型的区别。还讨论了类、结构、数组类型、枚举、委托类型和接口类型属于哪一种类型。同时纠正了关于结构、引用类型和对象传递的错误表述。最后提到了C#4中使用动态类型的关键字。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • Unity3D引擎的体系结构和功能详解
    本文详细介绍了Unity3D引擎的体系结构和功能。Unity3D是一个屡获殊荣的工具,用于创建交互式3D应用程序。它由游戏引擎和编辑器组成,支持C#、Boo和JavaScript脚本编程。该引擎涵盖了声音、图形、物理和网络功能等主题。Unity编辑器具有多语言脚本编辑器和预制装配系统等特点。本文还介绍了Unity的许可证情况。Unity基本功能有限的免费,适用于PC、MAC和Web开发。其他平台或完整的功能集需要购买许可证。 ... [详细]
  • gitlab重置password
    ruby没怎么学,自己搭建的gitlab的rootpassword又忘了。幸好看见此帖子,试验okhttp:roland.kierkels.netgitreset-your-git ... [详细]
  • Allegro总结:1.防焊层(SolderMask):又称绿油层,PCB非布线层,用于制成丝网印板,将不需要焊接的地方涂上防焊剂.在防焊层上预留的焊盘大小要比实际的焊盘大一些,其差值一般 ... [详细]
  • 导读:在编程的世界里,语言纷繁多样,而大部分真正广泛流行的语言并不是那些学术界的产物,而是在通过自由发挥设计出来的。和那些 ... [详细]
author-avatar
鄙人fisher_779
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有