Memcached 是一款老牌的、经典的高性能内存kv缓存服务器。
Free & open source, high-performance, distributed memory object caching system.
http://www.memcached.org
安装
RHEL 发行光盘自带,如果是编译安装,依赖 libevent 的支持。
# yum install -y memcached
配置与启动
# systemctl restart memcached
这样会使用默认的配置文件 /etc/sysconfig/memcached 来启动。
# cat /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""
也可以直接使用其二进制文件来启动:
# /usr/bin/memcached -p 11211 -u memcached -c 1024 -m 64 -vv
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 120 perslab 8738
slab class 3: chunk size 152 perslab 6898
slab class 4: chunk size 192 perslab 5461
slab class 5: chunk size 240 perslab 4369
slab class 6: chunk size 304 perslab 3449
slab class 7: chunk size 384 perslab 2730
slab class 8: chunk size 480 perslab 2184
slab class 9: chunk size 600 perslab 1747
slab class 10: chunk size 752 perslab 1394
slab class 11: chunk size 944 perslab 1110
slab class 12: chunk size 1184 perslab 885
slab class 13: chunk size 1480 perslab 708
slab class 14: chunk size 1856 perslab 564
slab class 15: chunk size 2320 perslab 451
连接 memcached
# telnet 192.168.111.128 11211
常用命令
add/set/replace
语法:
<command> <key> <flags> <expiration_time> <bytes>
value
- flags 为标志,有点儿类似 key 的下标
- expiration_time 为失效期,单位为秒,0表示不自动过期(并不是说永不过期,可能会被新数据挤掉),也可以指定一个绝对时间点到点过期(例如限时抢购),格式同 stats 命令 的 STAT time 时间格式。
- bytes 为缓存的长度(单位为字节)
注意:
- 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。如果缓存中已经存在键,则之前的值将仍然保持相同,并且您将获得响应 NOT_STORED。
- 仅当键已经存在时,replace 命令才会替换缓存中的键。如果缓存中不存在键,那么您将从 memcached 服务器接受到一条 NOT_STORED 响应。
- set 命令用于向缓存添加新的键值对。如果键已经存在,则之前的值将被替换。
示例:
set weather 0 0 6
cloudy <-- 输入值,长度要跟上面定义的6对应
STORED
get weather
VALUE weather 0 6
cloudy
END
delete weather
DELETED
incr/decr
示例:
set age 0 0 2
25
STORED
incr age 1
26
incr age 1
27
decr age 1
26
注意:incr/decr 操作是把值理解为32位无符号整数来操作的。
应用场景:点赞关注等。
stats
查看统计信息,可以利用之来计算命中率。
flush_all
清空缓存(但不清零统计信息)。
内存分配原理
- Memcached 的内存分配方法被称为 Slab Allocation。
- 其原理相当简单,先将分配给 Memcached 的内存分割成大小大致相当的几个大块(slab classes)。
- 然后将各个 slab classes 分成各种尺寸的块(即 chunk,每个 slab class 中的 chunk 的大小一致)。
当分配内存时,去最合适的 slab class 中申请 chunk。
- 比如,chunk size 有 96bytes, 120bytes, 152bytes 等,如果现在需要 140bytes,则去 152bytes 的 slab class 中分配一个 chunk(该 chunk 剩余的 12bytes 无法利用)。
- Slab Allocation 可以缓解内存碎片问题,但是不能完全避免。
- 如果需要的 chunk 已经用完,并不会寻找更大的,而是把旧数据踢掉。
注意观察上面的 chunk size 的递增,下一级是上一级的 1.25 倍,这个被称为生长因子(比例因子)。
- 比例因子默认值是 1.25,可以通过 -f 参数来指定,但是不宜设置得过大,过大容易造成浪费。
- 一般而言,观察缓存数据大小的变化规律(例如使用 web 界面管理工具 memadmin 查看),设置合理的生长因子。
过期数据清理机制(lary expiration)
1.当某个值过期后,并没有从内存清除。因此,stats 命令统计时,curr_item 有其信息
2.当某个新值去占用他的位置时,当成空 chuk 来占用
3.当 get 值时,如果过期,返回空,并且清空,curr_item 就减少了
即:所谓的过期,只是让用户看不到这个数据而已,并没有真正清除。这个称为 lary expiration(惰性失效),好处是节省了CPU时间和检测成本。
删除机制(LRU)与永久数据被踢现象
如果 slab class 中的 chunk 用完了,又有新的值要加入,挤掉谁?
memcached 使用 LRU 淘汰机制。其原理是,每个 chunk 通过一个计数器来判断最近谁最少被使用,谁就被T出。
注意,即使某个key是设置的永久有效也一样会被T出来!即老数据被踢现象(永久数据被踢现象)。网上有人反馈为“memcached数据丢失”:明明设为永久有效,却莫名其妙的丢失了。
memcached 中的一些参数限制
Key的长度:250字节(二进制协议支持65536个字节)
Value的限制:1M,一般都是存储一些文本,如新闻列表等等,这个值足够了
内存的限制:32位下最大设置到2G。
如果有大量数据要缓存,一般建议开启多个实例(技术上可以在不同的机器,或同台机器上的不同端口)。
键与服务器的关系
当有多台缓存服务器时,键放到哪台服务器呢?我们可以在应用代码中通过不同的算法来决定。
分布式算法之取模算法
将key转换成数字后,对N取模(N是服务器台数)。
弊端:
假设有8台服务器,运行良好,根据取模算法,各服务器中保存有不同的key,例如,某key转换为数字后为9,保存在第2台服务器中。
现有一台服务器当机,则求余的底数变成7。当去取该key时,会到第3台服务器中去取,未命中,导致缓存命中率下降。但实际情况是该key是缓存在服务器中的。
后果:
Key0%8=0,key0%7=0 hits
Key9%8=1,key9%7=2 miss
DOWN 的服务器越多,情况越糟糕。
一致性 Hash 算法
Consistent Hashing 原理如上图所示:首先求出memcached服务器(节点)的哈希值,并将其配置到0~2^32的圈(continuum)上。
然后用同样的方法求出存储数据的键的哈希值,并映射到圈上。
然后从key映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。
如果超过2^32仍然找不到服务器,就会保存到第一台memcached服务器上。
这样一来,Down 掉某个节点,只会影响该节点与前上一个节点之间的数据。这些数据会迁移至下一个节点(如此会导致下一节点压力爆增且数据分布不均匀)。
为了解决节点故障后导致的下一节点压力变大和数据不均匀问题,引入了虚拟节点的概念。
比如,有3台服务器 a,b,c 对就应的虚拟节点:
a: a1 a2 ... a64
b: b1 b2 ... b64
c: c1 c2 ... c64
如上图所示,红、绿、蓝分别代表三个节点,各条线就代表多个虚拟节点(即算法上的很多节点其实只有3个真实节点)。
当蓝色节点 Down 掉后,橙色广块代表的那部分数据迁移到绿色节点,紫红广块代表的那部分数据迁移到红色节点……,如此,就达到了故障节点数据均匀分布到其它节点的目的。
缓存雪消现象
一般是由某个节点失效导致其他节点的缓存命中率下降,缓存中缺失的数据去数据库查询,短时间内造成数据库服务器崩溃。
重启DB,缓存服务器中的数据也多了一些,但 DB 短期又被压跨,反复多次启动后缓存重建完毕,D8才稳定运行。
或者是由于缓存周期性的失效,比如每6小时失效一次,那么每6小时,将有一个请求峰值,严重者甚至会令DB崩溃。
解决办法:
1)把缓存时间调长使其夜里低峰时段一起生效,后面随着客户的访问会再逐渐把数据读入到缓存
2)把缓存设置为随机3到9小时的生命周期这样不同时失效,把工作分担到各个时间点上去
缓存的无底现象
facebook 的工作人员反应的,facebook 在2010年左右,memcached 节点就已经达3000个。存储数千G的缓存。
他们发现了一个问题:memcached 连接频繁,效率下降了,于是增加 memcached 节点。添加了后,发现因为连接频率导致的问题,仍然存在并没有好转,称之为“缓存无底洞现象”。
问题分析:
以用户为例;user-133-age,user-133-name,user-133-height…N个key,当服务器增多,133号用户的信息,也被散落在更多的节点。
所人同样是访问个人主页,得到相同的个人信息,节点越多,要连接的节点也越多。对于 memcached 的连接数,并没有随着节点的增多而降低,问题出现。
问题解决方案:
把某一组key按其共同前缀来分布。
比如 user-133-age,uer-133-nmme,user-133-heigt 这3个key,在用分布式算法求其节点时应以“user-133”来计算,而不是以user-133-age/nmme/height来计算。这样,3个关于个人信息的key都落在同1个节点上,访问个人主页时,只需要连接1个节点, 问题解决。
事实上,NosQL 和传统的 RDBAMS,并不是水火不容,两者在某些设计上,是可以相互参考的。对于 memcached 这种存储 key 的设计,可以参考 MySQL 中表列的设计,比如;user 表下有 age列,name 列,height 列,对应的 key 可以用 user:133:age=23,user:133:name='lisi',user:133:height=168。