1.Memcached是什么
Memcached是一个C语言编写的缓存系统,为了减少Web应用直接跟数据库交互,将一些经常访问的对象以K/V对的形式存放在内存中。缓存是一种加速应用速度的优化手段,核心思想是用对内存的读取换取直接对数据库的操作,因为内存读取速度高于磁盘读取速度。
Memcached是通过C语言编写的,使用libevent框架实现底层的TCP/IP通信,只要支持Socket操作的语言都可以编写自己的客户端,与Memcached Server端进行通信,从而使用Memcached提供的功能。
Memcached支持文本协议和二进制协议2种通信方式,二进制协议是从1.3版本引进的,为了提高数据通信的效率。
2.Memcached服务器端
2.1 安装
2.1.1安装libevent
(1)使用wget 指令下载libevent,libevent是一个开源的Socket库,memcached依赖了libevent
wget https://github.com/downloads/libevent/libevent/libevent-2.0.20-stable.tar.gz
(2)刚下载下来的文件只有444的权限,需要手动修改权限
chmod 777 libevent-2.0.20-stable.tar.gz
(3)安装libevent
解压tar axvf libevent-2.0.20-stable.tar.gz
进入解压之后的文件夹
./comfigure --prefix=/usr
make install
(4)验证
在 /usr/lib目录下出现
libevent-2.0.so-*
libevent_so-2.0.so-*等文件
通过指令 ll /usr/lib | grep libevent可以查看
2.1.2安装memcached
(1)下载
wget http://memcached.googlecode.com/files/memcached-1.4.14.tar.gz
(2)安装
tar zxcf memcached.googlecode.com/files/memcached-1.4.14.tar.gz
在解压目录下
./configure --with-libevent=usr
make install
(3)验证安装
在/usr/local/bin 目录下 恩纳个找到 可执行文件 memcached
这是memcached的启动文件
(4)启动memcached
memcached -m 64 -p 11211 -u root
(5)验证启动
打开新的shell
telnet localhost 11211
11211 是 刚才启动时指定的端口号
如果看到如下字样,表示已经启动成功
Trying 127.0.0.1...
Connected to localhost...
Escape cahracter is '^]'
2.2 配置
2.2.1 基本参数
通过 memcached -h 或者 man memcached 命令可以看到memcached命令的启动参数详细信息,很多参数都有缺省值。
-m | 告诉memcached使用多大的RAM来进行存储,单位是MB。这个数字并不是严格意义上的最大值,memcached可能会在此之上增加一些内存使用 |
-d | 告诉mecached以守护进程的方式启动 如果通过init script的方式启动memcached,可以忽略这个参数 第一次启动时可以忽略这个参数 |
-v | 告诉memcached运行时,打印标准输出STDOUT/STDERR 一个v 只打印启动信息 多个v 可以打印出来缓存命中的情况 在测试环境下,在前台打印输出信息是个不错的选择 |
-p | 指定启动实例的端口号,默认11211
实际的应用中,使用分布式Memcached的时候,应用每台PC部署一个实例,但是在学习测试环境下,可以在同一台PC上开启多个实例,只要端口号不冲突就可以
|
2.2.2 连接数限制
最大并发连接数的缺省值是1024,大于该值的连接会被挂起,直到有空缺为之。
通过stats指令查看listen_disabled_num的值,该值应该等于0或者接近0
2.2.3 多线程
memcached依赖的libevent框架具有很好的可扩展性和并发性,每个线程可以处理多个客户端连接。
这种实现与一些Web服务器不同,比如apache,会开启一个进程或者一个线程来处理一个活动的客户端连接。在这个意义上,memcached更像是服务器领域的Nginx。
并发线程数缺省值是4。
(查一下这些参数如何通过配置文件进行配置)
2.3 使用Memcached
2.3.1 Item的组成
一个完整的指令可以定义成一个item,一个item包含以下内容;
key | 最长250个字符的字符串,不包含空格和换行符 |
flag | 32bit |
有效时间 | 单位是秒,最大3个月 |
CAS | 64bit,需要保持唯一性,可选 |
Arbitrary data | 储存的数据,就是value |
可以看出,Memcached最大支持的key是250个字符;
缓存的最大实效时间是90天时间
2.3.2 Memcached协议
Memcached支持2种协议,ASCII的文本协议和二进制协议
这里列举几个重要的文本协议指令,通过指令也可以看出Memcached支持的一些基本功能
储存命令(Storage Commands)
set | 添加或者覆盖一个Key,新的item放在LRU的顶端 |
add | 添加一个Key |
replace | 覆盖一个key |
append | 在一个已有的Key的Value后面追加数据 |
prepend | 在一个已有的Key的Value前面添加数据 |
cas | Check-and-Set 或者叫 Compare-and-Swap,当数据等于指定值的时候,再对Key对应的value进行修改, 主要用于解决竞争条件 |
检索命令(Retrieval Commands)
get | 通过Key检索Value,可以传入一个key |
gets | 带CAS模式的get 返回一个64bit的数值和item 如果在处理期间cas值发生了修改,那么value不会进行存储 |
delete | 删除一个item,如果指定的Key存在 |
incr/decr | 针对value是一个64bit整数,会执行加/减操作 加和减的数值只能是正整数 如果key不存在,该命令会失败 |
统计(Statistics)
stats | 最基本的统计指令 |
stats settings | 可以查看当权运行实例参数的设置 |
stats items | |
stats slabs | |
stats sizes | |
flush_all | 清除所有缓存的item 该命令不终止服务,会马上返回,因为它没有刷新缓存,而是使所有的item过期 |
通过统计命令返回的一些参数值,我们可以知道memcached现在的实际运行情况,从而做出相应的维护调整。
stats指令的协议返回参数:
- curr_connections:当前连接的客户端数数量,查看是否接近设置的最大连接数
- listen_disabled_num:统计当前正在等待的连接客户端,当连接的客户端数量超过最大设置连接数的时候,再进来的连接请求客户端就会被挂起排队,直到有其他连接断开,挂起的连接才能进入连接,应该保证这个参数接近0
- accepting_conns:boolean类型的,已经达到连接数量最大值,该值为1;未达到连接数量最大值,该值为0
- limit_maxbytes:检查-m参数的实际值
- cmd_flush:flush_all命令被调用的次数
stats sizes指令不要在生产环境使用,因为这个指令会使服务器挂起几分钟的时间,用于统计。
对于应用来说,应该关注的一些参数:
(这里需要细化一些,哪些值能反映出来这些东西)
Global hitrate 命中率 |
命中率的定义是 (命中次数)/(命中次数+未命中次数)
命中率越高,说明应用走缓存的次数越多,性能越好。
|
Hitrate per slab | stats slabs指令可以统计每个slab的命中率 |
Evictions | 一个item如果还没有达到存活期的期限,但是已经在LRU cache的尾部,这个时候如果有一个新的item进来的时候,那么这个在尾部的item就会被evicted了 stats items指令可以统计处每个slab的Evictions状态 evicted 显示被废弃的items的总数量 evicted_nonzero 显示未达到存活期的,但是被废弃的items总数 evicted - evicted_nonzero 显示没有存活期限制的被废弃的item总数 |
3.Memcached客户端
3.1 客户端应该关注的一些事情
Memcached服务器端本身是不支持分布式管理功能的,因此,如果想在维护多台机器上的几个Memcacehd实例,需要由客户端来统一管理,应用调用者不需要直接与Server端接触,客户端的作用相当于是屏蔽了与Memcacehd集群通信的细节,同时为应用调用者提供灵活方便的API。
3.1.1 使用哈希维护多个memcached server
这里主要是需要做2件事情:
(1)将调用者输入的key转化成hash key,这需要一种算法策略
(2)使用一种哈希映射规则,将一个hash key对应到一个指定的memcached server上,这样可以保证读写操作都是针对同一个server处理
3.1.2 序列化
对于用户存储的Value未必是二进制数据,缓存读写操作需要将用户提供的对象和二进制数据之间相互转换,因此需要一种统一的编解码算法实现序列化和反序列化操作
3.1.3 数据压缩
memcached支持的value是有最大值限制的,对于超过该值的,需要提供压缩机制,在序列化之前先压缩。
3.1.4 同步/异步
根据调用者具体应用的场景不同,能够提供灵活的API,同时提供同步和异步API,而不需要调用者再自己封装。异步操作当然是提供超时判定的。
3.2 Spymemcached
Spymemcahed是一个Java编写的memcached客户端,使用比较广泛。现在在googlecode上维护。
3.2.1 基本API用法
Spymemcached的使用方法比较简单
(1)MemcachedClient构造函数需要提供Server列表。
(2)对于基本操作都有对应的API,例如
存储相关
add | 添加一组KV对 OperationFuture<Boolean> add(String key, int exp, Object o) 第二个参数exp代表KV对的有效期 (1)以秒为单位的有效时间,最大是30天,如果超过了30天,会被认为是(2) (2)距离1970/01/01的以秒为单位的时间 搜索的存储操作都是异步请求 通过返回的Future对象,可以判断处理的结果 |
set | 添加或者覆盖一组KV对,其他同add |
replace | 覆盖一组KV对,其他同add |
touch | ASCII协议不支持 仅二进制协议支持 |
incr decr | 在对应的Key的value上增加/减少 注意,储存的value不能是Integer或者Long形式的数字 必须是String形式的数字字符串"123" |
delete | 删除 |
检索相关
get | 同步检索K,返回value |
asyncGet | 异步检索K |
Gets | 检索V的同时返回cas值 结果包装在CASValue中 |
cas | 这个方法其实就是cas模式的replace 会分为3种不同的情况 OK - 操作成功 NOT_FOUND - 操作的Key不存在,这时候不会进行额外的add操作 EXISTS - Key存在,但是提交的cas已经发生了改变,不发生变化 |
asyncCAS | 异步版本cas |
getBulk | 批量读取 返回的是Map<String,Object> |
其他
getSats | 获得参数 |
getUnavailableServers | 获取当前可用Server |
getVersions | 获取memcached版本 |
flush | 清空缓存 |
注意:
(1)从API可以看出,memcached只能存储简单的对象,对于复杂的对象,只能通过Get-and-Put的方式,进行修改,除非调用方客户端自己封装类似的方法。
(2)append和prepend主要用于字符型的value之前和之后的前加和后加字符操作,如果使用的是非String类型的数据,会被认为是字符串进行编解码
(3)incr和decr都认为存储的value是字符串形式的数字
(4)对于写命令基本都是异步操作,对于检索命令,同时提供同步和异步版本,异步返回的是Future对象供调用者使用。
3.2.2 Spymemcached原理
(1)基本设计原理
Spymemecahed的基本设计如下图所示:

memcacehd提供的主体功能可以这样描述
- 接受调用者发出的命令(set/get/delete等)
- 将该操作按照key,对应成一个server节点
- 将调用者的命令转化为符合memcached协议的格式发送给这个server节点
- 接收这个server节点的数据,转化成用户数据返回给调用者
为了解决以上4个问题,Spymemecached作为一个控制节点,是这样设计的
- 对于来自用户端的请求,建立一个全局唯一的请求队列addedQueue,调用者的每一个请求(set/get/delete/flush等)都会放在这个队列中
- 对于用户的提供的key,通过某种hash算法对应成一个key,然后再将这个key映射成server列表中的其中一个
- 队列中的每一个节点是填充了Operation的MemcachedNode对象,也就是说在进入队列之前就已经分配好了位置
- 通过trancoder对象来完成数据的编解码操作
- 每个MemcachedNode对象对应与一个server节点,每个memcachedNode维护3个队列,这3个队列中的对象是Operation,inputQ是个临时队列,将操作转换为addQueue中的每个节点,writeQ是写队列,将每个写指令的操作直接在这个队列中排队,在处理过程中,因为每一个向server端的写操作都会有所响应,因此,这里将每个成功的写处理转换为相应的读处理,放在readQ中排队,当接收到完整数据之后,对调用相应的readQ中的节点,进行后续处理
类图如下图所示,

原理图中的addedQueue是在MemcachedConnection中维护的,MemcachedConnection是一个很重的类,
它会开启一个线程,主要负责顺序以下3件事
- 实时监控addedQueue的请求,并处理
- 使用NIO实时监听数据包的收发
- 容错处理
ConnectionFactory主要负责连接的建立,以及为一些基本的配置参数提供缺省值
例如:容错方法、哈希算法、最大请求队列的长度等参数
NodeLocator主要负责hash算法,Spymemcached提供了2种算法,普通的数组hash和Ketamal一致性哈希,同时维护着server列表
MemcachedNode代表一个server节点,维护这3个队列
OperationFactory是一个Operartion工厂,支持文本协议和二进制协议,每一种操作都继承子Operation接口
4. 自己想到的一些问题
4.1 网络通信中的一般处理过程
Application --> Codec --> Remoting --> Transport
Application:应用层的调用,涉及到业务对象
Codec:提供统一的序列化和反序列化的方法
Remoting:主要是用于分布式之间的负载均衡(一致性哈希可以理解成一种负载均衡的策略)
Transport:数据的手法,需要按照特定的协议进行拼装发送数据和将接收数据进行组包数据提取,Spymemcached这里直接使用原生的NIO,有很多开源框架可以直接使用(Mina,Netty)
4.2 设计一个维护分布式应用的“中间节点”需要考虑的内容
要有一种维护Node列表的方案:比如心跳、TCP长连接
要有一种负载均衡的方案:一致性哈希算法是一种比较好负载均衡的策略
要明确中心节点在集群中的作用:中心节点有2种应用模式
第一种是阻断客户端和集群之间的通信,中心节点作为唯一直接和集群通信的途径(Spymemcached就是这种模式);
第二种是中心节点只是一个配置节点,由客户端直接和集群通信,这样的话,其实是将中心节点分配到了每个客户端上(Tair的ConfigServer就是这种模式,TairClient就是客户端、承担了负载均衡的作用)