Memcached缓存技术
在开始接触memcached的时候并不了解它是什么,有什么作用。然后就在网上查阅的许多资料,但是网上资料太多又不是很清楚、全面。所以就是整合了网上诸多资料做了一下总结。
Memcached介绍
1.memcached是什么?
memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。现在已成为 mixi、 hatena、 Facebook、 Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。力,让Memcache作为一个缓存区域,把部分信息保存在内存中,在前端能够迅速的进使用Memcache的网站一般流量都是比较大的,为了缓解数据库的压行存取。
2. 协议简单
memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。
协议文档位于memcached的源代码内,也可以参考以下的URL。
http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
3. 基于libevent的事件处理
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。 memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。关于事件处理这里就不再详细介绍,可以参考Dan Kegel的The C10K Problem。
libevent: http://www.monkey.org/~provos/libevent/
The C10K Problem: http://www.kegel.com/c10k.html
4. 内置内存存储方式
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
5. memcached不互相通信的分布式
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现。
图2 memcached的分布式
安装memcached
memcached的安装比较简单,这里稍加说明。
memcached支持许多平台。
l Linux
l FreeBSD
l Solaris (memcached 1.2.5以上版本)
l Mac OS X
另外也能安装在Windows上。
1. Linux下Memcache服务器端的安装
memcached安装与一般应用程序相同,configure、make、make install就行了。
服务器端主要是安装memcache服务器端,目前的最新版本是 memcached-1.4.8 。
下载:http://www.danga.com/memcached/dist/memcached-1.2.2.tar.gz
另外,Memcache用到了libevent这个库用于Socket的处理,所以还需要安装libevent,libevent的最新版本是libevent-1.4。(如果你的系统已经安装了libevent,可以不用安装)
官网:http://www.monkey.org/~provos/libevent/
下载:http://www.monkey.org/~provos/libevent-1.3.tar.gz
1.分别把memcached和libevent下载回来,放到你指定的目录中,例如: /tmp 目录下:
2.先安装libevent:
# tar zxvf libevent-1.2.tar.gz
# cd libevent-1.2 //进入linux系统中libevent所在目录
# ./configure --prefix=/usr/local/libevent
# make
# make install
3.测试libevent是否安装成功:
# ls -al /usr/lib | grep libevent
lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent-1.2.so.1 -> libevent-1.2.so.1.0.3
-rwxr-xr-x 1 root root 263546 11?? 12 17:38 libevent-1.2.so.1.0.3
-rw-r–r– 1 root root 454156 11?? 12 17:38 libevent.a
-rwxr-xr-x 1 root root 811 11?? 12 17:38 libevent.la
lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent.so -> libevent-1.2.so.1.0.3
还不错,都安装上了。
4.安装memcached,同时需要安装中指定libevent的安装位置:
# cd /tmp
# tar zxvf memcached-1.2.0.tar.gz
# cd memcached-1.2.0 //进入linux系统中memcached所在目录
# ./configure –-prefix=/usr/local/memcached --with-libevent=/usr/local/libevent
# make
# make install
如果中间出现报错,请仔细检查错误信息,按照错误信息来配置或者增加相应的库或者路径。
5.测试是否成功安装memcached:
# ls -al /usr/local/mem*
-rwxr-xr-x 1 root root 137986 11?? 12 17:39 /usr/local/bin/memcached
-rwxr-xr-x 1 root root 140179 11?? 12 17:39 /usr/local/bin/memcached-debug
6.作软连接,否则运行memcached的时候将找不到libevent模块(注意你安装的libevent 的版本,以下为libevent安装目录中libevent-1.2.so.1)
# ln -s /usr/local/libevent/lib/libevent-1.2.so.1 /usr/lib
7.启动memcached (/usr/local/memcached为memcached的安装路径,到memcached的bin中启动memcached)
# /usr/local/memcached/bin/memcached -d -m 256 -p 11211 -u root
2. Windows下的Memcache安装
1. 下载memcache的windows稳定版 (在http://www.splinedancer.com/memcached-win32/ 下载 memcached 1.2.4 Win32 Beta Binaries),解压放某个盘下面,比如在c:\memcached
2. 在终端(也即cmd命令界面)下输入 ‘c:\memcached\memcached.exe -d install’ 安装
3. 再输入: ‘c:\memcached\memcached.exe -d start’ 启动。NOTE: 以后memcached将作为windows的一个服务每次开机时自动启动。这样服务器端已经安装完毕了。
3. Memcached启动、关闭参数
启动参数注释如下:
-p <num> 指定服务TCP端口,默认为11211
-U <num> 指定服务UDP端口 默认11211表示打开,设置0表示关闭
-s <file> 指定unix domain socket的文件名,则关闭端口绑定
-a <mask> 文件属性屏蔽字。8进制,默认0700(和-s配合使用)
-l <ip_addr> 指定监听IP地址(默认对所有网卡IP都生效)
-d run as a daemon 以精灵程序的方式运行
-r 设置coredump文件SIZE至上限
-u <username> 指定进程用户身份(只有以root运行时才有效,且root身份必须指定用户身份,memcached禁止以进程用户身份为root)
-m <num> 分配给memcached用作缓存的内存大小,单位为MB。默认64MB。
注意32位系统最大可管理的内存上限为3G,预留一些,测试分配2.8G是有效的。不要给memcached分配超过机器内存数,否则会自动使用swap分区,让性能大降,产生超时。(调优参数)
-M LRU算法开关。缓存满时不使用LRU算法替换旧的数据,返回错误
每个slab内的数据使用LRU,不是全局的。因此,在一些情况下,反而会影响命中率(例如多数KEY-VALUE大小近似,都保存在同一slab的情况下)
-c <num> 最大并发连接数。默认1024。 memcached使用libevent, 可以支持10K个连接。且memcached推荐客户端缓存连接(长连接)(调优参数)
-k 是否调用mlockall()锁定内存页。
注意:如果锁定了内存页,则若申请内存大小超过锁定值(可以通过ulimit -l查看)时就有可能失败(不建议使用。默认ulimit -l只有64KB)
-v 输出详细信息(在event轮询时输出错误或警告信息,以daemon方式运行无效)
-vv 输出更详细的信息(包括输出客户端的命令及回应信息)
-vvv 输出最详细的信息(包括输出内部的状态信息)
-h 打印版本、帮助信息及退出
-i 打印memcached及libevent的licence
-P <file> 保存进程ID到指定文件。与-d配合使用
-f <factor> chunk size的增长因子(合理范围1.05~2,默认:1.25)(1.2版本之前默认是2)
(调优参数)
-n <bytes> 每个ITEM保存数据占用的最小空间。最小空间大小=KEY的长度+value的长度+flags的长度(默认为48字节) 而每个ITEM的实际占用空间为 ITEM保存占用数据保存的最小空间 + ITEM结构占用的空间 = 48 + 32 = 80字节。因此chunk size的默认初始值为80字节(调优参数)
-L 尝试使用大容量内存页(如果可能的话)。增加内存页容量可以减少虚存-物理内存映射页表缓冲命中失败(TLB-MISSED)的次数, 并提高性能。但memcached会在启动的时候立即向OS申请-m参数指定的最大缓存块。(调优参数)
-D <char> 指定统计报告时ID同KEY之间的分隔符。默认的分隔符为“:”。 如果指定了此参数,统计采集功能就会自动启动;否则,可以通过发送“stats detail on”命令启动统计采集功能。
-t <num> 默认会创建4个工作线程,主线程用于监听客户建立的连接请求、accpet请求,然后通过管道通知子线程,由子线程处理读写请求。 memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个工作(workers)线程,无论是主线程还是workers线程全部通过libevent 管理网络事件, 实际上每个线程都是一个单独的libevent实例。建议不要超过系统CPU的个数。(调优参数)
-R 每次事件触发响应新连接的最大数目。设置此限制是防止其他I/O事件“挨饿”,得不到响应。每个工作线程都单独建立了libevent事件触发。(调优参数,一般不需要调整)
-C Disable use of CAS 关闭'CAS'指令。意思是说如果这个值我最后一次取的没有被修改的话才存储这个值,比如我先获取一个key为”update_time”的值,然后有其他进程修改了这个值,此时我再调用cas设置这个值时则会返回一个EXSISTS的错误表示修改失败。默认支持cas指令。则每次value的修改,都会记录一个CAS序列号(CAS_UNIQUE)。gets指令会返回CAS_UNIQUE。
-b 指定监听队列长度(listen()的参数)。默认为1024。(调优参数。不需要调整,内核对监听队列长度有个上限)
-B 指定绑定的memcached协议。包括:ascii: 文本协议;binary: 二进制协议;auto: 自动检测(默认选项)
-I 改变slab page的容量大小,以调整ITEM容量的最大值,默认为1MB。设置参数:<number>[k|K|m|M]不能少于1024bytes(即1K),不能大于128MB。memcached不推荐大于1MB,大于1MB增加了最低的内存要求,并会减少记忆效率。调优参数)注:低版本memcached 如memcached-1.2.6不支持该参数,1.4以上可以;测试时,我分配了2M的Page,但是实际上缓存的数据大小仍不能大于1M,其他资料说memcached缓存的item不能大于1M
-S Turn on Sasl authentication
启动SSL认证。需要在编译支持SSL。使用SSL认证时,只能使用二进制协议,不能使用文本协议。
Java使用Memcached的例子
许多语言都实现了连接memcached的客户端,其中以Perl、PHP为主。仅仅memcached网站上列出的语言就有
l Perl
l PHP
l Python
l Ruby
l C#
l C/C++
l Lua
l Java
等等。
1.下载java客户端
可以从https://github.com/gwhalin/Memcached-Java-Client下载java客户端
点击Downloads进入下载页面
选择要下载的版本下载,一般选择版本稍低的包,版本高的还有些bug,不够稳定。
解压,获取jar包。建立一个基本的Java工程吧。引入jar包。如图示:
2.Java中如何使用Memcached
package com.memcahce;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
public class MemCachedManager {
// 创建全局的唯一实例
protected static MemCachedClient mcc = new MemCachedClient();
protected static MemCachedManager memCachedManager = new MemCachedManager();
// 设置与缓存服务器的连接池
static {
// 服务器列表和其权重
String[] servers = { "192.168.1.107:11211","127.0.0.1:11211" };
Integer[] weights = { 3 };
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置服务器信息
pool.setServers(servers);
pool.setWeights(weights);
// 设置初始连接数、最小和最大连接数以及最大处理时间
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
//设置最大空闲时间为6小时
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// Tcp的规则就是在发送一个包之前,本地机器会等待远程主机对上一次发送的包的确认信息到来;这个方法就可以关闭套接字的缓存,以至这个包准备好了就发;
pool.setNagle(false);
//连接建立后对超时的控制
pool.setSocketTO(3000);
//连接建立时对超时的控制
pool.setSocketConnectTO(0);
// initialize the connection pool,初始化一些值并与MemcachedServer段建立连接
pool.initialize();
// 压缩设置,超过指定大小(单位为K)的数据都会被压缩
mcc.setCompressEnable(true);
mcc.setCompressThreshold(64 * 1024);
}
/**
* 保护型构造方法,不允许实例化!
*
*/
protected MemCachedManager() {
}
/**
* 获取唯一实例.
*
* @return
*/
public static MemCachedManager getInstance() {
return memCachedManager;
}
/**
* 添加一个指定的值到缓存中.
*
* @param key
* @param value
* @return
*/
public boolean add(String key, Object value) {
return mcc.add(key, value);
}
public boolean add(String key, Object value, Date expiry) {
return mcc.add(key, value, expiry);
}
public boolean set(String key,Object value){
return mcc.add(key, value);
}
public boolean replace(String key, Object value) {
return mcc.replace(key, value);
}
public boolean replace(String key, Object value, Date expiry) {
return mcc.replace(key, value, expiry);
}
/**
* 删除缓存中指定的值
* @param key
* @param value
* @param expiry
* @return
*/
public boolean delete(String key, Date expiry){
return mcc.delete(key, expiry);
}
/**
* 根据指定的关键字获取对象.
*
* @param key
* @return
*/
public Object get(String key) {
return mcc.get(key);
}
public Map<String,Object> get(String[] keys){
return mcc.getMulti(keys);
}
public static void main(String[] args) {
try {
MemCachedManager cache = MemCachedManager.getInstance();
List<String> list = new ArrayList<String>();//List集合
//创建文件实例
File file = new File("E:\\my.txt");
if (!file.exists()) {
file.createNewFile();
}else{
file.delete();
}
//文件写入器实例
FileWriter fw = new FileWriter(file);
//测试文件大于1M时,memcached是否仍缓存数据
for (int i = 0; i < 40000; i++) {
String s = "abcderghijklmn"+i;
fw.write(s,0,s.length()); //写入文件
list.add(s); //加入集合
}
fw.flush();
cache.add("hello", 23445,new Date(20000));
cache.add("list", list,new Date(20000));
//cache.delete("hello", new Date(1000));
//cache.add("hello", 234,new Date(15000));
System.out.println("get value : " + cache.get("hello"));
List lst = (List)cache.get("list");
System.out.println("get Size:" + lst.size());
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.保存数据
向memcached保存数据的方法有
1. add
2. replace
3. set
public boolean add(String key, Object value) {
return mcc.add(key, value);
}
public boolean add(String key, Object value, Date expiry) {
return mcc.add(key, value, expiry);
}
public boolean set(String key,Object value){
return mcc.add(key, value);
}
public boolean replace(String key, Object value) {
return mcc.replace(key, value);
}
public boolean replace(String key, Object value, Date expiry) {
return mcc.replace(key, value, expiry);
}
向memcached保存数据时可以指定期限(秒)。不指定期限时,memcached按照LRU算法保存数据。这三个方法的区别如下:
选项 | 说明 |
add | 仅当存储空间中不存在键相同的数据时才保存 |
replace | 仅当存储空间中存在键相同的数据时才保存 |
set | 与add和replace不同,无论何时都保存 |
4.获取数据
获取数据可以使用get和get_multi方法。
public Object get(String key) {
return mcc.get(key);
}
public Map<String,Object> get(String[] keys){
return mcc.getMulti(keys);
}
一次取得多条数据时使用get_multi。get_multi可以非同步地同时取得多个键值,其速度要比循环调用get快数十倍。
5.删除数据
删除数据使用delete方法,不过它有个独特的功能。
public boolean delete(String key, Date expiry){
return mcc.delete(key, expiry);
}
删除第一个参数指定的键的数据。第二个参数指定一个时间值,在指定的时间内可以禁止使用同样的键保存新数据。此功能可以用于防止缓存数据的不完整。但是要注意,set函数忽视该阻塞,照常保存数据
Memcached 的内存分配机制
1.几个关键概念
Page 是memcached内存分配的单位(仅是内存单位而不是内存空间)
Memcached的内存分配以page为单位,默认情况下一个page是1M,可以通过-I参数修改,最小1K,最大128M。如果需要申请内存时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配,在memcached重启前不会被回收或者重新分配
Slab是memcached划分的数据空间
Slab可以理解为一个内存块,一个slab是memcached一次申请内存的最小单位,在memcached中,一个slab的大小默认为1048576字节(1MB),也就是1Page的单位。每一个slab被划分为若干个相等的chunk,每个chunk里保存一个item,memcached默认情况下下一个slab的chunk值为前一个slab的chunk值的1.25倍,这个可以通过修改-f参数来修改增长比例。
Memcached并不是将所有大小的数据都放在一起的,而是预先将一个个数据空间划分为一系列的slabs,每个slab只负责一定大小范围内的数据存储。每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。例如:slab 3只存储大小介于137 到 224 bytes的数据。如果一个数据大小为230byte的数据进行存储,它将被分配到slab 4中。
Chunk是实际用来存储数据的内存空间
Chunk是一系列固定的内存空间,这个大小就是管理它的slab的最大存放大小。例如:slab 1的所有chunk都是104byte,而slab 4的所有chunk都是280byte。因为chunk的大小固定为slab能够存放的数据(iterm)的最大值,所以所有满足分配给当前slab的数据都可以被chunk存下。如果实际的数据大小小于chunk的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设计的。举例来说,如果chunk size是128byte,而存储的数据只有100byte,剩下的28byte将被闲置。此外,memcached允许配置的最小的chunk空间为48个字节(key+value+flags),通过-n参数可以调节这个数值。
但是chunk中不仅仅是保持了缓存对象的value,而且保存了缓存对象的key,expire time, flag等详细信息。chunk缓存的item的完整的结构如下:
key:键
nkey:键长
flags:客户端用来标识数据格式的数值,如json,xml,压缩等 nbytes:值长(包括换行符号\r\n)
suffix:后缀Buffer
nsuffix:后缀长
一个完整的item的长度为:键长+值长+后缀长+item结构大小(32字节)。
Slab Class 按chunk的大小,slab会分成不同的类别
slab按照自己的id分别组成链表,这些链表又按id挂在一个slabclass数组上,整个结构看起来有点像二维数组。
slab有一个初始chunk大小,1.1中是1字节,1.2中是80字节,1.2中有一个factor值,默认为1.25
在1.1中,chunk大小表示为 (初始大小*2^n),n为classid,即:id为0的slab,每chunk大小1字节,id为1的slab,每chunk大小2字节,id为2的slab,每chunk大小4字节……id为20的slab,每chunk大小为1MB,就是说id为20的slab里只有一个chunk
2.理解page、slab、chunk、slab class之间的关系
要理解memcached是如何分配内存的就要从理解上述三个东西之间的关系开始。
page是memcached在收到内存不够的请求,并进行内存分配的单位。举例来说,Slab Class2类型的所有空间(slabs)都用完了,又有大小适合Slab Class2的数据过来了,那么Slab Class2就会向memcached请求新的内存空间,memcached就会划分一个page大小的内存量到Slab Class2。page的默认大小是1M,这个数值可以通过参数-I来修改。
slab是memcached用来划定存储空间的大小概念,通常slab的大小为1page,即1M。每当memcached启动的时候,它会按照-n参数配置的值(如果有的话,否则为默认值48bytes)来决定第一个slab的chunk大小,然后根据-f参数的值来决定后续slab中chunk大小的增长速率,一个一个地决定后续的slab的chunk的大小,直到slab的大小达到设定的page大小(一般是1M)。
chunk是实际用来存储数据的内存空间,它的大小和包含它的slab的大小是一致的。当page大小的内存分配到slab的时候,slab会根据自身的大小将page大小的内存分割成 (page*1024*1024 / slabsize) 个chunk。(slabsize即每个chunk的大小)
Slab Class 是Memcached的一个数组,Memcached把大小不同的slab分为各种类型,分别放在slabs Class数组中,在1.1版本的Memcached中,slabs class的长度是40,1.2及更高版本是200。当同一类别的slab的内存空间(chunks)用完后,Memcached会分配出1page单位的空间大小划分为这一类别,并把划分出来的slab放到slab Class对应的id中。
简单的说,就是Memcached把分为一系列大小的slab,然后按slab的大小分为各种类型放在slabclass数组中。当要缓存item时,通过item的大小定位出它属于哪一类型的slabclass,然后再在这一类型slab中查找空的内存(chunk)进行存储,如果该类型下的所有slab都没有空闲的空间,那么就像memcached申请1page单位大小(默认1M)的空间来划分成这种类型的slab,并把它接在该slab数据的链表中。
3.举例分析
首先,是memcached启动时候的情况:
商人A很有钱,他有100个大小一摸一样的仓库(100M的memcached服务器,每个page大小1M,就是一个仓库)。商人A根据自己的商品尺寸,将自己的仓库分成了42种(42个slab),定义为最小一种的仓库是专门用来存放尺寸为96的货物的(slab1大小为96个字节),然后每种仓库存放的货物大小都是之前一种的1.25倍(增长因子-f为1.25)。商人预先将42个仓库按照预定义的42种货物大小整理、装修了下(memcached启动时候的42个slab预分配、chunk分割)。1号仓库(slab1)中有10922个(1M * 1024 * 1024 / 96)货物存储空间(chunk),后续的仓库类型的装修、空间分配都以此类推。
其次,来看下slab满了的时候的情况:
商人A进了一批尺寸是150的货物,共6899个。货物按大小分配,进入3号仓库( slab3)。因为3号仓库是仓库类型3,其大小只有6898个位置(6898个chunk),6898个货物被安置到仓库类型3(slab3)的3号仓库里去。然后还多出来一个货物没地方放,商人就安排了一个新的仓库装修成仓库类型3(1M的空间分配给slab3,大小为152个字节,含6898个chunk),然后将多余的一个货物放入到新的仓库里。
Memcached删除机制
数据不会真正从memcached中消失
memcached不会释放已分配的内存。记录超时后,客户端就无法再看见该记录(invisible,透明),其存储空间即可重复使用。
1.Lazy Expiration
memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。 这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。如果某一个item在memcached里过期了,这个东西并不会被删除,而是客户端无法再看见该记录(invisible,透明), 其存储空间即可重复使用。一般情况下memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况。
2.LRU:从缓存中有效删除数据的原理
memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为 Least Recently Used(LRU)机制来分配空间。顾名思义,这是删除“最近最少使用”的记录的机制。因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。从缓存的实用角度来看,该模型十分理想。
memcached启动时通过“-M”参数可以禁止LRU
启动时必须注意的是,小写的“-m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。
指定“-M”参数启动后,内存用尽时memcached会返回错误。话说回来,memcached毕竟不是存储器,而是缓存,所以推荐使用LRU。
Memcached安全
我们上面的Memcache服务器端都是直接通过客户端连接后直接操作,没有任何的验证过程,这样如果服务器是直接暴露在互联网上的话是比较危险,轻则数据泄露被其他无关人员查看,重则服务器被入侵,因为Mecache是以root权限运行的,况且里面可能存在一些我们未知的bug或者是缓冲区溢出的情况,这些都是我们未知的,所以危险性是可以预见的。为了安全起见,我做两点建议,能够稍微的防止黑客的入侵或者数据的泄露。
1.内网访问
最好把两台服务器之间的访问是内网形态的,一般是Web服务器跟Memcache服务器之间。普遍的服务器都是有两块网卡,一块指向互联网,一块指向内网,那么就让Web服务器通过内网的网卡来访问Memcache服务器,我们Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。
# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid
Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接
2.设置防火墙
防火墙是简单有效的方式,如果却是两台服务器都是挂在网的,并且需要通过外网IP来访问Memcache的话,那么可以考虑使用防火墙或者代理程序来过滤非法访问。
一般我们在Linux下可以使用iptables或者FreeBSD下的ipfw来指定一些规则防止一些非法的访问,比如我们可以设置只允许我们的Web服务器来访问我们Memcache服务器,同时阻止其他的访问。
# iptables -F
# iptables -P INPUT DROP
# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT
# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT
上面的iptables规则就是只允许192.168.0.2这台Web服务器对Memcache服务器的访问,能够有效的阻止一些非法访问,相应的也可以增加一些其他的规则来加强安全性,这个可以根据自己的需要来做。
Memcached调优
参考:
Memcached的分布式算法
参考原文:http://tech.idv2.com/2008/07/24/memcached-004/
1.memcached的分布式是什么意思?
下面假设memcached服务器有node1~node3三台,应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。
图1 分布式简介:首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。服务器选定后,即命令它保存“tokyo”及其值。
图2 分布式简介:添加时
同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。
接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。函数库通过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。只要数据没有因为某些原因被删除,就能获得保存的值。
图3 分布式简介:获取时
这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续运行。
接下来介绍第1次 中提到的Perl客户端函数库Cache::Memcached实现的分布式方法。
2.Cache::Memcached的分布式方法
Perl的memcached客户端函数库Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。
Cache::Memcached - search.cpan.org
该函数库实现了分布式功能,是memcached标准的分布式方法。
(1)根据余数计算分散
Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。
下面将Cache::Memcached简化成以下的Perl脚本来进行说明。
Cache::Memcached在求哈希值时使用了CRC。
首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。上面的代码执行后输入以下结果:
根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。
(2)根据余数计算分散的缺点
余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器,从而影响缓存的命中率。用Perl写段代码来验证其代价。
这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。将其保存为mod.pl并执行。
首先,当服务器只有三台时:
结果如上,node1保存a、c、d、e……,node2保存g、i、k……,每台服务器都保存了8个到10个数据。
接下来增加一台memcached服务器。
添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器,其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时,在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上,有可能会发生无法提供正常服务的情况。
mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。这种分布式方法称为 Consistent Hashing。
3.Consistent Hashing分布式方法的简单说明
Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
图4 Consistent Hashing:基本原理
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。
图5 Consistent Hashing:添加服务器
因此,Consistent Hashing最大限度地抑制了键的重新分布。而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每个物理节点(服务器)在continuum上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。
通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是,由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:
(1 - n/(n+m)) * 100