共计 7268 个字符,预计需要花费 19 分钟才能阅读完成。
一: 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 的空间内,会浪费较多的内存空间。
CentOS 6.6 下 Memcached 源码安装配置 http://www.linuxidc.com/Linux/2015-09/123019.htm
Memcached 安装及启动脚本 http://www.linuxidc.com/Linux/2013-07/87641.htm
PHP 中使用 Memcached 的性能问题 http://www.linuxidc.com/Linux/2013-06/85883.htm
Ubuntu 下安装 Memcached 及命令解释 http://www.linuxidc.com/Linux/2013-06/85832.htm
Memcached 的安装和应用 http://www.linuxidc.com/Linux/2013-08/89165.htm
使用 Nginx+Memcached 的小图片存储方案 http://www.linuxidc.com/Linux/2013-11/92390.htm
Memcached 使用入门 http://www.linuxidc.com/Linux/2011-12/49516p2.htm
Memcached 的详细介绍 :请点这里
Memcached 的下载地址 :请点这里
本文永久更新链接地址 :http://www.linuxidc.com/Linux/2016-05/131489.htm