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是一款开发工具,其设计思想主要反映以下几个方面:

  1. 简单的key/value存储:服务器不关心数据本身意义及结构,主要是可序列化数据即可。
  2. 功能实现一半依赖与客户端,一半基于服务器端。
  3. 各服务器间彼此无视,不在服务器间进行数据同步。
  4. O(1)的执行效率。
  5. 内存空间的再利用,Lazy Expiration + LRU 机制。

Memcache 与 memcached的区别

1.客户端两者区别:

  1. 两个不同版本PHP的memcached的客户端
  2. memcache是原生版本,完全是在PHP框架内开发的,支持OO和非OO两套接口并存;而memcached是建立在libmemcached的基础上,只支持OO接口。
  3. 其他一些实现和支持方面的不同等。

2.服务器端两者区别:

  1. Memcache 是项目名称
  2. 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 存储的值(始终位于第二行)

  1. set 命令用于向缓存添加新的键值对,如果已经存在,则之前的值将被替换。设置成功,服务器会使用单词STORED进行响应。
  2. add 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。如果缓存中已经存在键,则之前的值将仍然保持相同,并且您将获得响应 NOT_STORED。
  3. replace 仅当键已经存在时,replace 命令才会替换缓存中的键。如果缓存中不存在键,那么您将从 memcached 服务器接受到一条 NOT_STORED 响应。
  4. delete 删除命令的语法:command <key>,delete 命令用于删除 memcached 中的任何现有值。您将使用一个键调用delete,如果该键存在于缓存中,则删除该值。如果不存在,则返回一条NOT_FOUND 消息。

2.读取命令

  1. get 命令用于检索与之前添加的键值对相关的值。当使用一个键来调用 get,如果这个键存在于缓存中,则返回相应的值。如果不存在,则不返回任何内容。get命令的key可以表示一个或者多个键,键之间以空格隔开。
  2. gets 命令比普通的get命令多返回一个数字。这个数字可以检查数据是否发生变化:当key对应的数据变化时,这个数字也会改变。
  3. cas 即check and set,只有当最后一个参数和gets所获取的参数匹配时才能存储,否则返回“EXISTS”。

3.统计命令

  1. stats 显示服务器信息、统计数据
  2. stats settings 显示所有的参数设置
  3. stats slabs 显示各个slab的信息,包括chunk的大小、数目、使用情况等
  4. stats items 显示各个slab的item信息
  5. stats cachedump slab_id limit_num 查看指定slab前limit_num个item,[key,expiration_time]
  6. flush_all 用于清理缓存中所有键值对
  7. stats reset 清空统计数据

运行状态分析

1.stats指令解读

stats是一个比较重要的指令,用于列出当前MemCache服务器的状态,返回的参数反映着Memcache服务器的基本信息,他们的意思是:

参  数  名作      用
pidMemCache服务器的进程id 
uptime服务器已经运行的秒数
time服务器当前的UNIX时间戳 
versionMemCache版本 
pointer_size当前操作系统指针大小,反映了操作系统的位数,64意味着MemCache服务器是64位的 
rusage_user进程的累计用户时间 
rusage_system 进程的累计系统时间 
curr_connections  当前打开着的连接数
total_connections   当服务器启动以后曾经打开过的连接数
connection_structures 服务器分配的连接构造数 
cmd_get get命令总请求次数 
cmd_setset命令总请求次数 
cmd_flushflush_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 命令解读

参数名作用
outofmemoryslab 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已经负载过重了。

注意问题

  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

参考文档

https://www.cnblogs.com/xrq73...
http://zhihuzeye.com/archives...

    作者:奋起直追原文地址:https://segmentfault.com/a/1190000013530708

    %s 个评论

    要回复文章请先登录注册