分布式缓存的核心技术首先是内存本身的管理问题,包括了内存的分配,管理和回收机制。其次是分布式管理和分布式算法,其次是缓存键值管理和路由。
1.什么是Memcahed
Memcached是国外社区网站LiveJournal的开发团队开发的高性能分布式内存缓存服务器。许多Web应用程序都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大,访问的集中,就会出现RDBMS的负担加重,数据库响应恶化,网站显示延迟等重大影响。一般的使用目的是通过缓存数据库查询结果,减少数据库的访问次数,以提高动态Web应用的速度、提高扩展性。
MemcacheDB是新浪团队开发的,基于Memcached协议,所以可以使用Memcached客户端直接使用,存储引擎使用Berkeley DB,支持主从复制模式。Memcached是非持久存储,所以它的定位就是缓存,而Memcachedb不是用来做缓存的,它是和mysql同一个层次的东西。不一样的是,memcachedb性能要比mysql高,而mysql检索功能比memcachedb强。如果从缓存的角度看,memcached缓解Mysql读压力,而Memcachedb缓解mysql写压力。
Memcachedb适合应用的场景:
(1)浏览/点击/统计等,也可以局部代替mysql的count函数
(2)标志
(3)频繁写的地方,访客列表、评论之类的
2.Memcached特点
Memcached作为高速运行的分布式缓存服务器具有以下特点。
(1)协议简单:
memcached的服务器客户端通信并不使用复杂的xml等格式,而是使用简单的基于文本的协议,C/S架构。
(2)基于libevent的事件处理:
libevent是一套跨平台的事件处理接口的封装。memvached使用这个libevent库,能在Linux、BSD、Solaris等操作系统上发挥其高性能。
包装的接口包括:poll、select(Windows)、epoll(Linux),kqueue(BSD)、/dev/poo (Solaris)
Memcached使用libevent来进行网络并发连接的处理,能够保持在很大并发情况下,仍能够保持快速的响应能力。
(3)内置内存存储方式:
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached,重启操作系统会导致全部数据消失。另外,内容容量达到指定值以后memcached会自动删除不适用的缓存。
数据存储方式:Slab Allocation
默认使用Slab Allocation机制分配,管理内存。在改进机制以前,内存的分配是通过对所有记录简单的进行malloc和free来进行的。但是这种方式会导致内存碎片,加重操作系统内存管理器的负担。
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,已完全解决内存碎片问题。Slab Allocation将分配的内存分割成各种尺寸的块(chunck),并把尺寸相同的块分成组(chunck的集合)。
图1 Slab Allocation的构造图
而且Slab Allocator还有重复使用已分配内存的目的。也就是说,分配到的内存不会释放,而是重复利用。
图2 Slab Classes分配图
Page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。
Chunk:用于缓存记录的内存空间。
Slab Class:特定大小的chunk组。
在slab中缓存记录的原理:
memcached根据收到的数据的大小,选择最适合数据大小的slab。memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存于其中。
Slab Allocation缺点:
这个问题是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。
数据过期方式:Lazy Expiration + LRU
Memcached删除数据时数据不会真正从memcached中消失。Memcached不会释放已分配的内存。记录超时后,客户端就无法再看到该记录(visible透明),其存储空间即可重复利用。
Lazy Expiration:
memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。这种技术被称为lazy expiration。因此,memcached不会再过期监视上耗费cpu时间。
LRU:
memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新纪录时空间不足的情况,此时就要使用Least Recently Used(LRU)机制来分配空间。删除”最近最少使用“的记录机制。因此,当memcached的内存空间不足时,(无法从slab class 获取新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。从缓存的实用角度来看,该模型十分理想。
(4)Memcached不互相通信的分布式:
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会相互通信以共享信息。分布式主要通过客户端实现。
基于客户端的Memcached分布式:
例子:
假设memcached服务器有node1-node3三台,应用程序要保存键名为”tokyo“,”kanagawa“”chiba“”saitama“”gunma“的数据。
//按照Key值,获取一个服务器ID
int getServerId(char *key,int serverTotal){
int c ,hash = 0;
while(c = *key++){
hash+= c;}
return hash%serverTatal;
}
//服务器列表
node[0] =>192.168.0.1:11211
node[1] =>192.168.0.2:11211
node[2] =>192.168.0.3:11211
//获取key是tokyo的节点ID(服务器ID)
int id = getServerId("test",3);
//得出的结果是1,那么对应的机器就是
node[id] = node[1]
首先向memcached中添加”tokyo“。将”tokyo“传给客户端程序库后,客户端实现的算法就会根据”键“来决定保存数据的memcached服务器。服务器选定后,即命令它保存”tokyo“极其值。
同样,”kanagawa“"chaiba""saitama""gunma"都是先选择服务器再保存。
接下来获取保存的数据。获取时也要将要获取的键”tokyo“传递给函数库。函数库通过与数据保存时相同的算法,根据"键"选择服务器。使用的算法相同,就能选中了与保存时相同的服务器,然后发送get命令。只要数据没有 因为某些原因被删除,就能获得保存的值。
这样,将不同的键保存到不同的服务器上,就实现了memcached分布式。memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续运行。
3.一些经验和技巧
(1)在Memcached中可以保存的item数据量是没有限制的,只要内存足够。
(2)Memcached单进程最大使用内存为2G,要使用更多内存,可以分多个端口开启多个Memcached进程。
(3)最大30天的数据过期时间,设置为永久的也会在这个时间过期,常量REALTIME_MAXDELTA 60*60*24*30控制
(4)单个item最大数据是1M,超过1MB数据不予存储,常量POWER_BLOCK 1048576进行控制,它是默认的slab大小。
(5)最大同时连接数是200,通过conn_init()中的freetotal进行控制,最大软连接数是1024,通过settings.maxconns=1024进行控制
(6)跟空间占用相关的参数:settings.factor = 1.25,settings.chunk_size = 48,影响slab的数据占用和步进方式
查看Memcached内部工作状态:
访问Memcached:telnet 主机名 端口号
查看总状态:stats
查看某项状态:stats curr_connections
禁止LRU
有些情况下LRU机制反倒会造成麻烦。memcached启动时通过”-M“参数可以禁止LRU,如下所示:
$memcached -M -m 1024
启动时必须注意的是,小写的”-m“选项是用来指定最大内存的大小的。不指定具体数值则使用默认值64MB。
指定"-M"参数启动后,内存用尽时memcached会返回错误。不过memcached毕竟不是存储器,而是缓存,所以推荐使用LRU.
Memcached使用线程模式工作
在安装的时候必须打开:./configure --enable -threads
安装完之后,启动的时候看看帮助信息有没有这条:
-t <num> number of threads to use,defalt 4
如果存在该选项,说明已经支持了线程,就可以在启动的时候使用-t选项来启动多线程
然后启动的时候必须加上需要支持的线程的数量:
/usr/local/memcache/bin/mamcached -t 1024
调优Slab和内存分配
Memcached在启动时指定Growth Factor因子(通过-f 选项),就可以再某种程度上控制slab之间的差异。默认值为1.25.但是,在该选项出现之前,这个因子曾经固定为2,称为”powers of 2“策略。
我们用以前的设置,以verbose模式启动memcached:
$memcached -f 2 -vv
slab class 1:chunk size 128 perslab 8192
slab class 2:chunk size 256 perslab 4096
slab class 3:chunk size 512 perslab 2048
slab class 4:chunk size 1024 perslab 1024
slab class 5:chunk size 2048 perslab 512
slab class 6:chunk size 4096 perslab 256
slab class 7:chunk size 8192 perslab 128
slab class 8:chunk size 16384 perslab 64
slab class 9:chunk size 32768 perslab 32
slab class 10:chunk size 65536 perslab 16
slab class 11:chunk size 131072 perslab 8
slab class 12:chunk size 262144 perslab 4
slab class 13:chunk size 524288 perslab 2
可见,从128字节的组开始,组的大小一次增大为原来的2倍。这样设置的问题是,slab之间的差别比较大,有些情况下就相当浪费内存。因此,为尽量减少内存浪费,追加了growth factor这个选项,默认设置(f=1.25)时的输出:
slab class 1:chunk size 88 perslab 11915
slab class 2:chunk size 112 perslab 9362
slab class 3:chunk size 144 perslab 7281
slab class 4:chunk size 184 perslab 5698
slab class 5:chunk size 232 perslab 4519
slab class 6:chunk size 296 perslab 3542
slab class 7:chunk size 376 perslab 2788
slab class 8:chunk size 172 perslab 2221
slab class 9:chunk size 592 perslab 1771
slab class 10:chunk size 744 perslab 1409
可见,组间差距比因子为2时小得多,更适合缓存几百字节的记录。从上面的输出结果来看,可能会觉得有些计算误差,这些误差是为了保持字节数的对齐而故意设置的。
将memcached引入产品,或是直接使用默认值进行部署时,最好是重新计算一下数据的预期平均长度,调整growth factor,以获得最恰当的设置。
参考:
http://wenku.baidu.com/view/8686d46c7e21af45b307a8c3.html
http://blog.sina.com.cn/s/blog_493a845501013ei0.html
http://www.t086.com/article/4698