Memcache

在Mac环境下安装memcache

  1. 没 brew 安装brew

  2. brew安装完成后,执行以下命令:brew insatall memcached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Updating Homebrew...
==> Installing dependencies for memcached:libevent
==> Installing memcached dependency:libevent
==> Downloading https://homebrew.bintray.com/bottles/libevent-2.0.22.sierra.bott
######################################################################## 100.0%
==> Pouring libevent-2.0.22.sierra.bottle.1.tar.gz
�� /usr/local/Cellar/libevent/2.0.22: 734 files, 2.0M
==> Installing memcached
==> Downloading https://homebrew.bintray.com/bottles/memcached-1.4.33.sierra.bot
######################################################################## 100.0%
==> Pouring memcached-1.4.33.sierra.bottle.tar.gz
==> Caveats
To have launchd start memcached now and restart at login:
brew services start memcached
Or, if you don't want/need a background service you can just run:
/usr/local/opt/memcached/bin/memcached
==> Summary
�� /usr/local/Cellar/memcached/1.4.33: 11 files, 184.5K
  • 启动停止命令:
1
2
3
brew services start memcached

brew services stop memcached
  • 安装客户端:brew install libmemcached
1
2
3
4
==> Downloading https://homebrew.bintray.com/bottles/libmemcached-1.0.18_1.sierr
######################################################################## 100.0%
==> Pouring libmemcached-1.0.18_1.sierra.bottle.tar.gz
� /usr/local/Cellar/libmemcached/1.0.18_1: 230 files, 1.8M
  • 设置启动,守护线程 内存 ip端口:memcached -p 11211 -m 2048 -u root -d

  • 测试连接:telnet localhost 11211

1
2
3
Trying 127.0.0.1...
Connected to bogon.
Escape character is '^]'.
  • 按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种格式

  1. 设置秒数,从设定开始数,第N秒后失效

  2. 时间戳,到指定事件戳后失效,(适用于团购活动倒计时等场景)

  3. 设置为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
2
3
incr key num

decr key num

示例:incr age 2 //年龄增加2岁
需要注意的是,这两个命令是把值当做32位无符号来操作的,也就是说,值最小是0,不会出现负数

stats:统计命令

flush_all命令:清空所有存储对象

memcache内存分配机制

内存的碎片化?

如果用C语言直接malloc,free来向操作系统申请和释放内存时,在不断的申请和释放过程中,形成了一些很小的内存碎片,无法再利用,这种空闲,但无法利用内存的现象,称为内存的碎片化

memcache是如何缓解内存碎片化的?

memcache用slab allocator机制来管理内存

基本原理:预先把内存分成数个slab仓库,各仓库,切分成不同尺寸的小块,需要存入内容时,判断内容的大小,为其选择合理的仓库

image
image

需要注意的是,如果有100byte的内容要存,但122大小的仓库中的chunk满了,并不会寻找更大的仓库,如144的仓库来存储,而是把122仓库的旧数据踢掉。详见过期与删除机制

对于固定大小的chunk,也会有浪费,如何缓解?

对于特定的网站,可以长期统计网站内的缓存数据,根据自己网站的特点,设置chunk的大小

一般而言,观察缓数据大小的变化规律,设置合理的生长因子,grow factor默认是1.25倍,可以使用 -f num根据网站缓存的大小进行设置

memcache的过期数据删除机制

  1. 当某个值过期后,并没有从内存中删除,因此,stats统计时,curr_item仍有其信息

  2. 当某个新值去占用他的位置时,当成chunk来占用

  3. 当get值时,判断是否过期,如果过期,返回空,并且清空,curr_item就减少了

即这个过期数据只是让用户看不到而已,并没有在过期的瞬间立即从内存删除,这个称谓lazy expiration,惰性失效

如果chunk都满了,又有新的值要加入,要挤掉谁?

memcache此处用的是LRU删除机制

操作系统的内存管理:

FIFO:先进先出

LRU:最近最少使用:当某个单元被请求时,维护一个计数器,通过计数器来判断,最近谁最少被使用,就把谁踢出

memcache中的参数限制

  • key的长度:250字节,(二级制协议支持6535个字节)

  • value的限制:1M,一般都是存储一些文本,如新闻列表等等,这个值足够了

  • 内存的限制:32位下最大设置到2G,64位基本不需要考虑

如果有30G数据要缓存,一般也不会单实例装30G,(不要把鸡蛋放在一个篮子),一般建议,开启多个实例(可以再不同的机器,或同台机器上的不同端口)

PHP连接memcache

  1. 下载memcached.dll,并放在php/ext目录下,下载是需要考虑3个条件(在phpinfo()中查看),PHP版本/ts或nts/vc6还是vc9

  2. 观察正确的目录和配置文件路径,并观察extension_dir的路径,先运行phpinfo(),确认真正使用的php.ini是哪一个

  3. 把dll放入extension目录,并修改php.ini,引入dll

一致性哈希分布式

1. 余数Hash

假设现在通过相关计算,可以算得某字符串str的hashcode,同时现在又有3台服务器,利用余数hash便可以算得该字符串应该存储在哪台服务器上

1
2
3
4
5
6
0 % 3 ~ 0
1 % 3 ~ 1
2 % 3 ~ 2
3 % 3 ~ 0
4 % 3 ~ 1
5 % 3 ~ 2
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. 在网站访问量低谷,通常是深夜,技术团队加班,扩容、重启服务器

  2. 通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布

1. 一致性Hash

一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射,看一下我自己画的一张图:

image

具体算法过程为:先构造一个长度为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环如果加了一个节点会怎么样,看一下:

image

看到我加了一个Node4节点,只影响到了一个Key值的数据,本来这个Key值应该是在Node1服务器上的,现在要去Node4了。采用一致性Hash算法,的确也会影响到整个集群,但是影响的只是加粗的那一段而已,相比余数Hash算法影响了远超一半的影响率,这种影响要小得多。更重要的是,集群中缓存服务器节点越多,增加节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。

至于具体应用,这个长度为2^32^的一致性Hash环通常使用二叉查找树实现,至于二叉查找树,就是算法的问题了,可以自己去查询相关资料。