Memcache 学习总结
WHAT is Memcache?
Free & open source, high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.
memcached是一个免费开源、高性能、分布式的内存对象缓存系统,本质上是通用的,但旨在通过加速动态Web应用程序来减轻数据库负载。
Memcache是一款开发工具,其设计思想主要反映以下几个方面:
- 简单的key/value存储:服务器不关心数据本身意义及结构,主要是可序列化数据即可。
- 功能实现一半依赖与客户端,一半基于服务器端。
- 各服务器间彼此无视,不在服务器间进行数据同步。
- O(1)的执行效率。
- 内存空间的再利用,Lazy Expiration + LRU 机制。
Memcache 与 memcached的区别
1.客户端两者区别:
- 两个不同版本PHP的memcached的客户端
- memcache是原生版本,完全是在PHP框架内开发的,支持OO和非OO两套接口并存;而memcached是建立在libmemcached的基础上,只支持OO接口。
- 其他一些实现和支持方面的不同等。
2.服务器端两者区别:
- Memcache 是项目名称
- Memcached 是Memcache服务器端可执行文件的名称,Memcached是以守护程序(监听)方式运行于一个或多个服务器中,随时会接收客户端的连接和操作。
ps:本文说明的内容是关于memcache服务器的,请不要跟客户端的叫法混淆。
Memcached内置内存存储方式
1. Memcached 的高性能
首先从内存模型来研究memcached:C++里分配内存有两种方式,预先分配和动态分配内存,显然预先分配内存会使程序比较快,但是它的缺点是不能有效利用内存;而动态分配可以有效利用内存,但是会使程序运行效率下降,memcached的内存分配就是基于以上原理,显然为了获得更快的速度,有时候我们不得不以空间换时间。
Memcached的高性能源于两阶段哈希(two-stage-hash)结构。Memcached就像一个巨大的、存储了很多<key, value>对的哈希表,通过key可以存储或查询任意的数据。客户端可以把数据存储在多台memcached上,当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点;客户端将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)并返回个客户端。从实现的角度看,memcached是一个非阻塞、基于事件驱动的服务器程序。
为了提高性能,memcacahed中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据丢失。另外,内存容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为了缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
2. Slab Allocator 内存分配、管理机制
1.在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free 来进行的。但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比memcached 进程本身还慢。Slab Allocator 就是为解决该问题而诞生的, 它按照预先规定的大小,将分配的内存分割成特定长度的块,以完成解决内存碎片的问题。存储结构图如下:
Memcached的存储涉及到slab、page、chunk三个概念
chunk:固定大小的内存空间,用于缓存记录,默认为88Byte。
page:分配给Slab的内存空间,默认是1M。分配给Slab之后根据Slab的大小切成chunk。
slab:同样大小的chunk组成一类slab。
2.在Slab中缓存记录的原理
memcached 根据收到的数据大小,选择最适合数据大小的Slab,memcached中保存着Slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存其中。Slab allocator分配的内存不会释放,而是重复利用。
3.Slab Allocator 的缺点
由于分配的是特定长度的内存,因此无法有效的利用分配的内存。对与该问题没有完美的解决方案,但可以调节slab class的大小差别来减少空间浪费。
4.使用Growth Factor进行调优
memcached在启动时制定Growth Factor因子(通过f选项),就可以在某种程度上控制slab之间的差异。默认值时1.25.
Memcached 删除机制
memcached是缓存,不需要永久的保存到服务器上,下面介绍它的删除机制:
1.Lazy Expiration
memcached 不会释放已经分配的内存,记录过期后,客户端无法在看到这条记录,其存储空间可以再利用。memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。这种技术被称为Lazy Expiration。因此,memcached不会在过期监视上耗费CPU时间。
2.LRU(Least Recently Used)
memcached会优先使用已超时的记录空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为LRU机制来分配空间。顾名思义,这是删除“最近最少使用”记录的机制。因此,当memcahced的内存空间不足时(无法从slab class获取新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。
Memcached 分布式算法
1.Memcached "分布式"
特别澄清一个问题:MemCache虽然被称为"分布式缓存",但是MemCache本身完全不具备分布式的功能,Memcache集群之间不会相互通信(与之形成对比的,比如JBoss Cache,某台服务器有缓存数据更新时,会通知集群中其他机器更新缓存或清除缓存数据),所谓的"分布式",完全依赖于客户端程序的实现。前面已经讲过memcached的两段哈希了,数据的保存和获取都使用相同的算法。这样将不同的键保存到不同的服务器上,就实现了memcached的分布式。Memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续进行。
2.余数分布式算法
就是“根据服务器台数的余数进行分散”。求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。
余数算法的缺点:余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器,从而影响缓存的命中率。
3.Consistent hashing(一致哈希)
首先求出memcached 服务器(节点)的哈希值,并将其配置到0~2^32-1 的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过2^32-1仍然找不到服务器,就会保存到第一台memcached 服务器上。
从上图的状态中添加一台Memcached 服务器。余数分布式算法由于保存键的服务器会发生巨大变化,而影响缓存的命中率,但Consistent Hashing中,只有在continuum 上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。
Consistent Hashing(添加服务器):
因此,Consistent Hashing 最大限度地抑制了键的重新分布。而且,有的Consistent Hashing 的实现方法还采用了虚拟节点的思想。使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每个物理节点(服务器)在continum上分配100~200 个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。
通过上文中介绍的使用Consistent Hashing 算法的Memcached 客户端函数库进行测试的结果是,由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式: (1 -m/(n+m)) * 100
常用命令介绍
1.存储及删除命令,简单易用,使用语法如下:
command <key> <flags> <expiration time> <bytes>
<value>
参数说明如下:
command set/add/replace
key key 用于查找缓存值
flags 可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息
expiration time 在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
bytes 在缓存中存储的字节数
value 存储的值(始终位于第二行)
- set 命令用于向缓存添加新的键值对,如果已经存在,则之前的值将被替换。设置成功,服务器会使用单词STORED进行响应。
- add 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。如果缓存中已经存在键,则之前的值将仍然保持相同,并且您将获得响应 NOT_STORED。
- replace 仅当键已经存在时,replace 命令才会替换缓存中的键。如果缓存中不存在键,那么您将从 memcached 服务器接受到一条 NOT_STORED 响应。
- delete 删除命令的语法:command <key>,delete 命令用于删除 memcached 中的任何现有值。您将使用一个键调用delete,如果该键存在于缓存中,则删除该值。如果不存在,则返回一条NOT_FOUND 消息。
2.读取命令
- get 命令用于检索与之前添加的键值对相关的值。当使用一个键来调用 get,如果这个键存在于缓存中,则返回相应的值。如果不存在,则不返回任何内容。get命令的key可以表示一个或者多个键,键之间以空格隔开。
- gets 命令比普通的get命令多返回一个数字。这个数字可以检查数据是否发生变化:当key对应的数据变化时,这个数字也会改变。
- cas 即check and set,只有当最后一个参数和gets所获取的参数匹配时才能存储,否则返回“EXISTS”。
3.统计命令
- stats 显示服务器信息、统计数据
- stats settings 显示所有的参数设置
- stats slabs 显示各个slab的信息,包括chunk的大小、数目、使用情况等
- stats items 显示各个slab的item信息
- stats cachedump slab_id limit_num 查看指定slab前limit_num个item,[key,expiration_time]
- flush_all 用于清理缓存中所有键值对
- stats reset 清空统计数据
运行状态分析
1.stats指令解读
stats是一个比较重要的指令,用于列出当前MemCache服务器的状态,返回的参数反映着Memcache服务器的基本信息,他们的意思是:
参 数 名 | 作 用 |
---|---|
pid | MemCache服务器的进程id |
uptime | 服务器已经运行的秒数 |
time | 服务器当前的UNIX时间戳 |
version | MemCache版本 |
pointer_size | 当前操作系统指针大小,反映了操作系统的位数,64意味着MemCache服务器是64位的 |
rusage_user | 进程的累计用户时间 |
rusage_system | 进程的累计系统时间 |
curr_connections | 当前打开着的连接数 |
total_connections | 当服务器启动以后曾经打开过的连接数 |
connection_structures | 服务器分配的连接构造数 |
cmd_get | get命令总请求次数 |
cmd_set | set命令总请求次数 |
cmd_flush | flush_all命令总请求次数 |
get_hits | 总命中次数,重要,缓存最重要的参数就是缓存命中率,以get_hits / (get_hits + get_misses)表示,比如这个缓存命中率就是99.2% |
get_misses | 总未命中次数 |
auth_cmds | 认证命令的处理次数 |
auth_errors | 认证失败的处理次数 |
bytes_read | 总读取的字节数 |
bytes_written | 总发送的字节数 |
limit_maxbytes | 分配给MemCache的内存大小(单位为字节) |
accepting_conns | 是否已经达到连接的最大值,1表示达到,0表示未达到 |
listen_disabled_num | 统计当前服务器连接数曾经达到最大连接的次数,这个次数应该为0或者接近于0,如果这个数字不断增长, 就要小心我们的服务了 |
threads | 当前MemCache总线程数,由于MemCache的线程是基于事件驱动机制的,因此不会一个线程对应一个用户请求 |
bytes | 当前服务器存储的items总字节数 |
current_items | 当前服务器存储的items总数量 |
total_items | 自服务器启动以后存储的items总数量 |
比较关注的点:get_hits 和 get_misses,命中率:get_hits/(get_hits + get_misses) 是衡量memcache服务器的一个重要指标。
2.stats slabs 指令解读
参 数 名 | 作 用 |
---|---|
chunk_size | 当前slab每个chunk的大小,单位为字节 |
chunks_per_page | 每个page可以存放的chunk数目,由于每个page固定为1M即10241024字节,所以这个值就是(10241024/chunk_size) |
total_pages | 分配给当前slab的page总数 |
total_chunks | 当前slab最多能够存放的chunk数,这个值是total_pages*chunks_per_page |
used_chunks | 已经被分配给存储对象的chunks数目 |
free_chunks | 曾经被使用过但是因为过期而被回收的chunk数 |
free_chunks_end | 新分配但还没有被使用的chunk数,这个值不为0则说明当前slab从来没有出现过容量不够的时候 |
mem_requested | 当前slab中被请求用来存储数据的内存空间字节总数,(total_chunks*chunk_size)-mem_requested表示有多少内存在当前slab中是被闲置的,这包括未用的slab+使用的slab中浪费的内存 |
get_hits | 当前slab中命中的get请求数 |
cmd_set | 当前slab中接收的所有set命令请求数 |
delete_hits | 当前slab中命中的delete请求数 |
incr_hits | 当前slab中命中的incr请求数 |
decr_hits | 当前slab中命中的decr请求数 |
cas_hits | 当前slab中命中的cas请求数 |
cas_badval | 当前slab中命中但是更新失败的cas请求数 |
通过此命令返回的信息,可以查看slab class的分布情况,以及每个slab中chunk使用情况;根据slab的分布,可以判断增长因子的设置的是否合理等。
3.stats items 命令解读
参数名 | 作用 |
---|---|
outofmemory | slab class为新item分配空间失败的次数。这意味着你运行时带上了-M或者移除操作失败 |
number | 存放的数据总数 |
age | 存放的数据中存放时间最久的数据已经存在的时间,以秒为单位 |
evicted | 不得不从LRU中移除未过期item的次数 |
evicted_time | 自最后一次清除过期item起所经历的秒数,即最后被移除缓存的时间,0表示当前就有被移除,用这个来判断数据被移除的最近时间 |
evicted_nonzero | 没有设置过期时间(默认30天),但不得不从LRU中清除该未过期的item的次数 |
因为memcached的内存分配策略导致一旦memcached的总内存达到了设置的最大内存,表示所有的slab能够使用的page都已经固定,这时如果还有数据放入,将导致memcached使用LRU策略剔除数据。而LRU策略不是针对所有的slabs,而是只针对新数据应该被放入的slab,例如有一个新的数据要被放入slab 3,则LRU只对slab 3进行,通过stats items就可以观察到这些剔除的情况。
注意evicted_time:并不是发生了LRU就代表memcached负载过载了,因为有些时候在使用cache时会设置过期时间为0,这样缓存将被存放30天,如果内存满了还持续放入数据,而这些为过期的数据很久没有被使用,则可能被剔除。把evicted_time换算成标准时间看下是否已经达到了你可以接受的时间,例如:你认为数据被缓存了2天是你可以接受的,而最后被剔除的数据已经存放了3天以上,则可以认为这个slab的压力其实可以接受的;但是如果最后被剔除的数据只被缓存了20秒,不用考虑,这个slab已经负载过重了。
注意问题
- memcache已经分配的内存不会再主动清理。
- memcache分配给某个slab的内存页不能再分配给其他slab。
- flush_all不能重置memcache分配内存页的格局,只是给所有的item置为过期。
- memcache最大存储的item(key+value)大小限制为1M,这由page大小1M限制
- 由于memcache的分布式是客户端程序通过hash算法得到的key取模来实现,不同的语言可能会采用不同的hash算法,同样的客户端程序也有可能使用相异的方法,因此在多语言、多模块共用同一组memcached服务时,一定要注意在客户端选择相同的hash算法
- 启动memcached时可以通过-M参数禁止LRU替换,在内存用尽时add和set会返回失败
- memcached启动时指定的是数据存储量,没有包括本身占用的内存、以及为了保存数据而设置的管理空间。因此它占用的内存量会多于启动时指定的内存分配量,这点需要注意。
- memcache存储的时候对key的长度有限制,php和C的最大长度都是250
参考文档
https://www.cnblogs.com/xrq73...
http://zhihuzeye.com/archives...