Memcached使用经验谈


Memcached作为一款经典的缓存组件,具备极高的读取性能,下面将从四个特性分析Memcached的高性能。
协议简单
Memcached支持文本和二进制协议;
文本协议调试简单,内容可视化;
二进制性能高效,且相对文本协议安全性高。
基于libevent的事件处理
使用IO多路复用的IO模型,Linux系统下使用epoll处理数据读写,具备极高的IO性能。
内置内存存储方式
所有数据存放在内存,相对于Linux提供的malloc/free产生的内存碎片,Memcached独特的内存存储方式可以避免内存碎片,提高内存利用率和性能。因为数据存储在内存中,所以重启Memcached和操作系统,数据将全部丢失。
Memcached互不通信的分布式
Memcached实际上不是一个真正的分布式服务器,集群的各个Memcached服务器不互相通信以共享数据,分布式特性通过客户端实现。实际上这也避免分布式集群特有的问题:脑裂。
Memcached协议和基于libevent的事件处理都不是Memcached特有的特性,所以本文重点分享Memcached内置内存存储方式和不互相通信的分布式的特性。
Slab Allocation
传统的内存分配是通过malloc/free实现,易产生内存碎片,加重操作系统内存管理器的负担。Memcached使用Slab Allocation机制高效管理内存,Slab Allocation机制按照一个特定的增长比例,将分配的内存分割成特定长度的块,完全解决内存碎片问题。
Slab Allocation将内存分割成不同Slab Class,把相同尺寸的块Chunk组成组Slab,如下图所示。Slab Allocation重复使用已分配的内存块,覆盖原有内存块的数据。

图 Slab Class
首先介绍Slab Allocation的概念:
Page:操作系统的内存页,分配给Slab的内存空间,默认1MB;
Chunk:用于缓存记录的内存空间,不同Slab的Chunk大小通过一个特定增长比例逐渐增大;
Slab:特定大小的Chunk的组,同一个Page分割成相同大小的Chunk,组成一个Slab。
Memcached根据数据大小选择最合适的Slab,如上图所示,100 bytes items选择112 bytes的Slab Chunk存储数据,内存空间利用率最高,其中items包含缓存数据的key/value等,Slab Allocation的Memcached项占用空间分析及实践。

图 数据选择最合适的Slab
上图引出Slab Allocation的一个缺点:内存空间有浪费。112的Chunk存放100 bytes数据,有12 bytes空间浪费。通过下面公式可以计算上图所期望内存利用率,当达到90%的内存利用率时可能开始踢出数据。
我们计算每个chunk的期望内存利用率:
120B的chunk放置的数据项期望长度是(96+120)/2=108,期望的内存利用率为108/120=90%;
152B的chunk放置的数据项期望长度是(120+152)/2=136,期望的内存利用率为136/152=89%;
......
n B的chunk放置的数据项期望长度是(n/1.25+n)/2=9n/10,期望的内存利用率为9n/10/n=90%;
所以,内存利用率到90%就意味growth factor=1.25的Memcached内存已经放满数据,在没有过期数据的情况下,保存新数据只能淘汰LRULeast Recent Used的数据。
推广到任意growth factor(gf),n Bytes的chunk放置的数据项期望长度是(n/gf+n)/2=(1+gf)n/(2*gf),期望的内存利用率为(1+gf)/(2*gf),比如:
gf=1.25,期望的内存利用率为90%;
gf=2,期望的内存利用率为75%;
.......
极端情况,gf=1,期望的内存利用率为100%,但是growth factor必须大于1,否则会报错。那是不是说growth factor接近1,期望的内存利用率就能接近100%?结果出人意料。
在分配64M内存的Memcached中,growth factor=1.000001,1~199个Slab class的chunk size为96 Bytes,第200个Slab class的chunk size为1MB。
实践经验表明,growth factor最好介于1.05~2之间,并且根据业务缓存数据块大小而定。
不同Chunk size递增通过Growth Factor控制,Memcached启动可以指定Growth Factor,默认是1.25。起始Chunk size都是96B,初始Chunk size是如何计算的呢?
Memcached启动命令有一个选项-n,设置第一个item存储多少字节的数据,如果没有指定-n,默认48。该选项参数赋值给settings.chunk_size。当memcached启动时,初始化第一个Slab的chunk size首先指定为:
unsigned int size = sizeof(item) + settings.chunk_size;
其中,32位机器item结构是32字节,64位机器item结构是48字节。然后为了内存字节对齐,size必须是8的整数倍,其中CHUNK_ALIGN_BYTES=8:
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES – (size % CHUNK_ALIGN_BYTES);
所以第一个Slab的chunk size = 48 + 48 = 96B。
启动memcached时n = 1,按照上面的推算第一个Slab的chunk size = 48 + 1 + 7 = 56B,加7是为了字节对齐。最后,当启动memcached时n = 0时会报如下错误信息:
Chunk size must be greater than 0
如下,根据不同Growth Factor推出期望内存利用率,当真实内存利用率达到期望内存利用率,警惕Memcached踢出数据。
Growth Factor=2 : (n+2*n)/2/2n=75%
Growth Factor=1.25 : (n+1.25*n)/2/1.25n=90%
Growth Factor=gf : (n+gf*n)/2/(gf*n)=(1+gf)/2gf
Slab Allocation的Memcached项占用空间分析
从Slab Allocation原理知道,当Memcached选择一个Slab class的Chunk存放item数据,必须计算item的空间大小以选择合适的Slab class。按照很多人理解,item是不是只包含缓存对象的value?假设不是,那如何精确计算item的空间大小呢?从这个问题延伸,Chunk是不是只存放缓存对象的value?
先看下图的item结构体,包含item、key、suffix、data等内容,注意key/data存放的数据不只是我们认为的key/value,下面将从源码层面计算item的长度。

图 item结构体
Memcached删除机制
数据不会真正从Memcached消失
Memcached不会主动释放已分配的内存,记录超时后,客户端get该记录,Memcached不会返回该记录,并标志该记录过期失效,此时内存空间可重复使用。
Lazy Expiration
Memcached内部不会监视记录是否过期,而是客户端get时查看记录的时间戳,检查记录是否过期,如果过期,标志为过期,下次存放新记录优先使用过期记录占用的内存,这种技术就是Lazy Expiration,Memcached不会在过期监控上耗费CPU资源。
LRU: Least Recently Used
从缓存中有效删除数据原理。Memcached优先使用记录已超时和未使用的内存空间,但是在追加新记录时如果没有记录已超时和未使用的内存空间,此时使用Least Recently UsedLRU机制分配空间。顾名思义,就是删除”最近最少使用“的记录的机制。当Memcached内存空间不足无法从Slab获取Chunk时,就删除”最近最少使用“的记录,将其内存空间分配给新记录存放。从缓存使用角度,该模型非常理想。如果想关闭LRU机制,启动Memcached指定-M参数即可。
Slab 钙化问题
Memcached采用LRU(Least Recent Used淘汰算法,在内存容量满时踢出过期失效和LRU数据,为新数据腾出内存空间。不过该淘汰算法在内存空间不足以分配新的Slab情况下,这时只会在同一类Slab内部踢出数据。即当某个Slab容量满,且不能在内存足够分配新的Slab,只会在相同Slab内部踢出数据,而不会挪用或者踢出其他Slab的数据。这种局部剔除数据的淘汰算法带来一个问题:Slab钙化。
删除机制是数据不会真正从内存中消失,只要被其他数据覆盖,Memcached不会主动删除Slab chunk已存在的数据。这样就可以解释这个问题:为什么我写入比较新的数据,但被淘汰了?
假设Slab有各种规格64~ 1M字节,比如应用存入的大部分数据大小在 64 ~ 128 字节范围内,那么这些数据会存储在128个字节大小的Slab chunk中,这些Slab chunk以链表的方式连接在一起。当已经没有空余的内存分配新的Slab,如果这时候写入10K新数据,且之前并没有这么大的数据写入时,那么这条新数据可以写入成功。但是当下次再写入10K数据时,第一次写入的10K数据就会被逐出。当下一次写入的新数据在64 ~ 128字节时,128字节大小的Slab链表上的数据会以LRU方式淘汰,所以LRU只会淘汰同一级别的Slab数据。
Slab钙化降低内存使用率,如果发生Slab钙化,有三种解决方案:
1) 重启Memcached实例,简单粗暴,启动后重新分配Slab class,但是如果是单点可能造成大量请求访问数据库,出现雪崩现象,冲跨数据库。
2) 随机过期:过期淘汰策略也支持淘汰其他slab class的数据,twitter工程师采用随机选择一个Slab,释放该Slab的所有缓存数据,然后重新建立一个合适的Slab。
3) 通过slab_reassign、slab_authmove参数控制。
Memcached保存记录过程
下图为Memcached保存item流程图,具体步骤为:
第一步,从LRU队列寻找过期item,这里的LRU队列是相同Slab一个队列,而不是全局统一,过期item标记方法通过Lazy Expiration实现,如果有过期的item,使用新item替换过期item,结束;
第二步,如果没有过期item,查看是否有合适空闲的Chunk,如果有,保存新item到空闲Chunk,结束;
第三步,如果没有合适空闲的Chunk,尝试初始化一个新同等Chunk size的Slab,检查内存是否足够,如果够,分配内存创建Slab和Chunk,并使用Chunk存放新item,结束;
第四步,如果内存不够,从LRU队列淘汰最近最少使用的item,然后用这个Chunk存放新item,结束。注意这一步将导致非过期LRU数据丢失。
图 Memcached保存记录过程

Memcached内存储存启示
尽量避免缓存大对象,大对象降低内存利用率和命中率
缓存/节点失效后大量请求涌向数据库,容易造成雪崩
利用组件在预警时间之后失效时间之间访问缓存,主动刷新缓存
关键业务数据不要放,LRU机制导致缓存数据删除,影响业务
Memcached不互相通信的分布式特征
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能,各个memcached不会互相通信以共享信息,由客户端的实现访问Memcached集群。如图9,应用程序通过Memcached客户端程序库访问Memcached集群,Memcached客户端程序库根据Hash算法从服务器列表选择一台Memcached服务器存放数据,各个Memcached之间不共享数据,也就没有脑裂问题。
图 应用程序通过Memcached客户端程序库访问Memcached集群

一致性Hash算法
Memcached客户端程序库根据普通的Hash取余算法选择Memcached服务器存放数据,如果移除或者新增Memcached服务器,Memcached客户端程序库要根据服务器列表总数重新取余,就会选择一台其他的Memcached服务器存储数据,而该台服务器没有缓存上一台服务器的数据,所以导致大量数据发起大量请求访问DB获取数据,容易造成雪崩问题。
为了解决Hash取余算法的固有缺点,Memcached引入一致性Hash算法,如果图10所示,首先求出Memcached服务器节点的哈希值,将其分配到0~232的环上。然后用同样的方法求出存放数据的哈希值,映射到环上,并从数据映射的位置开始顺时针查找,将数据存放到最近的一个Memcached节点。如果超过232仍然找不到服务器,就会保存到第一台Memcached节点上。获取数据的查找方式也是如此。

图 一致性Hash基本原理
从上图的状态中添加一台Memcached服务器节点5,变成下图,只有环上增加节点5到逆时针方向的第一台节点节点2之间的键受影响,本应映射到节点4而映射到节点5。因此,一致性Hash算法最大程度抑制键的重新分布。

图 一致性Hash:添加服务器
下图的哈希表或许更加直观,md5根据key值摘要一个128bit的哈希值校验和,一般表示为32位的16进制数,我们取哈希值的第一位范围将key映射到不同节点,如图左侧表格所示。当新增一个节点5,把原本映射到节点4的一半数据映射到节点5,其他三个节点不受影响。但是引出数据在Memcached节点上分布不均匀的问题,原本左侧表格每个节点映射的数据量一样,但是右侧表格的节点4/5只有其他三个节点的一半数据量,导致节点4/5的带宽和内存使用率一直不饱满。

图 一致性Hash:哈希表方式
为此,引入虚拟节点,如图13所示,左侧表格中依md5值划分为16个虚拟节点,每四个虚拟节点映射到一个物理节点。当增加物理节点5时,就从节点1/2/3各拿一个虚拟节点映射到物理节点5,这样每个物理节点基本有3到4个虚拟节点的映射,缓存数据分布相对上图右侧表格均衡很多。

图 一致性Hash:引入虚拟节点
一致性Hash算法启示
副本存储到多个节点,避免单点故障或者数据失效大量请求涌向DB
节点故障后,有快速预热新节点应急手段,或者使用冷节点备份
Memcached一些疑难问题
1. 为什么不能用缓存保存session?
当Memcached内存空间不足,并且没有过期数据,Memcached用新数据覆盖LRU的数据,导致部分session信息被清除,用户重新登录才能获取到session,用户体验差。实际上,可以把session持久化到DB,Memcached缓存一份session,待Memcached查询不到session信息,再到DB查询,并更新Memcached,既能保证数据不丢失,也保证session查询速度快。
2. 为什么同一个Memcached的数据,有的缓存值较大的旧数据要2天才被置换出,有的缓存值较小的新数据几分钟就被置换出?
值较大的数据占用Slab Class的大Chunk size,值较小的数据占用Slab Class的小Chunk size,根据Slab钙化问题,当值较小的数据占用Slab Class空间不够用时,并且没有多余的内存和过期的数据,不会挪用值较大的数据占用Slab Class空间,只会复用原有值较小的数据占用Slab Class空间,根据LRU算法置换出同一种Slab的Chunk数据。
3. Cache失效后的拥堵问题
通常我们会为两种数据做Cache,一种是热数据,也就是说短时间内有很多人访问的数据;另一种是高成本的数据,也就说查询很耗时的数据。当这些数据过期的瞬间,如果大量请求同时到达,那么它们会一起请求后端重建Cache,造成拥堵问题。一般有如下几种解决思路可供选择:
首先,通过定时任务主动更新Cache;
其次,我们可以加分布式锁,保证只有一个请求访问数据库更新缓存。
4. Multiget的无底洞问题
出于效率的考虑,很多Memcached应用都以Multiget操作为主,随着访问量的增加,系统负载捉襟见肘,遇到此类问题,直觉通常都是增加服务器来提升系统性能,但是在实际操作中却发现问题并不简单,新加的服务器好像被扔到了无底洞里一样毫无效果。Multiget根据key请求多台服务器,但这并不是问题的症结,真正的原因在于客户端在请求多台服务器时是并行的还是串行的!问题是很多客户端,在处理Multiget多服务器请求时,使用的是串行的方式!也就是说,先请求一台服务器,然后等待响应结果,接着请求另一台,结果导致客户端操作时间累加,请求堆积,性能下降。
5. 缓存命中率下降,但是内存的利用率很高时,我们需要如何进行处理?
内存空间不足,导致缓存失效移除,命中率下降,既然内存利用率高,扩容Memcached服务器即可。
6. 缓存命中率下降,内存的利用率也在下降时,我们需要如何进行处理?
跟问题2类似,也是Slab钙化问题。空间利用率高的Slab Class不会使用空间利用率低的其他的Slab Class,导致空间利用率高的Slab Class不断因为LRU踢出数据,总体而言,缓存命中率下降,内存的利用率也会下降。Slab钙化降低内存使用率,如果发生Slab钙化,有三种解决方案:
重启Memcached实例,简单粗暴,启动后重新分配Slab class,但是如果是单点可能造成大量请求访问数据库,出现雪崩现象,冲跨数据库。
随机过期:过期淘汰策略也支持淘汰其他slab class的数据,twitter工程师采用随机选择一个Slab,释放该Slab的所有缓存数据,然后重新建立一个合适的Slab。
通过slab_reassign、slab_authmove。
7. 通常情况下,缓存的粒度越小,命中率会越高。
举个实际的例子说明:当缓存单个对象的时候例如:单个用户信息,只有当该对象对应的数据发生变化时,我们才需要更新缓存或者移除缓存。而当缓存一个集合的时候例如:所有用户数据,其中任何一个对象对应的数据发生变化时,都需要更新或移除缓存。由于序列化和反序列化需要一定的资源开销,当处于高并发高负载的情况下,对大对象数据的频繁读取有可能会使得服务器的CPU崩溃。
8. 缓存被“击穿”问题
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑另外一个问题:缓存被“击穿”的问题。
概念:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
如何解决:业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候判断拿出来的值为空,不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作比如Redis的SETNX或者Memcache的ADD去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
过期策略问题追查
随着业务的增长,数据量已经超过了memcached设置的最大内存,因为出现了内存置换出的情况,往往几天前的热点数据会被唤出,这也很正常。因为新需求,缓存中需要存放新的数据。但是实际测试发现,缓存中的数据几分钟就会被失效,导致MySQL压力很大。为什么同一个memcached的数据,有的缓存后面简称为旧数据要2天才会被置换出,有的缓存后面简称新数据几分钟就会被换出?
首先分析新旧数据的不同:
1,key肯定不同
2,value大小上,旧数据value较大,新数据value很小
memcached是按照slabs作为内存单元来分配。新旧数据value差异较大,肯定位于不同的chunk里面。考虑到memcached内存已经占满,会不停置换内存。为什么总是新数据被置换出来,而旧数据不容易被置换呢?
缓存数据的过期时间都没有设置,因此默认就是30天。这样当内存写满的情况下,分配一个item,前两步都不会满足,走到第三步。
对于旧数据,因为跑了很久,该chunk已经占用了很多的slabs,所以通过LRU置换,问题并不大。
对于新数据,因为value大小差异较大,自然用的是一个没多少slabs的chunk, 通过LRU置换,就会出现问题,导致频繁被置换。
可以想到,如果这时候重启了memcached,这样新旧数据会比较公平,一段时间后都会分配差不多的item假设新旧数据使用频率差不多,这样LRU换出的话,问题也不大。
解决之法
重启memcached,解决这种新旧数据不公平的情况。
分配更大的memcached,避免出现换出。
memcached可以使用stats 看evictions 的数据,如果不为0,说明此时memcached分配内存出现了换出。
如果数据使用频率差异很大,还是会发生这种情况。这时候就会麻烦一些,可以考虑分不同的memcached存储,或者预先用假数据预热缓存,目的就是占住LRU的位置。
如何处理Memcached kv数据过大的情况
Memcached支持的key的最大长度是250个字节,推荐使用使用较短的key,因为可以节省内存和带宽。支持的Value的最大上限为1M字节,但太大的对象,会占用较大的内存和带宽,导致较小的QPS,所以通常情况下建议value的大小在10K以下为宜。这要求应用在存储数据的时候,需要更多地考虑数据结构的设计,比如不存储实际的业务数据,改为存储索引数据等。
说明一点,这里的value大小是针对用户传入Memcached时序列化以后的大小,而不是在Memcached中实际存储的value大小,因此业务方只需对传入数据大小进行预估即可。那么当所存的业务数据确实无法再精简时,要怎么处理已获取更好的性能呢?以下总结几种常用的优化方式:
1. 实现轻量级序列化方式
这种方式从根本上减小了value的大小,但是从实现角度上来说会复杂一些,需要应用方自己做序列化和反序列化。java客户端对基本数据类型采用java默认的序列化方式,占用空间较大。因此可以选择轻量级的数据存储方式做序列化,比如使用json或者google protocal buffer进行序列化和反序列化。序列化后,数据bytes[]数组,然后写入Memcached。读出时,得到byte[]数组,然后再反序列化。
2. 拆分数据
这种方式主要从业务逻辑上去考虑,对大value进行拆分。假设一个key对应了多项业务数据,就可以把这个key拆分成多个key。这里key的名称可以通过加前缀来标识。拆分后如果需要读取这些数据,可以用批量读取接口完成比如mget。这样拆分后虽然增多了网络交互,可以把key的访问压力分散到多台机器上,总体性能上会优于单key存储。
如果这些数据不需要每次都全部取出的时候,也可以用prefix的方式存储,把业务数据分多类来存储,但是一个prefix下所有数据都存储在一台机器上,压力还是会集中在单机上。
3. 压缩数据
在数据传入Memcached之前,先使用压缩算法进行数据压缩,减小数据大小。当使用压缩算法的方式时,建议用户可以先对数据进行测试,避免用压缩算法后反而增大了数据的大小。
Memcached不能写入超过1M
启动Memcached每个Page 1MB, growth factor=1.25最大的Chunk Size=1048576 Bytes=1MB。因为Slab Allocation的最大chunk Size是1M字节,每个item都要写入到Slab Chunk中。当写入的item大于最大的chunk size1MB时,会提示SERVER_ERROR object too large for cache,写入的item不能超过Page大小。对于超过1MB的item,拆分成多个小对象存放。
memcached中对值(value)的大小限制:The maximum size of a value you can store in memcached is 1 megabyte. If your data is larger, consider clientside compression or splitting the value up into multiple keys.
默认大小为1MB,超过此大小时就要考虑压缩与做分离考虑。从1.4.2版本开始,可以使用'-I'参数调整该值来存放更大的(键)值。
memcached -I 128k # Refuse items larger than 128k.
memcached -I 16m # Allow objects up to 16MB
可在其配置文件(/etc/memcached.conf)中加入:
-I 16M
上文总结自:大头8086的简书博客,感谢作者。
memcached+SASL:更安全地访问memcached
memcached基于c/s架构的缓存系统,由于其默认不开启认证机制,导致客户端无需认证即可读取、修改缓存内容。在1.4.3版本加入了对SASL的支持,所以要使用认证功能的话需要将memcached升级到1.4.3以上的版本。
SASL全称Simple Authentication and Security Layer(简单认证与安全层),是一种用来扩充C/S模式验证能力的机制,SASL只是认证过程,将应用层与系统认证机制整合起来,SASL本身不能进行认证。SASL支持的认证方法为:getpwent kerberos5 pam rimap shadow ldap httpform。
在编译时增加相关选项后进行编译:--enable-sasl,如果要使用客户端,则客户端需要支持SASL。
为memcached启用Unix Domain Socket连接方式
memcached为高性能缓存系统,增加认证无疑会带来一定的性能损耗,所以在使用的时候要权衡利弊。一般情况下,memcached需要部署在信任的网络中,如果无法启用SASL,可使用主机防火墙(iptalbes、firewalld等)和网络防火墙对memcached服务端口进行过滤;或改为本地(127.0.0.1)监听或unix domain socket,但只能在本机使用了,据说 unix sockets 比 tcp sockets 的方式快33%。
memcached的FAQ中也提到为了安全验证,可以考虑让memcached监听unix domain socket。可以通过-s选项指定unix domain socket的路径名来支持这一功能,注意:为了可移植性,尽量使用绝对路径,因为Posix标准声称给unix domain socket绑定相对路径将导致不可预计的后果,在linux的测试是可以使用相对路径。
端口呢?没有端口了。socket文件你可以理解成FIFO的管道,unix domain socket的server/client通过这个管道进行通讯。
libmemcached支持通过unix domain socket来访问memcached,基于libmemcached实现的client应该都可以使用这一功能。目前来看,java平台由于不支持平台相关的unix domain socket,因此无法享受memcached的这一特性。不过有一个开源项目通过jni支持实现了unix domain socket,这个项目称为juds。核心类就三个,使用非常简单。下载文件后,解压缩,make & make install即可。注意,Makefile中写死了JAVA_HOME,手工修改即可。juds还只支持阻塞IO,考虑可进一步使用select、poll来扩展实现非阻塞IO。
memcached开启socket方式不成功的原因总结:
开启socket方式意味着就要关闭网络连接(本地的127地址或其它ip地址)的方式(tcp or udp),且在权限的设定上还有一定的要求。socket方式将会在本地生成为个socket类型的文件,应用程序通过该文件与memcached进行数据的交互,由于是文件,就涉及到用户与权限的问题。
将其配置文件中的'-l,-p,-U'这3个与网络连接相关配置参数注释,加上或修改'-s,-a'这两个参数。
# Set unix socket which we put in the folder /var/run/memcached and made memcache user the owner
#-s /tmp/memcached.sock
-s /var/run/memcached/mcached10.sock
# Set permissions for the memcached socket so memcache user and user or group can read or write or execute
#-a 0755
-a 0666
文件权限此时为:
srw-rw-rw- 1 memcache memcache 0 Sep 10 19:00 mcached10.sock
如果应用的用户与运行memcached的用户不同组的话,一定要保证文件权限的其它(other)位有'rw',不然不能对其进行写入操作,这点要注意。有人将socket文件放置到/tmp/目录下,也会有导致应用不能正常使用的情况,这多是因权限导致的;/var/run/memcached的属主及用户组均为其运行用户memcache,将其socket,pid,log文件放置于其下是一种比较推荐的方式,或将程序的运行用户加入到memcache这个组中。
如何测试
在网络环境下,memcached可以通过telnet方式登录进行简单的调试,那在socket方式有什么办法来简单的连接和操作执行呢?
Linux下有两个强大的网络操作类工具:
netcat 与 socat,下面收集其相关的用法,不过nc的测试没有像网上说的结果是成功的,socat试用下来是没有问题的。memcached工作于socket方式telnet自然是派不上用场了。
nc -U /var/run/memcached/mcached10.sock
'echo "stats" | nc local:/var/run/memcached/mcached10.sock'
echo "stats" | socat -v "UNIX-CONNECT:/var/run/memcached/mcached10.sock" STDIN
socat -v UNIX-CONNECT:/var/run/memcached/mcached10.sock STDIN
参考来源:
SASL Memcached Now Available
协议简单
Memcached支持文本和二进制协议;
文本协议调试简单,内容可视化;
二进制性能高效,且相对文本协议安全性高。
基于libevent的事件处理
使用IO多路复用的IO模型,Linux系统下使用epoll处理数据读写,具备极高的IO性能。
内置内存存储方式
所有数据存放在内存,相对于Linux提供的malloc/free产生的内存碎片,Memcached独特的内存存储方式可以避免内存碎片,提高内存利用率和性能。因为数据存储在内存中,所以重启Memcached和操作系统,数据将全部丢失。
Memcached互不通信的分布式
Memcached实际上不是一个真正的分布式服务器,集群的各个Memcached服务器不互相通信以共享数据,分布式特性通过客户端实现。实际上这也避免分布式集群特有的问题:脑裂。
Memcached协议和基于libevent的事件处理都不是Memcached特有的特性,所以本文重点分享Memcached内置内存存储方式和不互相通信的分布式的特性。
Slab Allocation
传统的内存分配是通过malloc/free实现,易产生内存碎片,加重操作系统内存管理器的负担。Memcached使用Slab Allocation机制高效管理内存,Slab Allocation机制按照一个特定的增长比例,将分配的内存分割成特定长度的块,完全解决内存碎片问题。
Slab Allocation将内存分割成不同Slab Class,把相同尺寸的块Chunk组成组Slab,如下图所示。Slab Allocation重复使用已分配的内存块,覆盖原有内存块的数据。

图 Slab Class
首先介绍Slab Allocation的概念:
Page:操作系统的内存页,分配给Slab的内存空间,默认1MB;
Chunk:用于缓存记录的内存空间,不同Slab的Chunk大小通过一个特定增长比例逐渐增大;
Slab:特定大小的Chunk的组,同一个Page分割成相同大小的Chunk,组成一个Slab。
Memcached根据数据大小选择最合适的Slab,如上图所示,100 bytes items选择112 bytes的Slab Chunk存储数据,内存空间利用率最高,其中items包含缓存数据的key/value等,Slab Allocation的Memcached项占用空间分析及实践。

图 数据选择最合适的Slab
上图引出Slab Allocation的一个缺点:内存空间有浪费。112的Chunk存放100 bytes数据,有12 bytes空间浪费。通过下面公式可以计算上图所期望内存利用率,当达到90%的内存利用率时可能开始踢出数据。
我们计算每个chunk的期望内存利用率:
120B的chunk放置的数据项期望长度是(96+120)/2=108,期望的内存利用率为108/120=90%;
152B的chunk放置的数据项期望长度是(120+152)/2=136,期望的内存利用率为136/152=89%;
......
n B的chunk放置的数据项期望长度是(n/1.25+n)/2=9n/10,期望的内存利用率为9n/10/n=90%;
所以,内存利用率到90%就意味growth factor=1.25的Memcached内存已经放满数据,在没有过期数据的情况下,保存新数据只能淘汰LRULeast Recent Used的数据。
推广到任意growth factor(gf),n Bytes的chunk放置的数据项期望长度是(n/gf+n)/2=(1+gf)n/(2*gf),期望的内存利用率为(1+gf)/(2*gf),比如:
gf=1.25,期望的内存利用率为90%;
gf=2,期望的内存利用率为75%;
.......
极端情况,gf=1,期望的内存利用率为100%,但是growth factor必须大于1,否则会报错。那是不是说growth factor接近1,期望的内存利用率就能接近100%?结果出人意料。
在分配64M内存的Memcached中,growth factor=1.000001,1~199个Slab class的chunk size为96 Bytes,第200个Slab class的chunk size为1MB。
实践经验表明,growth factor最好介于1.05~2之间,并且根据业务缓存数据块大小而定。
不同Chunk size递增通过Growth Factor控制,Memcached启动可以指定Growth Factor,默认是1.25。起始Chunk size都是96B,初始Chunk size是如何计算的呢?
Memcached启动命令有一个选项-n,设置第一个item存储多少字节的数据,如果没有指定-n,默认48。该选项参数赋值给settings.chunk_size。当memcached启动时,初始化第一个Slab的chunk size首先指定为:
unsigned int size = sizeof(item) + settings.chunk_size;
其中,32位机器item结构是32字节,64位机器item结构是48字节。然后为了内存字节对齐,size必须是8的整数倍,其中CHUNK_ALIGN_BYTES=8:
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES – (size % CHUNK_ALIGN_BYTES);
所以第一个Slab的chunk size = 48 + 48 = 96B。
启动memcached时n = 1,按照上面的推算第一个Slab的chunk size = 48 + 1 + 7 = 56B,加7是为了字节对齐。最后,当启动memcached时n = 0时会报如下错误信息:
Chunk size must be greater than 0
如下,根据不同Growth Factor推出期望内存利用率,当真实内存利用率达到期望内存利用率,警惕Memcached踢出数据。
Growth Factor=2 : (n+2*n)/2/2n=75%
Growth Factor=1.25 : (n+1.25*n)/2/1.25n=90%
Growth Factor=gf : (n+gf*n)/2/(gf*n)=(1+gf)/2gf
Slab Allocation的Memcached项占用空间分析
从Slab Allocation原理知道,当Memcached选择一个Slab class的Chunk存放item数据,必须计算item的空间大小以选择合适的Slab class。按照很多人理解,item是不是只包含缓存对象的value?假设不是,那如何精确计算item的空间大小呢?从这个问题延伸,Chunk是不是只存放缓存对象的value?
先看下图的item结构体,包含item、key、suffix、data等内容,注意key/data存放的数据不只是我们认为的key/value,下面将从源码层面计算item的长度。

图 item结构体
Memcached删除机制
数据不会真正从Memcached消失
Memcached不会主动释放已分配的内存,记录超时后,客户端get该记录,Memcached不会返回该记录,并标志该记录过期失效,此时内存空间可重复使用。
Lazy Expiration
Memcached内部不会监视记录是否过期,而是客户端get时查看记录的时间戳,检查记录是否过期,如果过期,标志为过期,下次存放新记录优先使用过期记录占用的内存,这种技术就是Lazy Expiration,Memcached不会在过期监控上耗费CPU资源。
LRU: Least Recently Used
从缓存中有效删除数据原理。Memcached优先使用记录已超时和未使用的内存空间,但是在追加新记录时如果没有记录已超时和未使用的内存空间,此时使用Least Recently UsedLRU机制分配空间。顾名思义,就是删除”最近最少使用“的记录的机制。当Memcached内存空间不足无法从Slab获取Chunk时,就删除”最近最少使用“的记录,将其内存空间分配给新记录存放。从缓存使用角度,该模型非常理想。如果想关闭LRU机制,启动Memcached指定-M参数即可。
Slab 钙化问题
Memcached采用LRU(Least Recent Used淘汰算法,在内存容量满时踢出过期失效和LRU数据,为新数据腾出内存空间。不过该淘汰算法在内存空间不足以分配新的Slab情况下,这时只会在同一类Slab内部踢出数据。即当某个Slab容量满,且不能在内存足够分配新的Slab,只会在相同Slab内部踢出数据,而不会挪用或者踢出其他Slab的数据。这种局部剔除数据的淘汰算法带来一个问题:Slab钙化。
删除机制是数据不会真正从内存中消失,只要被其他数据覆盖,Memcached不会主动删除Slab chunk已存在的数据。这样就可以解释这个问题:为什么我写入比较新的数据,但被淘汰了?
假设Slab有各种规格64~ 1M字节,比如应用存入的大部分数据大小在 64 ~ 128 字节范围内,那么这些数据会存储在128个字节大小的Slab chunk中,这些Slab chunk以链表的方式连接在一起。当已经没有空余的内存分配新的Slab,如果这时候写入10K新数据,且之前并没有这么大的数据写入时,那么这条新数据可以写入成功。但是当下次再写入10K数据时,第一次写入的10K数据就会被逐出。当下一次写入的新数据在64 ~ 128字节时,128字节大小的Slab链表上的数据会以LRU方式淘汰,所以LRU只会淘汰同一级别的Slab数据。
Slab钙化降低内存使用率,如果发生Slab钙化,有三种解决方案:
1) 重启Memcached实例,简单粗暴,启动后重新分配Slab class,但是如果是单点可能造成大量请求访问数据库,出现雪崩现象,冲跨数据库。
2) 随机过期:过期淘汰策略也支持淘汰其他slab class的数据,twitter工程师采用随机选择一个Slab,释放该Slab的所有缓存数据,然后重新建立一个合适的Slab。
3) 通过slab_reassign、slab_authmove参数控制。
Memcached保存记录过程
下图为Memcached保存item流程图,具体步骤为:
第一步,从LRU队列寻找过期item,这里的LRU队列是相同Slab一个队列,而不是全局统一,过期item标记方法通过Lazy Expiration实现,如果有过期的item,使用新item替换过期item,结束;
第二步,如果没有过期item,查看是否有合适空闲的Chunk,如果有,保存新item到空闲Chunk,结束;
第三步,如果没有合适空闲的Chunk,尝试初始化一个新同等Chunk size的Slab,检查内存是否足够,如果够,分配内存创建Slab和Chunk,并使用Chunk存放新item,结束;
第四步,如果内存不够,从LRU队列淘汰最近最少使用的item,然后用这个Chunk存放新item,结束。注意这一步将导致非过期LRU数据丢失。
图 Memcached保存记录过程

Memcached内存储存启示
尽量避免缓存大对象,大对象降低内存利用率和命中率
缓存/节点失效后大量请求涌向数据库,容易造成雪崩
利用组件在预警时间之后失效时间之间访问缓存,主动刷新缓存
关键业务数据不要放,LRU机制导致缓存数据删除,影响业务
Memcached不互相通信的分布式特征
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能,各个memcached不会互相通信以共享信息,由客户端的实现访问Memcached集群。如图9,应用程序通过Memcached客户端程序库访问Memcached集群,Memcached客户端程序库根据Hash算法从服务器列表选择一台Memcached服务器存放数据,各个Memcached之间不共享数据,也就没有脑裂问题。
图 应用程序通过Memcached客户端程序库访问Memcached集群

一致性Hash算法
Memcached客户端程序库根据普通的Hash取余算法选择Memcached服务器存放数据,如果移除或者新增Memcached服务器,Memcached客户端程序库要根据服务器列表总数重新取余,就会选择一台其他的Memcached服务器存储数据,而该台服务器没有缓存上一台服务器的数据,所以导致大量数据发起大量请求访问DB获取数据,容易造成雪崩问题。
为了解决Hash取余算法的固有缺点,Memcached引入一致性Hash算法,如果图10所示,首先求出Memcached服务器节点的哈希值,将其分配到0~232的环上。然后用同样的方法求出存放数据的哈希值,映射到环上,并从数据映射的位置开始顺时针查找,将数据存放到最近的一个Memcached节点。如果超过232仍然找不到服务器,就会保存到第一台Memcached节点上。获取数据的查找方式也是如此。

图 一致性Hash基本原理
从上图的状态中添加一台Memcached服务器节点5,变成下图,只有环上增加节点5到逆时针方向的第一台节点节点2之间的键受影响,本应映射到节点4而映射到节点5。因此,一致性Hash算法最大程度抑制键的重新分布。

图 一致性Hash:添加服务器
下图的哈希表或许更加直观,md5根据key值摘要一个128bit的哈希值校验和,一般表示为32位的16进制数,我们取哈希值的第一位范围将key映射到不同节点,如图左侧表格所示。当新增一个节点5,把原本映射到节点4的一半数据映射到节点5,其他三个节点不受影响。但是引出数据在Memcached节点上分布不均匀的问题,原本左侧表格每个节点映射的数据量一样,但是右侧表格的节点4/5只有其他三个节点的一半数据量,导致节点4/5的带宽和内存使用率一直不饱满。

图 一致性Hash:哈希表方式
为此,引入虚拟节点,如图13所示,左侧表格中依md5值划分为16个虚拟节点,每四个虚拟节点映射到一个物理节点。当增加物理节点5时,就从节点1/2/3各拿一个虚拟节点映射到物理节点5,这样每个物理节点基本有3到4个虚拟节点的映射,缓存数据分布相对上图右侧表格均衡很多。

图 一致性Hash:引入虚拟节点
一致性Hash算法启示
副本存储到多个节点,避免单点故障或者数据失效大量请求涌向DB
节点故障后,有快速预热新节点应急手段,或者使用冷节点备份
Memcached一些疑难问题
1. 为什么不能用缓存保存session?
当Memcached内存空间不足,并且没有过期数据,Memcached用新数据覆盖LRU的数据,导致部分session信息被清除,用户重新登录才能获取到session,用户体验差。实际上,可以把session持久化到DB,Memcached缓存一份session,待Memcached查询不到session信息,再到DB查询,并更新Memcached,既能保证数据不丢失,也保证session查询速度快。
2. 为什么同一个Memcached的数据,有的缓存值较大的旧数据要2天才被置换出,有的缓存值较小的新数据几分钟就被置换出?
值较大的数据占用Slab Class的大Chunk size,值较小的数据占用Slab Class的小Chunk size,根据Slab钙化问题,当值较小的数据占用Slab Class空间不够用时,并且没有多余的内存和过期的数据,不会挪用值较大的数据占用Slab Class空间,只会复用原有值较小的数据占用Slab Class空间,根据LRU算法置换出同一种Slab的Chunk数据。
3. Cache失效后的拥堵问题
通常我们会为两种数据做Cache,一种是热数据,也就是说短时间内有很多人访问的数据;另一种是高成本的数据,也就说查询很耗时的数据。当这些数据过期的瞬间,如果大量请求同时到达,那么它们会一起请求后端重建Cache,造成拥堵问题。一般有如下几种解决思路可供选择:
首先,通过定时任务主动更新Cache;
其次,我们可以加分布式锁,保证只有一个请求访问数据库更新缓存。
4. Multiget的无底洞问题
出于效率的考虑,很多Memcached应用都以Multiget操作为主,随着访问量的增加,系统负载捉襟见肘,遇到此类问题,直觉通常都是增加服务器来提升系统性能,但是在实际操作中却发现问题并不简单,新加的服务器好像被扔到了无底洞里一样毫无效果。Multiget根据key请求多台服务器,但这并不是问题的症结,真正的原因在于客户端在请求多台服务器时是并行的还是串行的!问题是很多客户端,在处理Multiget多服务器请求时,使用的是串行的方式!也就是说,先请求一台服务器,然后等待响应结果,接着请求另一台,结果导致客户端操作时间累加,请求堆积,性能下降。
5. 缓存命中率下降,但是内存的利用率很高时,我们需要如何进行处理?
内存空间不足,导致缓存失效移除,命中率下降,既然内存利用率高,扩容Memcached服务器即可。
6. 缓存命中率下降,内存的利用率也在下降时,我们需要如何进行处理?
跟问题2类似,也是Slab钙化问题。空间利用率高的Slab Class不会使用空间利用率低的其他的Slab Class,导致空间利用率高的Slab Class不断因为LRU踢出数据,总体而言,缓存命中率下降,内存的利用率也会下降。Slab钙化降低内存使用率,如果发生Slab钙化,有三种解决方案:
重启Memcached实例,简单粗暴,启动后重新分配Slab class,但是如果是单点可能造成大量请求访问数据库,出现雪崩现象,冲跨数据库。
随机过期:过期淘汰策略也支持淘汰其他slab class的数据,twitter工程师采用随机选择一个Slab,释放该Slab的所有缓存数据,然后重新建立一个合适的Slab。
通过slab_reassign、slab_authmove。
7. 通常情况下,缓存的粒度越小,命中率会越高。
举个实际的例子说明:当缓存单个对象的时候例如:单个用户信息,只有当该对象对应的数据发生变化时,我们才需要更新缓存或者移除缓存。而当缓存一个集合的时候例如:所有用户数据,其中任何一个对象对应的数据发生变化时,都需要更新或移除缓存。由于序列化和反序列化需要一定的资源开销,当处于高并发高负载的情况下,对大对象数据的频繁读取有可能会使得服务器的CPU崩溃。
8. 缓存被“击穿”问题
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑另外一个问题:缓存被“击穿”的问题。
概念:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
如何解决:业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候判断拿出来的值为空,不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作比如Redis的SETNX或者Memcache的ADD去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
过期策略问题追查
随着业务的增长,数据量已经超过了memcached设置的最大内存,因为出现了内存置换出的情况,往往几天前的热点数据会被唤出,这也很正常。因为新需求,缓存中需要存放新的数据。但是实际测试发现,缓存中的数据几分钟就会被失效,导致MySQL压力很大。为什么同一个memcached的数据,有的缓存后面简称为旧数据要2天才会被置换出,有的缓存后面简称新数据几分钟就会被换出?
首先分析新旧数据的不同:
1,key肯定不同
2,value大小上,旧数据value较大,新数据value很小
memcached是按照slabs作为内存单元来分配。新旧数据value差异较大,肯定位于不同的chunk里面。考虑到memcached内存已经占满,会不停置换内存。为什么总是新数据被置换出来,而旧数据不容易被置换呢?
缓存数据的过期时间都没有设置,因此默认就是30天。这样当内存写满的情况下,分配一个item,前两步都不会满足,走到第三步。
对于旧数据,因为跑了很久,该chunk已经占用了很多的slabs,所以通过LRU置换,问题并不大。
对于新数据,因为value大小差异较大,自然用的是一个没多少slabs的chunk, 通过LRU置换,就会出现问题,导致频繁被置换。
可以想到,如果这时候重启了memcached,这样新旧数据会比较公平,一段时间后都会分配差不多的item假设新旧数据使用频率差不多,这样LRU换出的话,问题也不大。
解决之法
重启memcached,解决这种新旧数据不公平的情况。
分配更大的memcached,避免出现换出。
memcached可以使用stats 看evictions 的数据,如果不为0,说明此时memcached分配内存出现了换出。
如果数据使用频率差异很大,还是会发生这种情况。这时候就会麻烦一些,可以考虑分不同的memcached存储,或者预先用假数据预热缓存,目的就是占住LRU的位置。
如何处理Memcached kv数据过大的情况
Memcached支持的key的最大长度是250个字节,推荐使用使用较短的key,因为可以节省内存和带宽。支持的Value的最大上限为1M字节,但太大的对象,会占用较大的内存和带宽,导致较小的QPS,所以通常情况下建议value的大小在10K以下为宜。这要求应用在存储数据的时候,需要更多地考虑数据结构的设计,比如不存储实际的业务数据,改为存储索引数据等。
说明一点,这里的value大小是针对用户传入Memcached时序列化以后的大小,而不是在Memcached中实际存储的value大小,因此业务方只需对传入数据大小进行预估即可。那么当所存的业务数据确实无法再精简时,要怎么处理已获取更好的性能呢?以下总结几种常用的优化方式:
1. 实现轻量级序列化方式
这种方式从根本上减小了value的大小,但是从实现角度上来说会复杂一些,需要应用方自己做序列化和反序列化。java客户端对基本数据类型采用java默认的序列化方式,占用空间较大。因此可以选择轻量级的数据存储方式做序列化,比如使用json或者google protocal buffer进行序列化和反序列化。序列化后,数据bytes[]数组,然后写入Memcached。读出时,得到byte[]数组,然后再反序列化。
2. 拆分数据
这种方式主要从业务逻辑上去考虑,对大value进行拆分。假设一个key对应了多项业务数据,就可以把这个key拆分成多个key。这里key的名称可以通过加前缀来标识。拆分后如果需要读取这些数据,可以用批量读取接口完成比如mget。这样拆分后虽然增多了网络交互,可以把key的访问压力分散到多台机器上,总体性能上会优于单key存储。
如果这些数据不需要每次都全部取出的时候,也可以用prefix的方式存储,把业务数据分多类来存储,但是一个prefix下所有数据都存储在一台机器上,压力还是会集中在单机上。
3. 压缩数据
在数据传入Memcached之前,先使用压缩算法进行数据压缩,减小数据大小。当使用压缩算法的方式时,建议用户可以先对数据进行测试,避免用压缩算法后反而增大了数据的大小。
Memcached不能写入超过1M
启动Memcached每个Page 1MB, growth factor=1.25最大的Chunk Size=1048576 Bytes=1MB。因为Slab Allocation的最大chunk Size是1M字节,每个item都要写入到Slab Chunk中。当写入的item大于最大的chunk size1MB时,会提示SERVER_ERROR object too large for cache,写入的item不能超过Page大小。对于超过1MB的item,拆分成多个小对象存放。
memcached中对值(value)的大小限制:The maximum size of a value you can store in memcached is 1 megabyte. If your data is larger, consider clientside compression or splitting the value up into multiple keys.
默认大小为1MB,超过此大小时就要考虑压缩与做分离考虑。从1.4.2版本开始,可以使用'-I'参数调整该值来存放更大的(键)值。
memcached -I 128k # Refuse items larger than 128k.
memcached -I 16m # Allow objects up to 16MB
可在其配置文件(/etc/memcached.conf)中加入:
-I 16M
上文总结自:大头8086的简书博客,感谢作者。
memcached+SASL:更安全地访问memcached
memcached基于c/s架构的缓存系统,由于其默认不开启认证机制,导致客户端无需认证即可读取、修改缓存内容。在1.4.3版本加入了对SASL的支持,所以要使用认证功能的话需要将memcached升级到1.4.3以上的版本。
SASL全称Simple Authentication and Security Layer(简单认证与安全层),是一种用来扩充C/S模式验证能力的机制,SASL只是认证过程,将应用层与系统认证机制整合起来,SASL本身不能进行认证。SASL支持的认证方法为:getpwent kerberos5 pam rimap shadow ldap httpform。
在编译时增加相关选项后进行编译:--enable-sasl,如果要使用客户端,则客户端需要支持SASL。
为memcached启用Unix Domain Socket连接方式
memcached为高性能缓存系统,增加认证无疑会带来一定的性能损耗,所以在使用的时候要权衡利弊。一般情况下,memcached需要部署在信任的网络中,如果无法启用SASL,可使用主机防火墙(iptalbes、firewalld等)和网络防火墙对memcached服务端口进行过滤;或改为本地(127.0.0.1)监听或unix domain socket,但只能在本机使用了,据说 unix sockets 比 tcp sockets 的方式快33%。
memcached的FAQ中也提到为了安全验证,可以考虑让memcached监听unix domain socket。可以通过-s选项指定unix domain socket的路径名来支持这一功能,注意:为了可移植性,尽量使用绝对路径,因为Posix标准声称给unix domain socket绑定相对路径将导致不可预计的后果,在linux的测试是可以使用相对路径。
端口呢?没有端口了。socket文件你可以理解成FIFO的管道,unix domain socket的server/client通过这个管道进行通讯。
libmemcached支持通过unix domain socket来访问memcached,基于libmemcached实现的client应该都可以使用这一功能。目前来看,java平台由于不支持平台相关的unix domain socket,因此无法享受memcached的这一特性。不过有一个开源项目通过jni支持实现了unix domain socket,这个项目称为juds。核心类就三个,使用非常简单。下载文件后,解压缩,make & make install即可。注意,Makefile中写死了JAVA_HOME,手工修改即可。juds还只支持阻塞IO,考虑可进一步使用select、poll来扩展实现非阻塞IO。
memcached开启socket方式不成功的原因总结:
开启socket方式意味着就要关闭网络连接(本地的127地址或其它ip地址)的方式(tcp or udp),且在权限的设定上还有一定的要求。socket方式将会在本地生成为个socket类型的文件,应用程序通过该文件与memcached进行数据的交互,由于是文件,就涉及到用户与权限的问题。
将其配置文件中的'-l,-p,-U'这3个与网络连接相关配置参数注释,加上或修改'-s,-a'这两个参数。
# Set unix socket which we put in the folder /var/run/memcached and made memcache user the owner
#-s /tmp/memcached.sock
-s /var/run/memcached/mcached10.sock
# Set permissions for the memcached socket so memcache user and user or group can read or write or execute
#-a 0755
-a 0666
文件权限此时为:
srw-rw-rw- 1 memcache memcache 0 Sep 10 19:00 mcached10.sock
如果应用的用户与运行memcached的用户不同组的话,一定要保证文件权限的其它(other)位有'rw',不然不能对其进行写入操作,这点要注意。有人将socket文件放置到/tmp/目录下,也会有导致应用不能正常使用的情况,这多是因权限导致的;/var/run/memcached的属主及用户组均为其运行用户memcache,将其socket,pid,log文件放置于其下是一种比较推荐的方式,或将程序的运行用户加入到memcache这个组中。
如何测试
在网络环境下,memcached可以通过telnet方式登录进行简单的调试,那在socket方式有什么办法来简单的连接和操作执行呢?
Linux下有两个强大的网络操作类工具:
netcat 与 socat,下面收集其相关的用法,不过nc的测试没有像网上说的结果是成功的,socat试用下来是没有问题的。memcached工作于socket方式telnet自然是派不上用场了。
nc -U /var/run/memcached/mcached10.sock
'echo "stats" | nc local:/var/run/memcached/mcached10.sock'
echo "stats" | socat -v "UNIX-CONNECT:/var/run/memcached/mcached10.sock" STDIN
socat -v UNIX-CONNECT:/var/run/memcached/mcached10.sock STDIN
参考来源:
SASL Memcached Now Available