Memcahce(MC)系列(一)Memcache介绍、使用、存储、算法、优化
写在前面:前不久在工作中被问到关于MC一致哈希的问题,由于时隔太久差点儿忘记,特前来恶补一下MC,下面是前几年在工作中学习MC时的一些资料,来历不明,特整理一下,希望对大家的学习也能有帮助。
关于memcache的安装,有兴趣的朋友请參考这篇文章:http://blog.csdn.net/xifeijian/article/details/22000173
1、memcached 介绍
1.1 memcached 是什么?
memcached 是以LiveJournal旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。如今已成为mixi、hatena、Facebook、Vox、LiveJournal 等众多服务中提高Web
应用扩展性的重要因素。很多Web 应用都将数据保存到RDBMS 中,应用server从中读取数据并在浏览器中显示。但随着数据量的增大、訪问的集中,就会出现RDBMS 的负担加重、数据库响应恶化、站点显示延迟等重大影响。这时就该memcached 大显身手了。memcached 是高性能的分布式内存缓存server。一般的使用目的是,通过缓存数据库查询结果,降低数据库訪问次数,以提高动态Web 应用的速度、提高可扩展性。
内置内存存储方式
为了提高性能,memcached 中保存的数据都存储在memcached 内置的内存存储空间中。由于数据仅存在于内存中,因此重新启动memcached、重新启动操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自己主动删除不使用的缓存。memcached 本身是为缓存而设计的server,因此并没有过多考虑数据的永久性问题
memcached 不互相通信的分布式
memcached 尽管是“分布式”缓存server,但server端并没有分布式功能。各个
memcached 不会互相通信以共享信息。那么,如何进行分布式呢?这全然取决于client的实现。
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 server
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 # 当前server存储items占用的字节数
STAT curr_items 4668 # server当前存储的items数量
STAT total_items 60056
STAT evictions 0 # 分配给memcache的空间用满后须要删除旧的items数,踢出。
STAT reclaimed 27160 #回收再利用,已过期的数据条目来存储新数据。
END
存储命令(set ,add ,replace)
client会发送一行像这样的命令:
<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> 是client希望server删除的内容的键名
- <time> 是一个单位为秒的时间(或代表直到某一刻的Unix时间),在该时间内server会拒绝对于此键名的“add”和“replace”命令。此时内容被放入delete队列,无法再通过“get”得到该内容,也无法是用“add”和“replace”命令(可是“set”命令可用)。直到指定时间,这些内容被终于从server的内存中彻底清除
<time>參数是可选的,缺省为0(表示内容会立马清除,并且随后的存储命令均可用
如:delete key1
退出命令(quit)
如:quit
4、理解memcached 的内存存储
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字节就浪费了
对于该问题眼下还没有完美的解决方式,但在文档中记载了比較有效的解决方式。就是说,假设预先知道client发送的数据的公用大小,或者仅缓存大小同样的数据的情况下,仅仅要使用适合数据大小的组的列表,就能够降低浪费。可是非常遗憾,如今还不能进行不论什么调优,仅仅能期待以后的版本号了。可是,我们能够调节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,以获得最恰当的设置。内存是珍贵的资源,浪费就太可惜了。
5、memcached 删除机制
memcached 是缓存,不须要永久的保存到server上,本章介绍memcache 的删除机制
5.1 memcached 在数据删除方面有效的利用资源
Memcached 不会释放已经分配的内存,记录过期之后,client无法再看到这一条记录,其存储空间就能够利用。
Lazy Expiration
memcached 内部不会监视记录是否过期,而是在get 时查看记录的时间戳,检查记录是否过期。这样的技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU 时间
5.2 LRU:从缓存中有效删除数据的原理
memcached 会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为Least Recently Used(LRU)机制来分配空间。顾名思义,这是删除“近期最少使用”的记录的机制。因此,当memcached 的内存空间不足时(无法从slab class 获取到新的空间时),就从近期未被使用的记录中搜索,并将其空间分配给新的记录。从缓存的有用角度来看,该模型十分理想。只是,有些情况下LRU 机制反倒会造成麻烦。memcached 启动时通过“M”參数能够禁止LRU,例如以下所看到的:
$ memcached -M –m 1024
启动时必须注意的是,小写的“m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。
指定“M”參数启动后,内存用尽时memcached 会返回错误。话说回来,memcached 毕竟不是存储器,而是缓存,所以推荐使用LRU
6、memcached 的分布式算法
6.1memcached 的分布式
memcached 尽管称为“分布式”缓存server,但server端并没有“分布式”功能。memcached 的分布式,则是全然由client程序库实现的。这样的分布式是memcached 的最大特点
memcached 的分布式是什么意思?
下面假设memcached server有node1~node3 三台,应用程序要保存键名为“tokyo”、“kanagawa”、“chiba”、“saitama”、“gunma”的数据
首先向memcached 中加入“tokyo”。将“tokyo”传给client程序库后,client实现的算法就会依据“键”来决定保存数据的memcached server。server选定后,即命令它保存“tokyo”及其值
同样,“kanagawa”、“chiba”、“saitama”、“gunma”都是先选择server再保接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。函数库通过与数据保存时同样的算法,依据“键”选择server。使用的算法同样,就能选中与保存时同样的server,然后发送get 命令。仅仅要数据没有由于某些原因被删除,就能获得保存的值。
这样,将不同的键保存到不同的server上,就实现了memcached 的分布式。memcached server增多后,键就会分散,即使一台memcached server发生问题无法连接,也不会影响其它的缓存,系统依旧能继续执行
6.2 余数分布式算法
就是“依据server台数的余数进行分散”。求得键的整数哈希值,再除以server台数,依据其余数来选择server
余数算法的缺点
余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当加入或移除server时,缓存重组的代价相当巨大。加入server后,余数就会产生巨变,这样就无法获取与保存时同样的server,从而影响缓存的命中。
6.3Consistent Hashing(一致哈希)
知识补充:哈希算法,即散列函数。将随意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。假设散列一段明文并且哪怕仅仅更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值能够检验数据的完整性。一般用于高速查找和加密算法。(常见的有MD5,SHA-1)
Consistent Hashing 的简单说明
Consistent Hashing 例如以下所看到的:首先求出memcached server(节点)的哈希值(一般的方法能够使用 cache 机器的 IP 地址或者机器名作为 hash 输入。),并将其配置到0~232 的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置開始顺时针查找,将数据保存到找到的第一个server上。假设超过232仍然找不到server,就会保存到第一台memcached server上。
从上图的状态中加入一台memcached server。余数分布式算法由于保存键的server会发生巨大变化,而影响缓存的命中率,但Consistent Hashing中,仅仅有在continuum 上添加server的地点逆时针方向的第一台server上的键会受到影响
Consistent hashing 的基本思想就是将对象和 cache 都映射到同一个 hash 数值空间中,并且使用同样的 hash 算法。
如今 cache 和对象都已经通过同一个 hash 算法映射到 hash 数值空间中了,接下来要考虑的就是如何将对象映射到 cache 上面了。
在这个环形空间中,假设沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上,由于对象和 cache 的 hash 值是固定的,因此这个 cache 必定是唯一和确定的。这样不就找到了对象和 cache 的映射方法了吗?
Consistent Hashing:加入server
因此,Consistent Hashing 最大限度地抑制了键的又一次分布。并且,有的Consistent Hashing 的实现方法还採用了虚拟节点的思想。使用一般的hash函数的话,server的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每一个物理节点(server)在continuum上分配100~200 个点。这样就能抑制分布不均匀,最大限度地减小server增减时的缓存又一次分布。
通过上文中介绍的使用Consistent Hashing 算法的memcached client函数库进行測试的结果是,由server台数(n)和添加的server台数(m)计算添加server后的命中率计算公式例如以下:
(1 n/(n+m)) * 100
存储命令
<command name> <key> <flags> <exptime> <bytes>\r\n
- <command name> 是set, add,或者repalce
- <key> 是接下来的client所要求储存的数据的键值
- <flags> 是在取回内容时,与数据和发送块一同保存server上的随意16位无符号整形(用十进制来书写)。client能够用它作为“位域”来存储一些特定的信息;它对server是不透明的。
- <exptime> 是终止时间。假设为0,该项永只是期(尽管它可能被删除,以便为其它缓存项目腾出位置)。假设非0(Unix 时间戳或当前时刻的秒偏移),到达终止时间后,client无法再获得这项内容。
- <bytes> 是随后的数据区块的字节长度,不包含用于分野的“\r\n”。它能够是0(这时后面尾随一个空的数据区块)。
- <data block> 是大段的8位数据,其长度由前面的命令行中的<bytes>指定。
• set 意思是“储存此数据”
• add 意思是“储存此数据,仅仅在server*未*保留此键值的数据时”
• replace 意思是“储存此数据,仅仅在server*曾*保留此键值的数据时”
发送命令行和数据区块以后,client等待回复,可能的回复例如以下:
- "STORED\r\n"表明成功.
- "NOT_STORED\r\n"表明数据没有被存储,但不是由于错误发生。这通常意味着add或replace 命令的条件不成立,或者,项目已经位列删除队列(參考后文的“delete”命令)。
取回命令
get <key>*\r\n
- <key>* 表示一个或多个键值,由空格隔开的字串这行命令以后,client的等待0个或多个项目,每项都会收到一行文本,然后跟着数据区块。全部项目传送完成后,server发送下面字串:"END\r\n"来指示回应完成,server用下面形式发送每项内容:
VALUE <key> <flags> <bytes>\r\n
<data block>\r\n
- <key> 是所发送的键名
- <flags> 是存储命令所设置的记号
- <bytes> 是随后数据块的长度,*不包含* 它的界定符“\r\n”
- <data block> 是发送的数据
假设在取回请求中发送了一些键名,而server没有送回项目列表,这意味着server没这些键名(可能由于它们从未被存储,或者为给其它内容腾出空间而被删除,或者到期,或者被已client删除)。
删除
delete <key> <time>\r\n
- <key> 是client希望server删除的内容的键名
- <time> 是一个单位为秒的时间(或代表直到某一刻的Unix时间),在该时间内server会拒绝对于此键名的“add”和“replace”命令。此时内容被放入delete队列,无法再通过“get”得到该内容,也无法是用“add”和“replace”命令(可是“set”命令可用)。直到指定时间,这些内容被终于从server的内存中彻底清除。<time>參数是可选的,缺省为0(表示内容会立马清除,并且随后的存储命令均可用)。
此命令有一行回应:- "DELETED\r\n"表示执行成功
- "NOT_FOUND\r\n"表示没有找到这项内容
添加/降低
命令“incr”和“decr”被用来改动数据,当一些内容须要替换、添加或降低时。这些数据必须是十进制的32位无符号整新。假设不是,则当作0 来处理。改动的内容必须存在,当使用“incr”/“decr”命令改动不存在的内容时,不会被当作0处理,而是操作失败。
client发送命令行:
incr <key> <value>\r\n或decr <key> <value>\r\n
- <key> 是client希望改动的内容的建名
- <value> 是client要添加/降低的总数。
回复为下面集中情形:
- "NOT_FOUND\r\n"指示该项内容的值,不存在。
- <value>\r\n ,<value>是添加/降低。
注意"decr"命令发生下溢:假设client尝试降低的结果小于0 时,结果会是0。"incr" 命令不会发生溢出。
状态
命令"stats" 被用于查询server的执行状态和其它内部数据。有两种格式。不带參数的:
stats\r\n
这会在随后输出各项状态、设定值和文档。还有一种格式带有一些參数:
stats <args>\r\n
通过<args>,server传回各种内部数据。由于随时可能发生变动,本文不提供參数的种类及其传回数据。
各种状态
受到无參数的"stats"命令后,server发送多行内容,例如以下:
STAT <name> <value>\r\n
server用下面一行来终止这个清单:END\r\n,在每行状态中,<name> 是状态的名字,<value>使状态的数据。下面清单,是全部的状态名称,数据类型,和数据代表的含义。
在“类型”一列中,"32u"表示32 位无符号整型,"64u"表示64 位无符号整型,"32u:32u"表示用冒号隔开的两个32 位无符号整型。
名称 | 类型 | 含义 |
pid | 32u | server进程ID |
uptime | 32u | server执行时间,单位秒 |
time | 32u | server当前的UNIX时间 |
version | string | server的版本号号 |
rusage_user | 32u | 该进程累计的用户时间(秒:微妙) |
rusage_system | 32u | 该进程累计的系统时间(秒:微妙) |
curr_items | 32u | server当前存储的内容数量 |
total_items | 32u | server启动以来存储过的内容总数 |
bytes | 64u | server当前存储内容所占用的字节数 |
curr_connections | 32u | 连接数 |
total_connections | 32u | server执行以来接受的连接总数 |
connection_structures | 32u | server分配的连接结构的数量 |
cmd_get | 32u | 取回请求总数 |
cmd_set | 32u | 存储请求总数 |
get_hits | 32u | 请求成功的总次数 |
get_misses | 32u | 请求失败的总次数 |
bytes_read | 64u | server从网络读取到的总字节数 |
bytes_written | 64u | server向网络发送的总字节数 |
limit_maxbytes | 32u | server在存储时被同意使用的字节总数 |
假设不想每次通过输入stats来查看memcache状态,能够通过echo "stats" |nc ip port 来查看,比如:echo "stats" | nc 127.0.0.1 9023。