分布式基于内存的缓存服务Memcached
一: memcached介绍
memcached是一个分布式的基于内存的缓存服务器,我们一般用memcached来减轻数据库的负载,提高程序的响应速度。
memcahched采用key-value存储数据,将对象序列化成二进制,以便在网络中进行传输。
二: Linux安装
memcached是基于libevent库的,所以要先安装libevent,然后在安装memcached,主要的几步操作如下:
tar -zxvf memcached-1.4.25.tar.gz
cd
./configure
make
make install
安装好后,启动memcached服务 ./memcached -d -m 1024 -p 10000 -u root -P /tmp/memcached.pid
查看服务是否启动:ps -ef | grep 10000
三:memached的工作机制
3.1 memcached的内存存储
memcached是一个基于内存的高性能的缓存服务器,为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启服务器、重启操作系统等都会导致数据丢失。
memcached目前采用的是Slab Allocator来管理内存,它的原理非常简单,根据我们约定好的块大小,预先将分配的内存分成各个块,并将大小一样的块分成各个组。
当客户端有一个add或者set操作往缓存里存数据的时候,memcached根据添加的数据大小选择合适的某个slab。
1、memcached将内存空间分为一组slab
2、每个page里面包含一组chunk,chunk是真正存放数据的地方,同一个slab里面的chunk的大小是固定的。如果key不和chunk相 匹配,会有一定的内存浪费。但是不会存在内存碎片,我们可以通过在启动服务时-f来减低每个chunk之间的大小差,以便更合理的利用内存
3、相同大小chunk的slab被组织在一起,称为slab_class.
3.2 memcached的缓存过期
在往缓存里面添加数据的时候,可以指定一个expire,表示该数据的过期时间,单位是s,但是由于memecached不会释放已经分配的内存,当我们往缓存里面添加数据的时候,可能会内存空间不足,这个时候memecached就需要择优选择一块可用的内存来存储我们的数据。
memcached使用的是LRU的方式来释放内存,即最近最少使用的内存将优先被用来存新的数据。
memcached采用的是偷懒机制,当某个key过期后,并不会马上释放内存,而是等待下次有get请求到来时,如果发现该key已经过期了才会删除,而且其实memcached内部在很多情况下都会判断某个key是否失效,比如当我们重新set一个新的数据时,这个时候memcached需要重新申请一个item来存储咱们的数据,它会首先判断咱们请求的大小然后选择相应的slab放到里面,在这个地方,它在循环slab里面的item的时候实际上已经对每个item进行了判断是否过期,如果过期了,那么就直接使用这个item了。
3.3 memcahced如何实现分布式
一般的分布式系统,都是在服务器端实现的分布式,但是memcached却不是,由于各个memcached服务器之间不存在主备关系,也没有互相通信,所以memcached的分布式是在客户端实现的。
当我们缓存一个key,value的数据时,客户端首先根据一致性hash算法根据key来决定哪个服务器保存该数据。这个地方的原理很简单,将各个服务器节点的哈希值映射到一个圆上,然后将算出的key的哈希值也映射到圆上,然后顺时针查找第一台服务器,找到了就将该key对应的value存到这台服务器上,如果顺时针查找完还没有找到对应的服务器,则选择第一台服务器保存value值。
当我们下次有get请求来的时候,采用同样算法计算key对应的哈希值,就能找到存储该value的服务器了。
3.4 memcached的二阶段Hash
当我们往缓存里面写入一个数据的时候,memecached会首先根据key算出对应的hash值,找到对应的服务器编号,这是第一个hash,当我们确定好对应的服务器编号之后,memcached通过socket在memcached集群里面找到对应的memcached服务器,将我们的数据写入到服务器chunk中,这是第二个hash。同理,当我们get数据的时候,第一个hash采用相同的算法算出key对应的hash值,自然能找到对应的服务器获取缓存的数据
这个地方需要注意的是memecached采用的是一致性hash算法,而不是传统的余数hash。
一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射:下面是一个草图
根据各个服务器节点名称的Hash值将缓存服务器节点放置在这个Hash环上,然后根据需要缓存的数据的Key值计算得到其Hash值,然后在Hash环上顺时针查找这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射,然后保存value即可。
四:JAVA客户端简单操作实现
工具类:
package com.memcached.util;
import java.util.Date;
import java.util.Map;
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
/**
* memcached的基本操作类的封装
*
* @author tanjie
*
*/
public class MemcachedUtil {
private MemcachedUtil() {
}
private static MemCachedClient memCachedClient = new MemCachedClient();
private static MemcachedUtil memcachedUtil = new MemcachedUtil();
/**
* 设置与缓存服务器的连接池
*/
static {
String[] servers = { "192.168.8.88:10000" };// Ip地址和端口号
// 权重
Integer[] weights = { 3 };
// 获取socket连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
//设置连接池可用cache服务器的权重,和server数组的位置一一对应
pool.setWeights(weights);
// 设置初始连接数、最小、最大连接数以及最大处理时间
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// 设置TCP的参数,连接超时等
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
// 初始化连接池
pool.initialize();
// 设置序列化,因为java的基本类型不支持序列化,在确定cache的数据类型是string的情况下设为true,可以加快处理速度
memCachedClient.setPrimitiveAsString(true);
}
public Object get(String key) {
return memCachedClient.get(key);
}
public static MemcachedUtil getInstance() {
return memcachedUtil;
}
//如果key不存在,就增加,如果存在,就覆盖
public boolean add(String key, Object value) {
return memCachedClient.add(key, value);
}
public boolean add(String key, Object value, Date expiryDate) {
return memCachedClient.add(key, value, expiryDate);
}
//如果key不存在,报错
public boolean replace(String key, Object value) {
return memCachedClient.replace(key, value);
}
public boolean replace(String key, Object value, Date expiry) {
return memCachedClient.replace(key, value, expiry);
}
public boolean delete(String key){
return memCachedClient.delete(key);
}
public boolean delete(String key, Date expiry){
return memCachedClient.delete(key, expiry);
}
public boolean delete (String key, Integer hashCode, Date expiry){
return memCachedClient.delete(key, hashCode, expiry);
}
//如果key存在,则失败
public boolean set(String key,Object value){
return memCachedClient.set(key, value);
}
public boolean set(String key,Object value,Integer hashCode){
return memCachedClient.set(key, value, hashCode);
}
//cache计数
public boolean storeCounter(String key,long value){
return memCachedClient.storeCounter(key, value);
}
public long incr(String key,long value){
return memCachedClient.incr(key, value);
}
//根据多个key获取对象
public Map<String,Object> getObjects(String[] keys){
return memCachedClient.getMulti(keys);
}
//清空对象
public boolean flush(){
return memCachedClient.flushAll();
}
//清空缓存对象,servers表示批量清空哪些机器的缓存
public boolean flushBaseTime(String[] servers){
return memCachedClient.flushAll(servers);
}
//获取服务器状态
public Map<String,Map<String,String>> getStats(){
return memCachedClient.stats();
}
//获取各个slab里item的数量
public Map<String,Map<String,String>> getStatsItem(){
return memCachedClient.statsItems();
}
}
实体类:
package com.memcached.vo;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String userName;
private String adder;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAdder() {
return adder;
}
public void setAdder(String adder) {
this.adder = adder;
}
}
分别运行后结果如下:
test():
从缓存中获取数据:memcached
123
张三
重庆
testDelete():
从缓存中获取数据:memcached
再次从缓存中获取数据:null
testReplace():
从缓存中获取数据:memcached
第一次从缓存里面获取user:123
再次从缓存中获取数据:replaceId
testGetStats():
ip:10000
key:items:4:evicted_nonzero,value:0
key:items:1:evicted_unfetched,value:0
key:items:1:evicted_time,value:0
key:items:1:expired_unfetched,value:0
key:items:4:tailrepairs,value:0
key:items:1:number,value:1
key:items:4:crawler_items_checked,value:0
key:items:1:lrutail_reflocked,value:0
key:items:1:reclaimed,value:0
key:items:4:crawler_reclaimed,value:0
key:items:1:evicted_nonzero,value:0
key:items:4:outofmemory,value:0
key:items:4:age,value:0
key:items:4:evicted,value:0
key:items:1:evicted,value:0
key:items:4:lrutail_reflocked,value:0
key:items:1:tailrepairs,value:0
key:items:4:evicted_time,value:0
key:items:1:age,value:0
key:items:1:crawler_reclaimed,value:0
key:items:4:reclaimed,value:0
key:items:1:outofmemory,value:0
key:items:1:crawler_items_checked,value:0
key:items:4:expired_unfetched,value:0
key:items:4:evicted_unfetched,value:0
key:items:4:number,value:1
其实memcached客户端的方法都大同小异,只是各个客户端所特有的特性不一样罢了,这就看大家在实际生产中的需求了。
五:memcached的缺点
经过上面的分析,相信大家对memcached已经有了初步的认识,memcached很快,性能足以满足大多数应用,但是它也有很多需要改进的地方:
1:memcache没有身份认证机制,所以任何机器都可以通过 telnet 等方式连接到缓存服务器。
2:没有合理的日志功能,一旦服务器遇到错误而崩溃,将难以找到错误原因。
3:内存中的数据不够安全,服务器停电或者其他因素将会造成数据的丢失。
4.slab 处理对象时,会先对其归类,比如 100KB 的对象会放到 120KB 的空间内,会浪费较多的内存空间。
Memcached 的详细介绍:请点这里
Memcached 的下载地址:请点这里