Memcached最初是为LiveJournal服务的,是为了缓解数据库压力而构建的一个分布式内存缓存系统。
在日常生活中,我们访问的网站的所有数据基本上都是保存在数据库中的,频繁的获取数据库的数据会让数据库的性能降低,无法同时服务更多的用户。
大家都知道,当有一个request过来后,web服务器并从db中存取相关数据,但db存取的花费是相当高昂的。特别是每次都取相同的数据,等于是让数据库每次都在做高耗费的无用功,数据库如果会说话,肯定会发牢骚,你都问了这么多遍了,难道还记不住吗?是啊,如果拿到第一次数据并存到内存里,下次读取时直接从内存里读取,而不用麻烦数据库,这样不就给数据库减负了?而且从内存取数据必然要比从数据库媒介取快很多倍,反而提升了应用程序的性能。
因此,我们可以在web与db层之间加一层cache层,主要目的:1.减少数据库读取负担;2.提高数据读取速度。而且,cache存取的媒介是内存,而一台服务器的内存容量一般都是有限制的,不像硬盘容量可以做到TB级别。所以,可以考虑采用分布式的cache层,这样更易于破除内存容量的限制,同时又增加了灵活性。
特点
命令
Memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。因此,通过telnet也能在Memcached上保存数据、取得数据。
set命令在memcached使用频率极高。
格式如下:set <key><flags><exptime><bytes> value
--<key>是客户端所要求存储的数据的键值
--<flags>可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息
--<exptime>在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
--<bytes>在缓存中存储的字节数
--<value>存储的值(始终位于第二行)
set name 0 0 4
nasa
STORED //正确
get name
VALUE name 0 4
nasa
END
set name 0 0 5
nasa
CLIENT_ERROR bad data chunk //长度错误
add name 0 0 4
qwer
NOT_STORED //不可以
添加已存在的key
add gong 0 0 4
yuan
STORED //添加成功
replace name 0 0 4
NASA
STORED //已存在的key才可以替换
replace x 0 0 4
NASA
NOT_STORED
delete name
DELETED //删除成功
flush_all
OK //清空数据
内存存储模式
如果需要申请内存时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在重启前不会被回收或者重新分配,以解决内存碎片问题。 page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。 chunk:用于缓存记录的内存空间。 slab申请内存时以page为单位,所以在放入第一个数据,无论大小为多少,都会有1M大小的page被分配给该slab。申请到 page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组。
Slab Class 特定大小的chunk的组。 Memcached并不是将所有大小的数据都放在一起的,而是预先将数据空间划分为一系列slabs,每个slab只负责一定范围内的数据存储。memcached根据收到的数据的大小,选择最适合数据大小的slab。memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存于其中。 为一个item分配存储空间的时候,具体的操作是这样的: 1、首先,计算该item占用的空间大小,只有知道了它的大小,才能知道它需要存储在哪个桶中。一个item的大小包括它的item结构体大小部分、名字长度部分、状态标识部分、内容大小部分等的总和。 2、然后寻找合适的slab用于存储,这一部分主要是比较item 和各slab桶的大小,寻找最合适的slab。
缺点
eg:将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。
引入memcached时,最好重新计算一下数据的预期平均长度,调整growth factor,以获得最佳的设置。 为了避免使用Memcached时出现异常, 使用Memcached的项目需要注意: 1. 不能往Memcached存储一个大于1MB的数据. 2. 往Memcached存储的所有数据,如果数据的大小分布于各种chunk大小区间,从64B到1MB都有,可能会造成内存的极大浪费以及Memcached的异常. Memcached最大可申请内存为2M, 你第一次存储一个10B的数据,那么Memcached会申请1MB的内存,以64B进行分割然后存储该数据, 第二次存储一个90B的数据,那么Memcached会继续申请1M的内存,以128B进行分割然后存储该数据, 第三次如果你想存储一个150B的数据, 如果可以继续申请内存, Memcached会申请1M内存以256B的大小进行分割, 但是由于最大可申请仅仅为2MB,所以会导致该数据无法存储.
缓存策略
1.LRU + 到期失效 :
- 优先使用已超时的记录的空间
- 从最近未被使用的记录中搜索,并将其空间分配给新的记录
2.Lazy Expiration :
- 内部不会监视记录是否过期,在get时查看记录的时间戳,检查记录是否过期
工作原理
- Memcached是以守护进程的方式运行在服务器端。
- 各个Memcached服务器之间互不通信,各自独立存取数据,不共享任何信息。服务器并不具有分布式功能,分布式部署取决于memcache客户端。
Memcached的分布式
Memcached的分布式Memcached的分布式是由客户端程序库实现的。 存取对象的时候,每个对象都又一个唯一的标识符key,存取都通过这个key来进行,保存到memcached中的对象实际上都是放置在内存中的。
1.存储数据的时候,key的值通过hash进行转换,根据hash值来把value传递到对应的server上。
2.获取对象数据时,根据key进行。首先对key进行hash,通过获得的值可以确保它被存放在哪台server上,然后在向该server发出请求。首先对key进行hash,通过获得的值可以确定他被保存在了哪台server上,然后再向该server发出请求。(client端只需要知道保存hash(key)的值在哪台服务器上就可以了)
两层哈希:
第一层在客户端库中实现;它将键散列到一个虚拟bucket列表中,每个bucket代表一个Memcached服务器,从而决定将请求发送给哪个Memcached服务器。
一旦完成,所选的Memcached服务器将使用一个典型的散列表。
余数计算分散法
余数计算分散简单,数据发散较好 几乎所有的缓存都会失效,缓存重组的代价非常大.(红色部分代表miss)
一致性hash算法
将server的hash值分配至0~2^32的圆环上, 用同样的方法求出存储数值键的hash值并映射到圆上. 然后从数据映射到的位置开始顺时针查找, 将数据存放至找到的第一台服务器上. 如果超过0~2^32还找不到, 则将数据存放至第一台服务器.
新添/删除server时, 只在圆上增加服务器的逆时针方向的第一台服务器上的键会受到影响。
优化一致性hash算法
- 一般的一致性hash算法最大限度的抑制了键的重新分配
- 服务器的映射地点的分布非常的不均匀, 导致数据访问倾斜, 大量的key被映射到同一台服务器上
- 虚拟节点: 每台机器计算出多个hash值, 每个值对应环上的一个节点位置
- key的映射方式不变, 就多了层从虚拟节点到再映射到物理机的过程
优化后:在物理机很少的情况下, 只要虚拟节点足够多, 也能使的key分布相对均匀. 提升了算法的平衡性.
与Redis对比
不同点 | Memcached | Redis |
数据结构 | 支持简单的K-V | 支持string,hash,list,set, sorted set |
线程 | 多线程 | 单线程 |
持久化 | 不可持久化 | 可持久化 |
分布式 | 通过客户端hash来实现分布式 | 有主从结构 |
虚拟内存 | LRU | 将一些很久没用的数据刷到磁盘 |
Memcached实验性能
- 实验一:3758条数据,单线程每次去Memcached随机获取一条数据(每条数据是1K)。
- 实验二:10000000条数据,单线程每次去Memcached随机获取一条数据(每条数据是1K) 。
实验一:
网络 | 远程/本地访问Memcached | get时间/ms |
1G | 远程 | 0.442 |
1G | 本地 | 0.083 |
10G | 远程 | 0.139 |
10G | 本地 | 0.084 |
实验二:
网络 | 远程/本地访问Memcached | get时间/ms |
10G | 远程 | 0.036 |
10G | 本地 | 0.024 |
假设 远程访问时间 = 计算时间+网络通信时间,本地访问 = 计算时间 (无网络通信)
那么 : 远程访问时间-计算时间 = 网络通信时间
网络 | 实验 | 网络占比= 网络通信时间 / 远程时间 |
1G | 实验一 | 0.81 |
10G | 实验一
| 0.40 |
1G | 实验二
| - |
10G | 实验二
| 0.32 |
在增加计算复杂度时,Memcached的网络占比还是较大,可以说明Memcached的查询计算做的较好。
由于每次都是串行访问数据,网络带宽绰绰有余。所以网络没有发生一些竞争的现象,网络带来的占比应该是网络固有开销造成的,猜测应该是找到了网络的下界?
实验二测试了Memcached 的add和get操作所花时间:
网络 | 远程/本地访问Memcached | add时间/ms | get时间/ms
|
10G | 远程 | 0.053 | 0.036
|
10G | 本地 | 0.020 | 0.024 |
当Memcached放在远程(与客户端不在一个机器)时,get比set时间快0.39倍=>Memcached是一个适合多读少写的系统。