在Mac环境下安装memcache
没 brew 安装brew
brew安装完成后,执行以下命令:brew insatall memcached
1 | Updating Homebrew... |
- 启动停止命令:
1 | brew services start memcached |
- 安装客户端:brew install libmemcached
1 | ==> Downloading https://homebrew.bintray.com/bottles/libmemcached-1.0.18_1.sierr |
设置启动,守护线程 内存 ip端口:memcached -p 11211 -m 2048 -u root -d
测试连接:telnet localhost 11211
1 | Trying 127.0.0.1... |
- 按Ctrl + ] 会呼出telnet的命令行,出来telnet命令好之后就可以执行telnet命令
memcached的命令
分为 增删改查4类,沿着这个思路来学习
增:add往内存增加一行新纪录
1 | 语法:add key flag expire length |
用add时,如果内存中已经有这个键,就无法再使用add进行添加,但是可以使用replace进行替换/更改
key:给值取一个独特的名字
flag:标志,要求为一个正整数
expire:有效期
length:缓存的长度(字节为单位)
flag的意义:memcache基本文本协议,传输的东西,理解成字符串来存储
question:想存一个PHP对象和一个PHP数组怎么办?
answer:序列化成字符串,往外取得时候,自然还要反序列成对象/数组/json/格式等等,这是flag的意义就体现出来了
expire的意义:设置缓存的有效期,有3种格式
设置秒数,从设定开始数,第N秒后失效
时间戳,到指定事件戳后失效,(适用于团购活动倒计时等场景)
设置为0,不自动失效(不是永不失效,如服务器重启或者老数据被挤出,都会导致数据失效,即便什么都没发生,30天后也会失效)
删:删除内存中的一行记录
1 | 语法:delete key [time seconds] |
中括号里面的秒数是可选参数,加上之后是指,被删除的key,N秒内不能再用,目的是让网站上的页面缓存也代谢完毕
替换:替换内存中的一行记录
1 | 语法:replace key flag expire length |
参数与add完全一样,不再赘述
只有key值存在时,才能修改键值,
查询:查询一条记录
1 | 语法:get key |
返回key值
set 是设置和修改值:相当于有set和replace两者的功能
1 | 语法:set key flag expire length |
如果服务器无此键 ——> 增加的效果
如果服务器有此键 ——> 修改的效果
incr、decr命令:增加或减少值的大小(适用于抢购等场景)
1 | incr key num |
示例:incr age 2 //年龄增加2岁
需要注意的是,这两个命令是把值当做32位无符号来操作的,也就是说,值最小是0,不会出现负数
stats:统计命令
flush_all命令:清空所有存储对象
memcache内存分配机制
内存的碎片化?
如果用C语言直接malloc,free来向操作系统申请和释放内存时,在不断的申请和释放过程中,形成了一些很小的内存碎片,无法再利用,这种空闲,但无法利用内存的现象,称为内存的碎片化
memcache是如何缓解内存碎片化的?
memcache用slab allocator机制来管理内存
基本原理:预先把内存分成数个slab仓库,各仓库,切分成不同尺寸的小块,需要存入内容时,判断内容的大小,为其选择合理的仓库
需要注意的是,如果有100byte的内容要存,但122大小的仓库中的chunk满了,并不会寻找更大的仓库,如144的仓库来存储,而是把122仓库的旧数据踢掉。详见过期与删除机制
对于固定大小的chunk,也会有浪费,如何缓解?
对于特定的网站,可以长期统计网站内的缓存数据,根据自己网站的特点,设置chunk的大小
一般而言,观察缓数据大小的变化规律,设置合理的生长因子,grow factor默认是1.25倍,可以使用 -f num根据网站缓存的大小进行设置
memcache的过期数据删除机制
当某个值过期后,并没有从内存中删除,因此,stats统计时,curr_item仍有其信息
当某个新值去占用他的位置时,当成chunk来占用
当get值时,判断是否过期,如果过期,返回空,并且清空,curr_item就减少了
即这个过期数据只是让用户看不到而已,并没有在过期的瞬间立即从内存删除,这个称谓lazy expiration,惰性失效
如果chunk都满了,又有新的值要加入,要挤掉谁?
memcache此处用的是LRU删除机制
操作系统的内存管理:
FIFO:先进先出
LRU:最近最少使用:当某个单元被请求时,维护一个计数器,通过计数器来判断,最近谁最少被使用,就把谁踢出
memcache中的参数限制
key的长度:250字节,(二级制协议支持6535个字节)
value的限制:1M,一般都是存储一些文本,如新闻列表等等,这个值足够了
内存的限制:32位下最大设置到2G,64位基本不需要考虑
如果有30G数据要缓存,一般也不会单实例装30G,(不要把鸡蛋放在一个篮子),一般建议,开启多个实例(可以再不同的机器,或同台机器上的不同端口)
PHP连接memcache
下载memcached.dll,并放在php/ext目录下,下载是需要考虑3个条件(在phpinfo()中查看),PHP版本/ts或nts/vc6还是vc9
观察正确的目录和配置文件路径,并观察extension_dir的路径,先运行phpinfo(),确认真正使用的php.ini是哪一个
把dll放入extension目录,并修改php.ini,引入dll
一致性哈希分布式
1. 余数Hash
假设现在通过相关计算,可以算得某字符串str的hashcode,同时现在又有3台服务器,利用余数hash便可以算得该字符串应该存储在哪台服务器上
1 | 0 % 3 ~ 0 |
HashCode | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
路由到的服务器 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 |
假设我现在将服务器扩充到了4台,就会出现如下变化
HashCode | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
路由到的服务器 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 |
数据由原来100%的命中率,下降为30%,这个结果显然是无法接受的,在网站业务中,大部分的业务数据的操作请求上事实上是通过缓存获取的,只有少量读操作会访问数据库,因此数据库的负载能力是以有缓存为前提而设计的。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这些数据访问的压力就落在了数据库的身上,这将大大超过数据库的负载能力,严重的可能会导致数据库宕机。
- 这个问题有解决方案,解决步骤为:
在网站访问量低谷,通常是深夜,技术团队加班,扩容、重启服务器
通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布
1. 一致性Hash
一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射,看一下我自己画的一张图:
具体算法过程为:先构造一个长度为2^32^的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2^32^-1])将缓存服务器节点放置在这个Hash环上,然后根据需要缓存的数据的Key值计算得到其Hash值(其分布也为[0, 2^32^-1]),然后在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
就如同图上所示,三个Node点分别位于Hash环上的三个位置,然后Key值根据其HashCode,在Hash环上有一个固定位置,位置固定下之后,Key就会顺时针去寻找离它最近的一个Node,把数据存储在这个Node的MemCache服务器中。使用Hash环如果加了一个节点会怎么样,看一下:
看到我加了一个Node4节点,只影响到了一个Key值的数据,本来这个Key值应该是在Node1服务器上的,现在要去Node4了。采用一致性Hash算法,的确也会影响到整个集群,但是影响的只是加粗的那一段而已,相比余数Hash算法影响了远超一半的影响率,这种影响要小得多。更重要的是,集群中缓存服务器节点越多,增加节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。
至于具体应用,这个长度为2^32^的一致性Hash环通常使用二叉查找树实现,至于二叉查找树,就是算法的问题了,可以自己去查询相关资料。