一、概述
1. Memcache是什么?
Memcache(Memcached)是一个高性能的分布式的内存对象缓存系统。
Memcache是danga的一个项目,最早为LiveJournal服务的,最初是为了加速LiveJournal访问速度而开发的,后来被很多大型的网站采用。
Memcache是高性能的分布式的内存对象缓存系统,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力。主要是通过在内存里维护一个统一的巨大的Hash表,它能够用来存储各种类型的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中 ,然后从内存中读取,从而大大提高数据的读取速度。
Memcache是以守护进程的方式运行于一个或者多个服务器中,随时会接受客户端的连接和操作。
说明:
1. 什么是守护进程?
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。
Linux的大多数服务器就是用守护进程实现的。
2.为什么会有Memcache和Memcached两种名称?
Memcache是个项目名称,而Memcached是它的服务器端的主程序文件名。
3.Memcache的数据对象
Memcache存储的对象都是以keyàvalue的形式存储在服务器端的内存中。但是在它中存储的数据不是持久的,一旦服务器停止或者重启,数据就会丢失。
4.Memcache的主要特点
1) 基于C/S架构,协议简单。Memcache的服务器客户端通信并不使用复杂的XML等格式,二使用简单的基于文本行的协议。因此,通过telnet也能在Memcache上保存数据、取得数据。
2) 基于Libevent的事件处理。Libevent是一套跨平台的事件处理接口,能够兼容Windows和Linux操作系统,Memcached使用Libevent来进行网络并发连接的处理,能够保持在很大并发情况下,仍旧能够保持快速的响应能力。
3) 自主内存存储处理。为了提高性能,Memcached中保存的数据都存储在Memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启服务、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
4) 基于客户端的Memcached分布式。
5.如何在Java开发中使用Memcache
在Java开发中使用Memcache一般要用到以下几个程序:
1).Memcached
该程序用来在Linux或者Windows服务器上建立和管理缓存。
2).Magent
Magent是一款开源的Memcached代理服务器软件,使用它可以搭建高可用性的集群应用的Memcached服务。
3).Memcached客户端程序
有三种,推荐使用memcached client for java
4).其他
Libevent,在Linux环境下应用Memcached时,Memcached用到了libevent这个库,用户Socket处理,所以还需要安装libevent.
最新版本:libevent-1.4.13
Windows下安装需要下载安装包:下载安装包:memcached-1.2.6-win32-bin.zip
二、工作原理
首先Memcached是以守护程序方式运行于一个或者多个服务器中,随时接收客户端的连接操作,客户端可以有各种语言编写,目前已知的客户端API包括Perl/PHP/Python/ruby/Java/C#/C等待。客户端与Memcached服务建立链接后进行对象存取,接下来的事情就是存取对象了,每个存取对象都有唯一的标识key,存取操作都是通过这个key进行的,保存到Memcached 中的对象实际上是放置到内存中的,并不是保存在cache文件中的,这也是为什么Memcached能够如此高效快速的原因。但是这些存储到cache中的对象并不是持久的,服务停止或者重启,里面的数据都会丢失。
与许多的cache工具类似,Memcached的原理并不复杂。它采用了C/S模式架构,使用TCP连接与服务器通信,在server端启动服务进程,启动时可以指定监听的ip、端口号、使用的内存大小等参数。一旦启动,服务就一直处于可用状态。Memcached允许多个server协同工作,但是这些server之间是没有任何通讯关系的,每个server只是对自己的数据进行管理。
图1 :Memcache和数据库交互流程图
如上图所示:传统的查询方法是直接查询数据库,然后由数据库将查询结果返回给查询语句;而如果使用Memcache作为中间缓存层时,查询的是Memcache的缓存数据,这样就减少了数据库层的访问。
- 查询数据(select):首先通过指定的key查询(get)Memcache中间缓存数据,如果存在相对应的数据,则直接获取数据结果,查询过程完全不需要查询数据库。如果不存在,则查询数据库,并以key对应value的形式将查询结果存储在Memcache缓存数据中,然后将结果返回给查询语句。
- 更新数据(update):首先要更新数据,然后删除相关的Memcache数据。
- 增加数据(add):首先删除相关的缓存数据,然后增加数据。
- 删除数据(delete):删除数据,并删除Memcache数据。
三、Windows下的Memcached安装
1. 安装步骤
- 下载安装包:memcached-1.2.6-win32-bin.zip,解压在某个盘下,如c:memcached
- 安装:在cmd命令界面下输入:memcached –d install,回车。
- 启动服务:memcached –d start,回车,也可以在服务中手动启动服务。
停止服务:memcached –d stop
卸载命令: memcached –d uninstall
- 下载java_memcached-release_2.6.6.zip,获得java支持
- 进行java程序的部署和测试。
2. Memcache的基本设置
memcached的基本设置:
-p 监听的端口
-l 连接的IP地址, 默认是本机
-d start 启动memcached服务
-d restart 重起memcached服务
-d stop|shutdown 关闭正在运行的memcached服务
-d install 安装memcached服务
-d uninstall 卸载memcached服务
-u 以的身份运行 (仅在以root运行的时候有效)
-m 最大内存使用,单位MB。默认64MB
-M 内存耗尽时返回错误,而不是删除项
-c 最大同时连接数,默认是1024
-f 块大小增长因子,默认是1.25
-n 最小分配空间,key+value+flags默认
3. 设置Memcache缓存大小和端口
Memcache的默认启动时的参数可能不满足实际生产环境的需要,于是就想到直接修改windows服务的启动参数,操作如下:
打开注册表(regedit),找到:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\memcached Server,其中的ImagePath项的值为: c:\memcached\memcached.exe" -d runservice
改成:c:\memcached\memcached.exe" d runservice -m 1024 -c2048.
其中,-p就是端口,-m就是缓存大小,以M为单位。
4. 查看Memcache的内部运行状态
memcached有个名为stats的命令,使用它可以获得各种各样的信息。执行命令的方法很多,用telnet最为简单:
格式:telnet 主机名 端口号
连接到memcached之后,输入stats再按回车,即可获得包括资源利用率在内的各种信息。此外,输入"stats slabs"或"stats items"还可以获得关于缓存记录的信息。结束程序请输入quit。
四、Linux下的Memcached安装
前提:
- 下载安装包libevent-1.4.9-stable.tar.gz和memcached-1.4.5.tar.gz。
- 在Linux系统上新建目录memcache,将安装包上传到该目录下。
1. 编译安装libevent
tar zxvf libevent-1.4.9-stable.tar.gz
cd libevent-1.4.9-stable/
./configure --prefix=/usr
make && make install
cd ../
2. 编译安装Memcached
tar zxvf memcached-1[1].4.5.tar.gz
cd memcached-1.4.5/
./configure --with-libevent=/usr
make && make install
cd ../
3. 启动一个Memcached的服务器端
进入到Memcached的安装目录,如
#cd /memcached-1.4.5
#./memcached -d -m 50 -u root -c 256 -P /tmp/memcached.pid
#./memcached -d -m 10 -u root -l 127.0.0.1 -p 1521 -c 256 -P /tmp/memcached.pid
参数介绍:
-d 选项是启动一个守护进程,
-m 是分配给Memcache使用的内存数量,单位是MB,这里是10MB
-u 是运行Memcache的用户,这里是root,
-l 是监听的服务器IP地址,这里指定了服务器的IP地址127.0.0.1,
-p 是设置Memcache监听的端口,这里设置了6957,最好是1024以上的端口,
-c 是最大运行的并发连接数,默认1024,这里是256,按照服务器的负载量来设定。
-P 是设置保存Memcache的pid文件,我这里是保存在/tmp/memcached.pid
4. 结束一个Memcache进程
如果要结束Memcache进程,执行:
# kill -9 进程id
也可以启动多个守护进程,不过端口不能重复。
五、Memcached在telnet命令下的操作
1. 测试Memcached进程是否启动成功
#telnet 127.0.0.1 11211
启动进程后,使用telnet命令测试Memcached是否启动成功。telnet连通说明进程启动成功,否则,未成功。
2. 查看Memcached进程的状态
使用telnet命令连接上memcache后直接输入stats当前memcache的状态。
3. 简单数据操作
1) set增加一个信息
保存数据。
2) get获取一个信息
查看value,数据已存在。
3) replace一个信息
查看value,数据已经替换了。
4) delete删除一个信息
查看value,数据已经不存在了。
5) 其他方法
六、Memcache的安全
我们上面的Memcache服务器端都是直接通过客户端连接后直接操作,没有任何的验证过程,这样如果服务器是直接暴露在互联网上的话是比较危险,轻则数据泄露被其他无关人员查看,重则服务器被入侵,因为Memcached是以root权限运行的,况且里面可能存在一些我们未知的bug或者是缓冲区溢出的情况,这些都是我们未知的,所以危险性是可以预见的。为了安全起见,这里做两点建议,能够稍微的防止黑客的入侵或者数据的泄露。
1. 内网访问
最好把两台服务器之间的访问是内网形态的,一般是Web服务器跟Memcached服务器之间。普遍的服务器都是 有两块网卡,一块指向互联网,一块指向内网,那么就让Web服务器通过内网的网卡来访问Memcached服务器,我们Memcached的服务器上启动的时 候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。
# memcached -d -m 1024 -u root -l 127.0.0.1 -p 11211 -c 1024 -P /tmp/memcached.pid
Memcache服务器端设置监听通过内网的127.0.0.1的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接。
2. 设置防火墙
防火墙是简单有效的方式,如果却是两台服务器都是挂在网的,并且需要通过外网IP来访问Memcached的话,那么可以考虑使用防火墙或者代理程序来过滤非法访问。一般我们在Linux下可以使用iptables或者FreeBSD下的ipfw来指定一些规则防止一些非法的访问,比如我们可以设置只允许我们的Web服务器来访问我们Memcached服务器,同时阻止其他的访问。
# iptables -F
# iptables -P INPUT DROP
# iptables -A INPUT -p tcp -s 127.0.0.1 –dport 11211 -j ACCEPT
# iptables -A INPUT -p udp -s 127.0.0.1 –dport 11211 -j ACCEPT
上面的iptables规则就是只允许127.0.0.1这台Web服务器对Memcached服务器的访问,能够有效的阻止一些非法访问,相应的也可以增加一些其他的规则来加强安全性,这个可以根据自己的需要来做。
六、Memcached客户端程序
Memcached的java客户端已经存在三种了:
1)官方提供的基于传统阻塞io由Greg Whalin维护的客户端
2)Dustin Sallings实现的基于java nio的Spymemcached
3)XMemcached
1.三种API比较
1) memcached client for java
较早推出的memcached JAVA客户端API,应用广泛,运行比较稳定。
2) spymemcached
A simple, asynchronous, single-threaded memcached client written in java. 支持异步,单线程的memcached客户端,用到了java1.5版本的concurrent和nio,存取速度会高于前者,但是稳定性不好,测试中常报timeOut等相关异常。
3) xmemcached
XMemcached同样是基于java nio的客户端,java nio相比于传统阻塞io模型来说,有效率高(特别在高并发下)和资源耗费相对较少的优点。传统阻塞IO为了提高效率,需要创建一定数量的连接形成连接池,而nio仅需要一个连接即可(当然,nio也是可以做池化处理),相对来说减少了线程创建和切换的开销,这一点在高并发下特别明显。因此XMemcached与Spymemcached在性能都非常优秀,在某些方面(存储的数据比较小的情况下)Xmemcached比Spymemcached的表现更为优秀,具体可以看这个Java Memcached Clients Benchmark。
2. 建议
由于memcached client for java发布了新版本,性能上有所提高,并且运行稳定,所以建议使用memcached client for java。
七、分布式应用
Memcached虽然一直说是分布式缓存应用,但是这个分布式并不是有ms(memcached server)来去实现如何分布,ms之间并不互相通信,所以真正实现分布式缓存是由mc(memcached client)来实现,那具体的实现算法是什么呢?
Memcached的分布算法一般有两种,分别是hash模余算法和Consistent hashing算法。
1. Hash模余算法
简单概括就是“根据服务器台数的余数进行分散”,即根据唯一键值的哈希值与存放服务器列表中的服务器台数进行模数运算来选择将数据存放到那台服务器上。首先求的键的整数哈希值,再除以服务器台数,根据余数来选择服务器。访问memcached服务的所有客户端操作数据时都必须使用同一种哈希算法和相同的服务器配置列表,否则就会获取不到数据或者重复存放数据。由于不同数据的唯一键值对应的哈希值不同,所以不同数据就有可能分散到不同的服务器上,从而达到多个服务器负载均衡的目的。
缺点:当添加或者移除服务器时,缓存重组的代价相当大。因为添加或者移除服务器时(特别是某台服务器down掉之后),服务器台数的减少导致余数产生变化,这样就无法保证获取时计算的服务器节点与保存时相同,这就意味着突然之间几乎所有的cache都失效,从而影响缓存的命中率——造成原有缓存数据大规模失效。
2. Consistent hashing算法
Consistent hashing 是一种 hash 算法,简单的说,在添加或者移除一个服务器时,它能够尽可能小的改变已存在key 映射关系。
- 环形Hashing空间:考虑通常的 hash 算法都是将 value 映射到一个 32位的 key 值,也即是 0~232-1次方的数值空间;我们可以将这个空间想象成一个首( 0 )尾(232-1)相接的圆环。
- 把对象映射到Hashing空间中:通过Hash函数计算出key的哈希值,然后分布在步骤1中的圆上。
- 把cache映射到环形空间中:Consistent hashing 的基本思想就是将对象和 cache 都映射到同一个 hash 数值空间中,并且使用相同的 hash 算法,求出memcached服务器节点(IP地址或者域名或者服务器名)的哈希值,并将其映射到步骤1中的圆上。
- 把对象再与cache映射上:现在 cache 和对象都已经通过同一个 hash 算法映射到 hash 数值空间中了,然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一台服务器上。如果超过超过232仍未找到服务器,将会讲数据保存到第一台memcached服务器上。
图2:Consistent hashing算法基本原理
当添加或者移除一台服务器:
- 添加服务器:在node2和node4之间添加一台服务器node5,这时受影响的将仅是那些沿node5逆时针遍历直到下一个节点(node2)之间的对象(添加node5之前是node4上的对象),将这些对象重新映射到node5上即可。
图3:添加服务器
- 移除服务器:如果node2挂了,根据上面讲到的映射方法,这时收影响的将仅是那些沿node2逆时针遍历直到下一个节点(node1)之间的对象,也即是本来映射到node2上的那些对象,因此需要变动node1和node2上的键,让其移动到node4上。
图4:移除服务器
虚拟节点:
考量 Hash 算法的另一个指标是平衡性 (Balance)。,定义如下:
平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。hash 算法并不是保证绝对的平衡,如果 cache 较少的话,对象并不能被均匀的映射到 cache 上,比如在上面的例子中,仅部署 node1和 node4的情况下,在 9 个对象中, node1仅存储了 3个对象 ,而node4 则存储了6个对象,分布是很不均衡的。
为了解决这种情况, consistent hashing 引入了“虚拟节点”的概念,它可以如下定义:
“虚拟节点”( virtual node )是实际节点在 hash 空间的复制品( replica ),一实际个节点对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以 hash 值排列。仍以仅部署node1 和 node4的情况为例,在图 4 中我们已经看到, cache 分布并不均匀。现在我们引入虚拟节点,并设置“复制个数”为 4,这就意味着一共会存在 8 个“虚拟节点”, node1#1、node1#2 、node1#3和 node1#4 代表了node1 , node4#1、node4#2 、node4#3和 node4#4代表了node4 ,假设一种比较理想的情况,参见图5。
图5:引入"虚拟节点"后的映射关系
此时,对象到"虚拟节点"的映射关系为:每个节点映射一个对象,除了node4#2之外。这样的映射较之前的那种平衡性提高了很多。引入“虚拟节点”后,映射关系就从 { 对象 -> 节点 } 转换到了 { 对象 -> 虚拟节点 } 。查询物体所在服务器时的映射关系如图6所示。
图6:查询对象所在cache
“虚拟节点的” hash 计算可以采用对应节点的 IP 地址加数字后缀的方式。例如假设 cnode1的IP地址为 127.0.0.1。引入"虚拟节点"前,计算 node1 的 hash 值为:Hash("127.0.0.1")。引入虚拟节点后,计算虚拟节点node1#1、node1#2 、node1#3和 node1#4 的 hash 值分别为:
Hash("127.0.0.1#1"); // node1#1
Hash("127.0.0.1#2"); // node1#2
Hash("127.0.0.1#3"); // node1#3
Hash("127.0.0.1#4"); // node1#4