1.基本概念
基于高性能的key-value的内存数据库。单进程多线程,协议简单,使用文本行的协议,支持数据类型简单,不支持持久化,轻量级锁CAS机制,集群互不通信,缓存策略(LRU,FIFO,LFU)只支持LRU
2.与redis对比
redis: 线程模型-单进程单线程,QPS/TPS-10w+/10w-,数据支持类型-丰富,持久化-支持, 支持数据备份-支持, 能否集群-能,数据一致性-支持事务, list排序支持-支持, 缓存策略-LRU,FIFO,LFU,jcache支持-支持,单条数据约束-不同数据结构有不同约束
memcached: 线程模型-单进程多线程,QPS/TPS-10w+/10w-,数据支持类型-简单,持久化-不支持,支持数据备份-不支持,能否集群-能,数据一致性-轻量级CAS机制,list排序支持-不支持,缓存策略-LRU, jcache支持-支持,单条数据约束-默认值:key最大长度250字符,value容量<=1M
3.安装及使用
参考:http://www.runoob.com/memcached/memcached-install.html
wget http://memcached.org/latest
tar -zxvf memcached-1.x.x.tar.gz
cd memcached-1.x.x
./configure && make && make test && sudo make install
启动:./memcached -p 11211 -d -c 1024 -u root (-p 表示端口,-d 表示后台运行, -u 指定用户)
停止:ps aux|grep memcached 查到进程id,然后 kill -9 pid
客户端:没有专门的客户端,使用telnet ip port
集群:在同一台机器上可以通过指定不同的端口来运行,如
./memcached -p 11211 -d -c 1024 -u root
./memcached -p 11212 -d -c 1024 -u root
./memcached -p 11213 -d -c 1024 -u root
4.memcached客户端使用
1.memcached-java-client
引入pom:
<!-- memcached-java-client -->
<!-- https://mvnrepository.com/artifact/com.whalin/Memcached-Java-Client -->
<dependency>
<groupId>com.whalin</groupId>
<artifactId>Memcached-Java-Client</artifactId>
<version>3.0.2</version>
</dependency>
详见:MemcachedJavaClientTest
2.spymemcached
引入pom:
<!--spymemcached-->
<!-- https://mvnrepository.com/artifact/net.spy/spymemcached -->
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
详见:MemcachedJavaClientTest
3.xmemcached
引入pom:
<!-- xmemcached -->
<!-- https://mvnrepository.com/artifact/com.googlecode.xmemcached/xmemcached -->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.5</version>
</dependency>
详见:MemcachedJavaClientTest
4.springcache与xmemcached 整合
引入pom:
<!-- xmemcached -->
<!-- https://mvnrepository.com/artifact/com.googlecode.xmemcached/xmemcached -->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.5</version>
</dependency>
<!-- spring cache 与 xmemcached 整合 采用simple-spring-memcached实现 -->
<dependency>
<groupId>com.google.code.simple-spring-memcached</groupId>
<artifactId>simple-spring-memcached</artifactId>
<version>2.0.0</version>
</dependency>
配置:applicationContext-cache.xml
详见:SpringCacheMemcachedTest
5.原理(重点理解)
5.1.hash算法
memcached单节点是有存储局限的,所以可以使用集群的方式。这里就有分布式存取的问题。不管是写入还是读取,肯定要通过某种算法,这种算法负责定位到某个固定的节点
1.余数分散:m = hash(key) mod n (其中key为传入的数据,n为机器的个数 ,将计算得到的m值 就是对应第几台机器,将key存入改机器中)
优点:算法简单,分散性好
缺点:当新增或者删除机器时,缓存重组的代价很大。当新增或删除机器时,余数会有很大的变化,这样就无法获取与保存时同样的机器,从而导致缓存的命中率很低
代码详见:HashTest
2.一致性hash算法
一致性hash算法就是为了解决余数分散 增加或删除机器时命中率低的问题
步骤:1.构建hash环 2.把数据映射到hash环 3.把机器节点映射到hash环 4.把数据映射到机器节点
优点:解决了余数分散算法增加或删除节点命中率大幅下降的问题
缺点:算法实现比较复杂,需要构建hash环
代码详见:ConsistentHashTest ConsistentHashNodeServiceImpl
3.一致性hash算法 + 虚拟节点
一致性hash算法有个缺点:数据分布不均,有倾斜性,即有些节点存的数据很多,有些则很少。
为了解决这个问题,于是有了增加虚拟节点的方案:
可以在原来缓存的机器的基础上,增加相应的虚拟节点,这样数据就会均匀的打散到其他节点中。这个时候客户端进行数据存取的时候找的就是虚拟节点,而不是真实的物理节点服务器,这样增加节点会删除节点虽然命中率会降低,却很好的解决了数据倾斜问题
代码详见:ConsistentHashTest ConsistentHashNodeServiceImpl2
5.2内存管理策略
Memcached采用Slab Allocator(Slab分配器)进行内存管理
内存被拆分成多个Slab class
每个Slab class 有多个Slab,每个Slab=1M
每个Slab下有多个大小相等的Chunk
不同Slab中的Chunk的大小不一样
数据存储在Chunk中
使用./memcached -p 11211 -d -u root -vv 可以观察内存分配情况
客户端Telnet使用stats slabs查看内存分布情况
增长因子:
启动时指定增长(growth factor)因子(-f 2),就可以在某种程度上控制slab之间的差异,默认值为1.25
如果增加-f 2参数即 ./memcached -p 11211 -f 2 -d -u root -vv 那么表示每一个slab的chunk size 都比上一个slab大了一倍
注意:真实项目中,要均衡数据的大小,如果数据之间大小差别不大建议增长因子设置比较小,而数据之间大小悬殊,数据的增长因子可以考虑增大
5.3缓存过期策略
MC不会主动删除过期数据,而是采用lazy策略
Memcached不会释放已分配的内存,其存储空间可以重复使用
Memcached内部不会监视数据是否过期,而是在get时查看数据的时间戳,查看数据是否过期。被称为lazy expiration(惰性过期)
Memcached内存空间不足,即无法从slab class中获取到新的空间时,就从最近未被使用的数据中搜索,将其空间分配给新的数据。(如果要禁用LRU,使用-M参数,超出会报错)
6.问题
1.什么是一致性hash,他和普通hash有什么不同(hash环,虚拟节点干什么的)?
一致性hash是为了解决普通hash算法(余数分散算法)因增加或删除节点导致命中率大幅下降的问题 而产生的
与普通hash不同点:
1.构建hash环 2.将数据映射到hash环上 3.将节点映射到hash环上 4.将数据映射到节点上
虚拟节点:是为了解决一致性hash中节点个数少导致数据分布不均问题而产生的,通过增加虚拟节点,可以使得数据均匀的打散到其他节点。
2.一致性hash在上面情况下使用,单节点情况下用一致性hash是否合适?
单节点不适用,因为使用一致性hash算法性能比较低,单节点使用普通的hash算法就行了
3.slab内存模型是什么样的,对于增长因子来说,他对性能有什么影响?
slab内存模型:
内存被拆分成多个slab class
每个slab class 有多个slab,每个slab=1M
每个slab下有多个大小相等的chunk
不同slab中chunk大小不一样
数据存储在chunk中
增长因子:
用于控制slab之间的差异。默认是1.25,可以根据实际情况,设置增长因子(在启动memcached时加参数 -f 设置)
如果数据之间的大小差别不大,就设置小点,如果大小差别很大,就可以设置大点
4.增长因子对性能影响:
如果数据间隔小,而设置很大的话,就浪费空间。
如果数据间隔大,而设置很小的话,那就存不了数据,也是浪费空间
如果增长因子设置过大,代表的每个slab里面chunk大小跨度很大。 但数据却很小,经常导致只有一部分chunk被使用 而且经常导致内存的清理,导致性能降低,如果数据跨度不大,可以考虑把增长因子设置得小一点的
5.一致性hash环的大小为什么是2^32
因为,java中int的最大值是2^31-1最小值是-2^31,2^32刚好是无符号整形的最大值;
为什么java中int的最大值是2^31-1最小值是-2^31
int的最大值最小值范围设定是因为一个int占4个字节,一个字节占8位,二进制中刚好是32位
hash环:
把二的三十二次方想象成一个圆,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1,我们把这个由2的32次方个点组成的圆环称为hash环
6.如果服务器计算出的hash值相同,那么数据不是被放在相同的服务器上了吗?
这个问题其实就是hash 碰撞,而在一致性hash里面hash碰撞的几率不大,理论上有hash碰撞的可能性,任何hash的有可能。
就像md5值,如果数据量足够大,足够长,算出的md5理论上可能重复,但并不影响它使用。 这是种出现概率极低的情况
7.每个slab大小是1M,为什么slab class有39个?
slab class有39个 只是显示它会这么分配,这个是由增长因子确定,但并没真的这么分配。具体的分配你应该在服务器运行的时候基于指定的内存分配,可以stats slabs命令查看
8.一致性hash算法应用场景:
memcached的客户端,redis中的hash slot(hash槽),nginx的负载均衡
hash一致性算法是为了在分布式环境下 动态增加或删除节点 而尽量减少对数据存取的影响即命中率低的问题 而产生,具体的算法在不同的应用场景中不太一样
9.缓存过期策略
FIFO(First In First Out):先见先出,淘汰最先近来的页面,新进来的页面最迟被淘汰,完全符合队列
LRU(least recently used):最近最少使用,淘汰最近不使用的页面
LFU(least frequently used):使用次数最少, 淘汰使用次数最少的页面
memcached 采用的是LRU