浅谈memcached
Memcached简介
Memcached 是一个高性能的分布式内存对象缓存系统,现在很多的大型web应用程序包括Facebook, LiveJournal,mixi, Digg等等都在使用memcached来支持他们每天数亿级的页面访问。通过把cache层与他们的web架构集成,他们的应用程序在提高了性能的同 时,还大大降低了数据库的负载。
memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。现在已成为 mixi、 hatena、 Facebook、 Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。 一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、 提高可扩展性。
memcached的特征
memcached作为高速运行的分布式缓存服务器,具有以下的特点。
- 协议简单
- 基于libevent的事件处理
- 内置内存存储方式
- memcached不互相通信的分布式
协议简单
memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据(set)、取得数据(get)。
基于libevent的事件处理
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。 memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。
Memcached的工作原理
Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。
Memcached有两个核心组件组成:服务器端(server)和客户端(client),在一个memcached的查询中,client先通过计算key的hash值来确定kv对所处在的server位置。当server确定后,客户端就会发送一个查询请求给对应的server,让它来查找确切的数据。因为这之间没有交互以及多播协议,所以memcached交互带给网络的影响是最小化的。
举例说明:考虑以下这个场景,有三个client分别是c1,c2,c3,还有三个ms分别是s1,s2,s3:
设置kv对
c1想设置key=”com”,value=”iQiyi”
c1拿到server列表,并对key做hash转化,根据hash值确定kv对所存的server位置
s2被选中了
c1连接上s2,s2收到请求,把(key=”com”,value=”iQiyi”)存了起来
获取kv对
c3想得到key=”com”的value
c3用相同的hash算法算出hash值,并确定key=”aa”的值存在s2上
c3连接上s2,并从s2那边得到value=”iQiyi”
其他任何从c1,c2,c3的想得到key=”com”的值的请求都会发向s2
memcached不互相通信的分布式
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。 各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。
添加数据:根据客户端实现的算法,根据数据的“键”来选择保存数据的memcached服务器,选定好服务器,再将数据写入memcached中。
eg:set(gyz”,data);
获取数据:获取时,将获取数据的键‘gyz’传递给函数库。函数库通过与数据保存时相同的算法,根据键来选择服务器。使用的算法相同就能保证与保存时相同的服务器,然后发送get命令。
这样,不同的键保存到了不同的服务器,就实现了memcached的分布式。
服务器和具体的请求均可以使用一致性哈希来进行实现
Memcached的内存管理机制
先了解几个基本概念:
1、slab class:在memcached中,对元素的管理是以slab为单元进行管理的。每个slab class对应一个或多个空间大小相同的chunk。参考下图。
2、chunk:存放元素的最小单元。用户数据item(key、value等)最终会保存在chunk中。memcached会根据元素大小将其放到合适的slab class中。每一个slab class中的chunk空间大小是一样的,所以元素存放进来后,chunk可能会有部分空间剩余。参考下图。
3、page:大小固定为1MB。当slab class空间不足时,就会申请page,并将page按chunk的大小进行切割。
memcached使用自己的内存管理机制,可以有效避免系统内存碎片,避免给操作系统带来负担。启动memcached时,以-m指定大小的内存将会用于数据的存放。默认情况下,这些内存会被分隔成1M的page。每个page在必要时分配给slab class,然后根据slab class里chunk的大小,将page分隔成chunk。一旦一个page被赋给一个slab class后,它将不会再被移动。因为内存空间有限,如果在slab class 3中使用了较多的page,那么在slab class 4中就只能使用较少的page。可以这么认为,memcached是一个有很多更小的相互独立的缓存系统,每一个更小的缓存系统实际上就是slab class。每个slab class都有它自己的统计信息以及自己的LRU。
在启动memcached时,指定-vv参数,可以在启动日志中查看每个slab class的chunk大小。
$ ./memcached -vv
slab class 1: chunk size 80 perslab 13107
slab class 2: chunk size 104 perslab 10082
slab class 3: chunk size 136 perslab 7710
slab class 4: chunk size 176 perslab 5957
slab class 5: chunk size 224 perslab 4681
slab class 6: chunk size 280 perslab 3744
slab class 7: chunk size 352 perslab 2978
slab class 8: chunk size 440 perslab 2383
slab class 9: chunk size 552 perslab 1899
slab class 10: chunk size 696 perslab 1506
[...etc...]
在slab class 1中,每个chunk大小为80字节,每个slab class包含13107个chunk(或item)。这两个数相乘,应该最近接1个page的大小(默认是1MB)。
当存储元素时,它们将被放到最合适的slab class中。例如,50字节的元素(包含key、value等信息)将被存放到slab class 1中的chunk,根据之前的说明,此chunk中将会有30字节的空间浪费。如果存放数据总共有90字节,将被存放到slab class 2中,此时会有14字节的空间浪费。
启动时,通过设置参数-f,可以设置每个slab class下chunk的空间增长率。
另外,memcached因为一些其他功能也会使用内存空间。例如:用来查找元素的hash table;连接时也会使用一些缓存。当然,这些功能只会占用很少一部分内存空间。
如果获取了一个已失效的元素,memcached将会释放内存。后续再将新的元素存放进来时,将会重用此内存。(前提是新元素也是要存放在同一slab class中。)
由于LRU,元素将被踢出以让给新的元素,或发现有过期的元素,它们的内存将被重用。
内置内存存储方式
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。 由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
内存管理采用采用Slab Allocator机制分配内存、管理内存。
基本原理:按照预先规定的大小,将分配的内存分割成特定长度的块
(类似于空间配置器),默认内存大小为64MB,将内存切分成chunk
memcache根据收到的数据的大小,选择最适合数据大小的块(memcached保存着空闲块的列表)
缺点:分配的是特定长度的内存,无法有效利用分配的内存。
解决:预先知道客户端发送数据的大小,动态调整内存分割块的大小。
可以使用growth factor选项。
memcached在启动时指定Growth Factor因子(-f选项),可以控制slab分割块之间的差异,可以调整分割块之间的间距。
可以使用stats查看memcached内部状态。
memcached在数据删除方面有效利用资源,不会释放已经分配的内存,记录超时后,客户端就无法看见该记录,其存储空间可重复使用。当内存空间不足时,从“最近最少使用”LRU搜索,并将其空间分配给新的记录。内部不会监视记录是否过期,而是在get时查看记录的时间戳。不会在过期监视上耗费CPU时间。