一、memcached 介绍php
1.1 memcached 是什么?node
memcached 是以LiveJournal旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。如今已成为mixi、hatena、Facebook、Vox、LiveJournal 等众多服务中提升Webc++
应用扩展性的重要因素。许多Web 应用都将数据保存到RDBMS 中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS 的负担加剧、数据库响应恶化、网站显示延迟等重大影响。这时就该memcached 大显身手了。memcached 是高性能的分布式内存缓存服务器。通常的使用目的是,经过缓存数据库查询结果,减小数据库访问次数,以提升动态Web 应用的速度、提升可扩展性。算法
内置内存存储方式数据库
研究memcached这个产品,首先从它的内存模型开始:咱们知道c++里分配内存有两种方式,预先分配和动态分配,显然,预先分配内存会使程序比较快,可是它的缺点是不能有效利用内存,而动态分配能够有效利用内存,可是会使程序运行效率降低,memcached的内存分配就是基于以上原理,显然为了得到更快的速度,有时候咱们不得不以空间换时间。浏览器
Memcached的高性能源于两阶段哈希(two-stage hash)结构。Memcached就像一个巨大的、存储了不少<key,value>对的哈希表。经过key,能够存储或查询任意的数据。 客户端
能够把数据存储在多台memcached上。当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点;客户端将请求发送给选中的节点,而后
memcached节点经过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)并返回给客户端。从实现的角度看,memcached是一个非阻塞的、基于事件的服务器程序。缓存
为了提升性能,memcached 中保存的数据都存储在memcached 内置的内存存储空间中。因为数据仅存在于内存中,所以重启memcached、重启操做系统会致使所有数据消失。另外,内容容量达到指定值以后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached 自己是为缓存而设计的服务器,所以并无过多考虑数据的永久性问题服务器
memcached 不互相通讯的分布式网络
memcached 尽管是“分布式”缓存服务器,但服务器端并无分布式功能。各个app
memcached 不会互相通讯以共享信息。那么,怎样进行分布式呢?这彻底取决于客户端的实现。
2.2 memcached 启动
memcached 启动的命令在安装目录的bin 二级目录下,如/home/test/app/memcahced-1.4.2/bin/memcached -p 11222 -m 128–d
经常使用的一些启动选项介绍选项说明
-p 侦听的端口,默认为11211
-m 使用内存大小,默认的64m
-d 做为daemon 在后台启动
-vv 用very vrebose 模式启动,调试信息和错误输出到控制台
-l 侦听的地址,默认为全部能够访问的地址
-M 用于在内存溢出的时候,返回一个错误,禁止自动的移出数
据,替代的是返回一个error
-P Pid 文件存在的路径,仅限加上-d 参数是用
-c 最大同时的链接数,默认为1024
其它的一些选项,能够经过–h 命令来进行查看
2.3命令行访问memcached
下面假设memcached 启动时的-p 参数为11311,命令操做在启动memcached
本机首先telnet 链接到memcached 服务器
telnet 127.0.0.1 11311
telnet 成功以后,大概会显示下面的信息
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
各类状态(stats)
STAT <name> <value>\r\n
如:stats命令,则返回如下信息:
stats
STAT pid 26804
STAT uptime 182783
STAT time 1404973716
STAT version 1.4.13
STAT libevent 2.0.11-stable
STAT pointer_size 64
STAT rusage_user 2.320647
STAT rusage_system 5.411177
STAT curr_connections 34
STAT total_connections 558
STAT connection_structures 37
STAT reserved_fds 20
STAT cmd_get 127292
STAT cmd_set 60056
STAT cmd_flush 145
STAT cmd_touch 0
STAT get_hits 83811
STAT get_misses 43481
STAT delete_misses 15970
STAT delete_hits 11992
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 14300156
STAT bytes_written 11507140
STAT limit_maxbytes 134217728 # 分配给memcache的内存大小(字节)
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 16884
STAT evicted_unfetched 0
STAT bytes 609350 # 当前服务器存储items占用的字节数
STAT curr_items 4668 # 服务器当前存储的items数量
STAT total_items 60056
STAT evictions 0 # 分配给memcache的空间用满后须要删除旧的items数,踢出。
STAT reclaimed 27160 #回收再利用,已过时的数据条目来存储新数据。
END
存储命令(set ,add ,replace)
客户端会发送一行像这样的命令:
<command name> <key> <flags> <exptime> <bytes>\r\n
如:
set key1 0 600 5\r\nvalue\r\n
add key2 0 500 2\r\n
replace key1 0 600 6\r\nvalue1\r\n
详细的命令说明,能够见附录的memcached 中英文协议内容
读取命令(get)
命令以下:get <key>*\r\n
- <key>* 表示一个或多个键值,由空格隔开的字串
如:
get key1
VALUE key1 0 7
value12
删除命令(delete)
命令如:delete <key> <time>\r\n
<key> 是客户端但愿服务器删除的内容的键名
- <time> 是一个单位为秒的时间(或表明直到某一刻的Unix时间),在该时间内服务器会拒绝对于此键名的“add”和“replace”命令。此时内容被放入delete队列,没法再经过“get”获得该内容,也没法是用“add”和“replace”命令(可是“set”命令可用)。直到指定时间,这些内容被最终从服务器的内存中完全清除
<time>参数是可选的,缺省为0(表示内容会马上清除,而且随后的存储命令都可用
如:delete key1
退出命令(quit)
如:quit
四、理解memcached 的内存存储
Memcache内存分配机制
Memcache使用了Slab Allocator的内存分配机制:按照预先规定的大小,将分配的内存分割成特定长度的块,以彻底解决内存碎片问题
Memcache的存储涉及到slab,page,chunk三个概念
1.Chunk为固定大小的内存空间,默认为96Byte。
2.page对应实际的物理空间,1个page为1M。
3.一样大小的chunk又称为slab。
4.1Slab Allocation 机制:整理内存以便重复使用
最近的memcached 默认状况下采用了名为Slab Allocator 的机制分配、管理内存。在该机制出现之前,内存的分配是经过对全部记录简单地进行malloc和free 来进行的。可是,这种方式会致使内存碎片,加剧操做系统内存管理器的负担,最坏的状况下,会致使操做系统比memcached 进程自己还慢。Slab Allocator 就是为解决该问题而诞生的
Slab Allocation 的原理至关简单。将分配的内存分割成各类尺寸的块
(chunk),并把尺寸相同的块分红组(chunk 的集合)
并且,slab allocator 还有重复使用已分配的内存的目的。也就是说,分配到的内存不会释放,而是重复利用。
Slab Allocation 的主要术语
Page
分配给Slab 的内存空间,默认是1MB。分配给Slab 以后根据slab 的大小切分红chunk。
Chunk
用于缓存记录的内存空间。
Slab Class
特定大小的chunk 的组
4.2 在Slab中缓存记录的原理
memcached 根据收到的数据的大小,选择最适合数据大小的slab,memcached 中保存着slab 内空闲chunk 的列表,根据该列表选择chunk,然
后将数据缓存于其中
4.3 Slab Allocator 的缺点
因为分配的是特定长度的内存,所以没法有效利用分配的内存。例如,将100 字节的数据缓存到128 字节的chunk 中,剩余的28字节就浪费了
对于该问题目前尚未完美的解决方案,但在文档中记载了比较有效的解决方案。就是说,若是预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的状况下,只要使用适合数据大小的组的列表,就能够减小浪费。可是很遗憾,如今还不能进行任何调优,只能期待之后的版本了。可是,咱们能够调节slab class 的大小的差异。接下来讲明growth factor 选项。
4.4 使用Growth Factor进行调优
memcached 在启动时指定Growth Factor 因子(经过f 选项),就能够在某种程度上控制slab 之间的差别。默认值为1.25。可是,在该选项出现以前,这个因子曾经固定为2,称为“powers of 2”策略。
下面是启动后的verbose 输出:
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)时的输出(篇幅所限,这里只写到第10 组):
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 472 perslab 2221
slab class 9: chunk size 592 perslab 1771
slab class 10: chunk size 744 perslab 1409
可见,组间差距比因子为2 时小得多,更适合缓存几百字节的记录。从上面的输出结果来看,可能会以为有些计算偏差,这些偏差是为了保持字节数的对齐而故意设置的。将memcached 引入产品,或是直接使用默认值进行部署时,最好是从新计算一下数据的预期平均长度,调整growth factor,以得到最恰当的设置。内存是珍贵的资源,浪费就太惋惜了。
item占用空间计算
*nsuffix = (uint8_t) snprintf(suffix, 40, " %d %d\r\n", flags, nbytes – 2); return sizeof(item) + nkey + *nsuffix + nbytes;
*nsuffix=" %d %d\r\n”的长度
若是ITEM_CAS标志设置时,这里有8字节的数据
完整的item长度是键长+值长+后缀长+item结构大小(48字节) + 8
item.length=56+key.lenght+value.length+后缀长
32位机器 item结构是32字节
64位机器 itme结构是48字节
memcache存储的时候对key的长度有限制,php和C的最大长度都是250
五、memcached 删除机制
memcached 是缓存,不须要永久的保存到服务器上,本章介绍memcache 的删除机制
5.1 memcached 在数据删除方面有效的利用资源
Memcached 不会释放已经分配的内存,记录过时以后,客户端没法再看到这一条记录,其存储空间就能够利用。
Lazy Expiration
memcached 内部不会监视记录是否过时,而是在get 时查看记录的时间戳,检查记录是否过时。这种技术被称为lazy(惰性)expiration。所以,memcached不会在过时监视上耗费CPU 时间
5.2 LRU:从缓存中有效删除数据的原理
1.search->refcount == 0 && 已通过期的 删除
2.tries = 50; // 最多尝试50次 LRU队列tail 查找 search->refcount == 0 第一个 删除
3. tries = 50; // 最多尝试50次 LRU队列tail 查找search->refcount != 0 查询时间(超过3小时)的item 第一个 删除
memcached 会优先使用已超时的记录的空间,但即便如此,也会发生追加新记录时空间不足的状况,此时就要使用名为Least Recently Used(LRU)机制来分配空间。顾名思义,这是删除“最近最少使用”的记录的机制。所以,当memcached 的内存空间不足时(没法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。从缓存的实用角度来看,该模型十分理想。不过,有些状况下LRU 机制反倒会形成麻烦。memcached 启动时经过“M”参数能够禁止LRU,以下所示:
$ memcached -M –m 1024
启动时必须注意的是,小写的“m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。
指定“M”参数启动后,内存用尽时memcached 会返回错误。话说回来,memcached 毕竟不是存储器,而是缓存,因此推荐使用LRU
六、memcached 的分布式算法
6.1memcached 的分布式
memcached 虽然称为“分布式”缓存服务器,但服务器端并无“分布式”功能。memcached 的分布式,则是彻底由客户端程序库实现的。这种分布式是memcached 的最大特色
memcached 的分布式是什么意思?
下面假设memcached 服务器有node1~node3 三台,应用程序要保存键名为“tokyo”、“kanagawa”、“chiba”、“saitama”、“gunma”的数据
首先向memcached 中添加“tokyo”。将“tokyo”传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的memcached 服务器。服务器选定后,即命令它保存“tokyo”及其值
一样,“kanagawa”、“chiba”、“saitama”、“gunma”都是先选择服务器再保接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。函数库经过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,而后发送get 命令。只要数据没有由于某些缘由被删除,就能得到保存的值。
这样,将不一样的键保存到不一样的服务器上,就实现了memcached 的分布式。memcached 服务器增多后,键就会分散,即便一台memcached 服务器发生故障没法链接,也不会影响其余的缓存,系统依然能继续运行
6.2 余数分布式算法
就是“根据服务器台数的余数进行分散”。求得键的整数哈希值,再除以服务器台数,根据其他数来选择服务器
余数算法的缺点
余数计算的方法简单,数据的分散性也至关优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价至关巨大。添加服务器后,余数就会产生巨变,这样就没法获取与保存时相同的服务器,从而影响缓存的命中。
6.3Consistent Hashing(一致哈希)
知识补充:哈希算法,即散列函数。将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据惟一且极其紧凑的数值表示形式。若是散列一段明文并且哪怕只更改该段落的一个字母,随后的哈希都将产生不一样的值。要找到散列为同一个值的两个不一样的输入,在计算上是不可能的,因此数据的哈希值能够检验数据的完整性。通常用于快速查找和加密算法。(常见的有MD5,SHA-1)
Consistent Hashing 的简单说明
Consistent Hashing 以下所示:首先求出memcached 服务器(节点)的哈希值,并将其配置到0~232 的圆(continuum)上。而后用一样的方法求出存储数据的键的哈希值,并映射到圆上。而后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。若是超过232 仍然找不到服务器,就会保存到第一台memcached 服务器上。
从上图的状态中添加一台memcached 服务器。余数分布式算法因为保存键的服务器会发生巨大变化,而影响缓存的命中率,但Consistent Hashing中,只有在continuum 上增长服务器的地点逆时针方向的第一台服务器上的键会受到影响
Consistent Hashing:添加服务器
所以,Consistent Hashing 最大限度地抑制了键的从新分布。并且,有的Consistent Hashing 的实现方法还采用了虚拟节点的思想。使用通常的hash函数的话,服务器的映射地点的分布很是不均匀。所以,使用虚拟节点的思想,为每一个物理节点(服务器)在continuum上分配100~200 个点。这样就能抑制分布不均匀,最大限度地减少服务器增减时的缓存从新分布。
经过上文中介绍的使用Consistent Hashing 算法的memcached 客户端函数库进行测试的结果是,由服务器台数(n)和增长的服务器台数(m)计算增长服务器后的命中率计算公式以下:
(1 n/(n+m)) * 100
存储命令
<command name> <key> <flags> <exptime> <bytes>\r\n
- <command name> 是set, add,或者repalce
- <key> 是接下来的客户端所要求储存的数据的键值
- <flags> 是在取回内容时,与数据和发送块一同保存服务器上的任意16位无符号整形(用十进制来书写)。客户端能够用它做为“位域”来存储一些特定的信息;它对服务器是不透明的。
- <exptime> 是终止时间。若是为0,该项永不过时(虽然它可能被删除,以便为其余缓存项目腾出位置)。若是非0(Unix 时间戳或当前时刻的秒偏移),到达终止时间后,客户端没法再得到这项内容。
- <bytes> 是随后的数据区块的字节长度,不包括用于分野的“\r\n”。它能够是0(这时后面跟随一个空的数据区块)。
- <data block> 是大段的8位数据,其长度由前面的命令行中的<bytes>指定。
• set 意思是“储存此数据”
• add 意思是“储存此数据,只在服务器*未*保留此键值的数据时”
• replace 意思是“储存此数据,只在服务器*曾*保留此键值的数据时”
发送命令行和数据区块之后,客户端等待回复,可能的回复以下:
- "STORED\r\n"代表成功.
- "NOT_STORED\r\n"代表数据没有被存储,但不是由于发生错误。这一般意味着add或replace 命令的条件不成立,或者,项目已经位列删除队列(参考后文的“delete”命令)。
取回命令
get <key>*\r\n
- <key>* 表示一个或多个键值,由空格隔开的字串这行命令之后,客户端的等待0个或多个项目,每项都会收到一行文本,而后跟着数据区块。全部项目传送完毕后,服务器发送如下字串:"END\r\n"来指示回应完毕,服务器用如下形式发送每项内容:
VALUE <key> <flags> <bytes>\r\n
<data block>\r\n
- <key> 是所发送的键名
- <flags> 是存储命令所设置的记号
- <bytes> 是随后数据块的长度,*不包括* 它的界定符“\r\n”
- <data block> 是发送的数据
若是在取回请求中发送了一些键名,而服务器没有送回项目列表,这意味着服务器没这些键名(可能由于它们从未被存储,或者为给其余内容腾出空间而被删除,或者到期,或者被已客户端删除)。
删除
delete <key> <time>\r\n
- <key> 是客户端但愿服务器删除的内容的键名
- <time> 是一个单位为秒的时间(或表明直到某一刻的Unix时间),在该时间内服务器会拒绝对于此键名的“add”和“replace”命令。此时内容被放入delete队列,没法再经过“get”获得该内容,也没法是用“add”和“replace”命令(可是“set”命令可用)。直到指定时间,这些内容被最终从服务器的内存中完全清除。<time>参数是可选的,缺省为0(表示内容会马上清除,而且随后的存储命令都可用)。
此命令有一行回应:- "DELETED\r\n"表示执行成功
- "NOT_FOUND\r\n"表示没有找到这项内容
增长/减小
命令“incr”和“decr”被用来修改数据,当一些内容须要替换、增长或减小时。这些数据必须是十进制的32位无符号整新。若是不是,则看成0 来处理。修改的内容必须存在,当使用“incr”/“decr”命令修改不存在的内容时,不会被看成0处理,而是操做失败。
客户端发送命令行:
incr <key> <value>\r\n或decr <key> <value>\r\n
- <key> 是客户端但愿修改的内容的建名
- <value> 是客户端要增长/减小的总数。
回复为如下集中情形:
- "NOT_FOUND\r\n"指示该项内容的值,不存在。
- <value>\r\n ,<value>是增长/减小。
注意"decr"命令发生下溢:若是客户端尝试减小的结果小于0 时,结果会是0。"incr" 命令不会发生溢出。
状态
命令"stats" 被用于查询服务器的运行状态和其余内部数据。有两种格式。不带参数的:
stats\r\n
这会在随后输出各项状态、设定值和文档。另外一种格式带有一些参数:
stats <args>\r\n
经过<args>,服务器传回各类内部数据。由于随时可能发生变更,本文不提供参数的种类及其传回数据。
各类状态
受到无参数的"stats"命令后,服务器发送多行内容,以下:
STAT <name> <value>\r\n
服务器用如下一行来终止这个清单:END\r\n,在每行状态中,<name> 是状态的名字,<value>使状态的数据。如下清单,是全部的状态名称,数据类型,和数据表明的含义。
在“类型”一列中,"32u"表示32 位无符号整型,"64u"表示64 位无符号整型,"32u:32u"表示用冒号隔开的两个32 位无符号整型。
名称 |
类型 |
含义 |
pid |
32u |
服务器进程ID |
uptime |
32u |
服务器运行时间,单位秒 |
time |
32u |
服务器当前的UNIX时间 |
version |
string |
服务器的版本号 |
rusage_user |
32u |
该进程累计的用户时间(秒:微妙) |
rusage_system |
32u |
该进程累计的系统时间(秒:微妙) |
curr_items |
32u |
服务器当前存储的内容数量 |
total_items |
32u |
服务器启动以来存储过的内容总数 |
bytes |
64u |
服务器当前存储内容所占用的字节数 |
curr_connections |
32u |
链接数 |
total_connections |
32u |
服务器运行以来接受的链接总数 |
connection_structures |
32u |
服务器分配的链接结构的数量 |
cmd_get |
32u |
取回请求总数 |
cmd_set |
32u |
存储请求总数 |
get_hits |
32u |
请求成功的总次数 |
get_misses |
32u |
请求失败的总次数 |
bytes_read |
64u |
服务器从网络读取到的总字节数 |
bytes_written |
64u |
服务器向网络发送的总字节数 |
limit_maxbytes |
32u |
服务器在存储时被容许使用的字节总数 |
若是不想每次经过输入stats来查看memcache状态,能够经过echo "stats" |nc ip port 来查看,例如:echo "stats" | nc 127.0.0.1 9023。
7.Memcache 命中率
缓存命中率 = get_hits/cmd_get * 100% (总命中次数/总请求次数)
要提升memcached的命中率,预估咱们的value大小而且适当的调整内存页大小和增加因子是必须的。
命中率的提高能够经过多种方案实现.
其一,提升服务获取的内存总量
其二,提升空间利用率,这实际上也是另外一种方式的增长内存总量
其三,应用一级别上再来一次LRU
其四,对于总体命中率,能够采起有效的冗余策略,减小分布式服务时某个server发生服务抖动的状况
8.一些注意1. memcache已经分配的内存不会再主动清理。2. memcache分配给某个slab的内存页不能再分配给其余slab。3. flush_all不能重置memcache分配内存页的格局,只是给全部的item置为过时。4. memcache最大存储的item(key+value)大小限制为1M,这由page大小1M限制5.因为memcache的分布式是客户端程序经过hash算法获得的key取模来实现,不一样的语言可能会采用不一样的hash算法,一样的客户端程序也有可能使用相异的方法,所以在多语言、多模块共用同一组memcached服务时,必定要注意在客户端选择相同的hash算法6.启动memcached时能够经过-M参数禁止LRU替换,在内存用尽时add和set会返回失败7.memcached启动时指定的是数据存储量,没有包括自己占用的内存、以及为了保存数据而设置的管理空间。所以它占用的内存量会多于启动时指定的内存分配量,这点须要注意。8.memcache存储的时候对key的长度有限制,php和C的最大长度都是250