Author: FreeKnightDuzhi
----------------------------------------------------------------------------------------
Memcache++Client
------------------------------------------
1:Memcached 是什么?
------------------------------------------
Memcached 是一个高性能的分布式的内存对象缓存系统,用于动态Web应用以缓冲数据库负载,减少数据库访问次数,提升访问速度。
它通过在内存里维护一个巨大的统一的Hash表来存储各种格式的数据,包括文件,数据库检索结果,甚至图像视频等。
Memcached使用了LibEvent(Linux下使用epoll)来均衡任何数量的链接,使用非阻塞网络I/O,对内部对象使用了引用计数,并使用自己的内存页块分配器和哈希表,所以其虚拟内存不会产生碎片且内存分配的时间复杂度为O(1)。
Memcached相比共享内存,特点是可以让不同主机的多个用户同时访问,而共享内存只能单机应用。
Memcached相比数据库,特点是解决了数据库的磁盘开销,SQL解析开销和阻塞带来的麻烦。
注意的是,Memcached自身没有认证和安全机制,意味着Memcached服务器需要放置在防火墙之后。
Memcached服务器下载点:http://memcached.org/
如果你需要一个轻量级的,类型安全的,简单易用的Memcache客户端,请从 http://sourceforge.net/projects/memcachepp/ 下载,它是使用C++编写的Memcache客户端。
------------------------------------------
2:Memcached 特点?
------------------------------------------
1> 基于LibEvent事件处理。即使对服务器的连接数增加,也是O(1)性能。
2> 内置内存存储方式。自己的内存分页管理,减少碎片。基于LRU方式管理缓存,更为高效。自己的Hash算法。
3> 客户端独立,服务器独立。客户端之间没有通讯,完全独立。服务器之间也没有通讯,完全独立。
4> 跨平台。支持Linux,FreeBSD, MacOS,Windows
------------------------------------------
3:简介Memcache++
------------------------------------------
这个项目是开源的,它开发了大约一年,现被多个项目中使用。
但请注意,该项目使用Boost库,所以其中受到Boost的License额外限制。请参考 http://www.boost.org/LICENSE_1_0.txt
------------------------------------------
4:作者简述
------------------------------------------
作者Deam Michael Berris是一个从事多年C++软件开发的工程师,在2007年2月到2009年6月期间,他在 Friendster( http://www.friendster.com/ ) 编写的该项目。当时他在菲律宾电信系统从事软件开发工作。
------------------------------------------
5:项目概述
------------------------------------------
编译Memcache++客户端必须Boost C++库支持。
Memcache++客户端项目编译平台为Linux,编译环境为GCC。
该项目在 Linux(32bit 和 64bit ) GCC4.1.2 GCC4.3.3 均测试通过。
该项目在Windows( 32bit 和 64bit )Ms VC++ 2008 上测试通过。
该项目是一个只有头文件的Lib,意味着它的所有实现均在头文件中。
该项目没有过于特殊的编译要求和配置单,但有以下要求:
1>在你的项目中,除包含本项目头之外,必须包含以下Boost头:Boost.Asio, Boost.Date_Time, Boost.Fusion, Boost.Regex, Boost.Serialization, Boost.Spirit, Boost.System, Boost.Thread
2>若希望该项目为线程安全模式,必须定义 _REENTRANT 宏。这个宏是受到 Boost.Thread 内限制的。
------------------------------------------
6:简易演示Demo
------------------------------------------
下面是个简单的Demo,告诉我们如何去使用Memcache++Client去连接Memcached服务器,并从服务器获取数据,删除数据,设置数据的操作。
#include <memcachepp/memcache.hpp> // 该头文件是Memcache++Client唯一对外需要包含的头文件。
#include <string>
#include <iostream>
int main( int argc, char* argv[] )
{
// Memcache服务器句柄
memcache::handle mc;
// Memcache服务器默认开启11211端口。下面连接服务器
mc << memcache::server("localhost", 11211 ) << memcache::connect;
// 首先删除Memcache服务器的指定Key的Node
try
{
mc << memcache::delete_("FKKey");
}
catch( ... ) { }
std::string szMyString("FreeKnightValue");
std::string szCachedMsg;
try
{
mc << memcache::set("FKKey", szMyString ) // 设置Memcache服务器内FKKey的Value
<< memcache::get("FKKey", szCachedMsg ) // 然后从Memcache服务器内取出FKKey的Value
<< memcache::delete_("FKKey"); // 删除Memcache服务器内FKKey所在Node
std::cout << szCachedMsg << std::endl;
}
catch( std::runtimer_error & e )
{
std::cerr << "Unknown error: " << e.what();
}
return 0;
}
我们可以看出在使用Memcache++时,很类型标准C++流操作,当然,在早期版本,我们还有一种方式如下
using namespace memcache::fluent;
key( mc, "FKKey" ) = szMyString;
warp( szCachedMsg ) = get( mc, "FKKey" );
remove( mc, "FKKey" );
这种方式完全等同于上面的
mc << memcache::set("FKKey", szMyString )
<< memcache::get("FKKey", szCachedMsg )
<< memcache::delete_("FKKey");
现在的Memcache版本可以支持任意一种,我们可以根据自己习惯自由选择。
------------------------------------------
7:核心对象解释
------------------------------------------
Memcache++Client中,memcache::handle是非常重要的对外对象,它是NonCopyable(禁止拷贝构造)的。
它有如下核心成员
connection_ptr
server_info
pool_info
server_container
pool_container
它有如下核心函数
void add_server( serverHostName, serverInfo ); // 增加一个或多个Memcache服务器连接
void add_pool( serverPoolName, serverInfo ); // 增加一个服务器池连接
void connect(); // 连接所有的Memcache服务器(在addServer的参数中被定义)
string version( size_t ); // 获取Memcache服务器版本
void delete_( size_t, Key, time ); // 删除一个Key的Node(参数Time意义请看下文具体补充)
void get<D>( size_t, Key, DateType ); // 获取一个Key的Value填充到DateType内
void get_raw( size_t, Key, szDate ); // 获取一个Key的Value并保存为string填充刚到szDate内
bool is_connect( serverHostName ); // 判断是否和一个Memcache服务器连接
size_t pool_count(); // 获取Handle内的池子数量
size_t server_count(); // 获取Handle内连接的Memcache服务器数量
void set<D>( size_t, Key, DateType, Time ... );
void set_raw( size_t, Key, szDateRaw, Time... );
void add<D>( size_t, Key, DateType, Time ... );
void add_raw( size_t, Key, szDateRaw, Time... );
void replace<D>( size_t, Key, DateType, Time ... );
void replace_raw( size_t, Key, szDateRaw, Time... );
void append_raw( size_t, Key, szDateRaw, Time... );
void prepend_raw( size_t, Key, szDateRaw, Time... );
void incr( size_t, Key, int , int );
其中值得说明的是:
1> 向Memcache内保存数据可以指定期限(秒),过了这个期限,该数据将被丢弃。若不指定期限,则自动按照LRU算法保存数据。
2> 函数中add,replace,set均是向Memcache服务器保存一个数据,区别是:
add 仅当存储空间内不存在指定健的时候,数据才进行保存。
replace 仅当存储空间内已存在指定键的时候,数据才进行保存。
set 无论存储空间是否有指定键,数据均进行保存。
3> Memcached可以使用get方法获取一个键键值,同时也可以使用get_mutil非同步的获取多个键的键值,比循环单个get效率高。这点在Memcache++Client里暂未找到相关接口。
4> 删除数据时候指定的Time是禁止在指定的单位时间内不再允许该Key保存新数据,该功能可以防止缓冲数据的不完整。但是,set函数可能忽视该阻塞,照常保存。
-----------------
Memcache++Client中,”命令“是个很重要的概念。
Demo中的 memcache::set ,memcache::get 均是”命令“。每个命令均存在的函数有以下三个:
HandleInstance& HandleInstance << Directive; // 将一个命令填充到Handle内。
D( Directive );
D< T1, T2 .... Tn >( t1, t2 ... tn ); // 对多个命令进行构造。
void Directive( HandleInstance ); // 对一个Handle执行指定命令。
我们可以定义自己的命令,但是强制要求实现三个函数,一个构造函数一个是operator << 和operator ().我们看一下Memcache++Client里的get_directive命令源代码:
template< typename T >
struct get_directive
{
explicit get_directive( std::string const & key, T & holder )
: _key( key ), _holder( holder ){};
template< typename T >
void operator() ( T & handle ) const
{
size_t pools = handle.pool_count();
assert( pools != 0 );
handle.get( handle.hash( _key, pools ), _key, _holder );
}
private:
mutable std::string _key;
mutable T & _holder;
}
Memcache++Client中,默认命令有如下:
get,set,add,replace,delete_,raw_get,raw_set,raw_append,raw_prepend,incr,decr,server,pool,connet.
每个C++流式命令对应的均有一个fluent格式的函数调用。
例如:
mc << memcache::set( "key", value ); 完全等同于 key( mc, "key") = value;
mc << memcache::get( "key", value ); 完全等同于 wrap( value ) = get( mc, "key" );
其中后面的调用被称为 fluent 格式函数调用。这些API的详细描述这里不再给出,若有不清晰,可直接邮件联系
这里只给出核心的命令
connect: 打开一个到Memcache的连接
pconnect: 打开一个到Memcache的长连接
close: 关闭一个到Memcache的连接
set,get,repalce: 保存,提取,替换 一个Memcache服务器的数据
delete: 删除一个Memcache服务器的数据
getStats: 获取当前Memcache服务器运行状态
-----------------
Memcache++Client中,Handle中保存了三种策略。
其中包括:ThreadPolicy线程策略 , DateInterchangePolicy数据交换策略,HashPolicy哈希策略。
其中线程策略,使我们可以定义Handle在如何协调线程的合作,默认状态下线程策略是使用一个嵌套的ScopedLock,而且也未必是单线程,这取决于_REENTRANT宏对Boost.Thread的控制。
其中数据交换策略,默认为binary_interchange策略,我们可以修正为以下三种 binary_interchange, text_interchange, string_interchange .
------------------------------------------
8:某志补充
------------------------------------------
通常一个Memcached服务器进程一个会占用2GB内存空间。
虽然Memcached服务器是网络通讯,理论上是可以支持无限多链接,但和Apache不同之处是,它更多时候面对的稳定的连接,根据Linux线程连接并发能力考虑,保守认为Memcached最大同时连接数250左右,但已足够常规游戏服务器组使用了。
Memcached是自己的内存管理方式,和APC不同,没有基于共享内存和MMAP,所以也就没有共享内存的限制,两者是两回事。
Memcached服务器是使用预分配内存的方式,其内存分配最小单位为Slab。其中Slab可以理解为一个内存块,大小为1048576字节,正好是1MB,所以Memcached始终是整MB的使用内存。一个Slab内会分配若干个Chunk,每个Chunk会有一个Item(内部包含Item结构体以及Key,Value).Slab会有自己的ID,又会被挂接在SlabClass数据中。
Memcached服务器对内存要求相对略高,对CPU基本不做要求,在硬件配置时可考虑该点。
辅助监视Memcached可以使用Nagios开源监视软件。
------------------------------------------
9:细说MemCached 内存存储机制
------------------------------------------
MemCached是使用SlabAllocator进行内存管理的。它是将原本预先分配的内存,分割为各种不同尺寸长度的块,并且将相同长度的内存块分成组进行管理。
这里我们需要解释几个名词概念:
1:Page 是一次分配给Slab的内存空间,如我总结所说,其大小默认为1MB。之后这1MB会交由SlabAllocator的需要切割为大小不同的chunk。
2:Chunk 是实际的内存空间,大小未必一致。
3:SlabClass 是对Chunk进行管理的,每一个SlabClass内的Chunk大小是完全一致的。
当客户端或者数据库要求添加一条数据时,Memcached根据收到的数据大小(假设为100bytes)其寻则最合适数据大小的SlabChunk(假设为112bytes的SlabClass里),填充其中,并通知所属Slab,这个chunk已被占用,由Slab维护一个空闲chunk列表。
但是值得注意的是,内存的Chunk分割是在MemCached启动时便进行的,是固定长度,那么我们就如上例可知,假设Slab分割Chunk有以下几种规格的大小:88bytes, 112bytes, 144bytes, 184bytes, 512bytes ... 那么我们100bytes的数据写入时,只能填充到112bytes的Chunk内,即浪费了12bytes。
若我们需要存储的对象大小均为600bytes以上,则浪费了大量的小Chunk。此时我们可以用 grown factor 进行调节。
默认时这个值为1.25。我们通过verbose查看$ memcached -f 1.25 -vv 可得知如下结果
slab class 1: chunk size 88 bytes perslab 11915
slab class 2: chunk size 112 bytes perslab 9362
slab class 3: chunk size 144 bytes perslab 7281
....
slab class 10: chunk size 744 bytes perslab 1409
....
可知每个slab class内的chunk大小是1.25倍递增的(个别时候有误差,是为了字节对齐设置的)。
我们同样可以设置为2倍。$memcached -f 2 -vv 可得知如下结果
slab class 1: chunk size 128 bytes perslab 8192
slab class 2: chunk size 256 bytes perslab 4096
.....
slab class 10: chunk size 65536 bytes perslab 16
slab class 11: chunk size 131072 bytes perslab 8
.....
当然这个状态可以通过Perl脚本进行监察,得知 Chunk大小,LRU内最旧的记录生存时间,分配给Slab的页数,Slab内的记录数等信息。
Memcached是内存缓存机制,没有对磁盘操作,所以,其大小受到内存制约,在优化内存使用方面,Memcached有以下手段:
1> 不释放内存。Memcached是不会释放内存的,当我们delete_命令后,服务器只会将指定记录设置为不可见,之后进行重复使用,而不会真正的free归还内存,避免了内存碎片问题。
2> 惰性释放。Memcached内部不会频繁的刷新以删除过期记录,而是在get时检查记录时间戳,这是被动的检查,所以Memcached不会在监视刷新上消耗CPU时间。
3> 使用LRU删除记录。LRU全称为Least Recently Used,就意味着,当Memcached内存不足时(从slabClass里获取未使用空间失败时),就会从最近未被使用的记录中进行搜索,查找使用最近最少使用的记录进行删除标志,重复利用内存。
------------------------------------------
10:MemCached通讯数据交换机制
------------------------------------------
MemCached当前更多的是使用binary_interchange协议,它是由16字节的包头和Key以及不定长数据组成的。包头内包含了标准的Magic字节,命令种类,键长,值长等信息。
二进制协议解决了文本协议可能出现的平台问题,以及XML等格式带来的复杂度。
------------------------------------------
11:MemCached的分布式
------------------------------------------
我们上面了解了许多,却无法发现MemCached的分布式在哪儿,而其实Memcached的分布式不存在于服务器,而是完全由客户端实现的。
假设我们要保存三个Key。分别为"Free""Knight""Duzhi",另有Memcached服务器两台。
我们Memcached客户端会通过一个算法,将Key分类,假设我们设定Key第一个字母在'G'之前的数据保存在第一个Memcached服务器上,'G'之后的数据保存在第二个Memcached服务器上。
在get,set,replace等操作时,也先在算法这一层做出服务器责任分割,则可以实现Memcached的分布式。
这里,就是Memcache++Client替我们去做到的事情。
常规的余数Hash是最容易想到的,它会根据Memcached服务器个数和键的整数哈希值范围进行分布,用键的哈希值除以服务器台数得到的余数,就是所选择的服务器编号。这样是相对均衡合理的。
但这种方式的一大问题就是,当服务器个数发生更变时,代价相当昂贵。余数的变更将改变全部数据的服务器分配。此时,我们有个更好的解决方法就是ConsistentHash.
ConsistentHash的基本概念就是
首先求出Memcached服务器节点的哈希值,将其配置到0-2^32的一个圆上。
然后用同样的算法求出Key的哈希值,并映射到圆上,然后从Key哈希映射的位置开始顺时针查找,将数据保存到第一个找到的服务器节点上。
若超过了2^32仍然找不到服务器,就将其保存在第一台Memcached服务器上。
当我们增加一个Memcached服务器节点后,ConsistentHash内只有部分数据存储受到影响,而这些数据是 上一个Memcached服务器节点顺时针到新添加的Memcached服务器节点之间的KeyHash映射数据。
但是这样的话,ConsistenHash可以看的出来,在添加Memcached服务器时,它很大限度上抑制了键的重新分布,但它的哈希分布更可能没有余数法均匀,此时,有的ConsistentHash使用了虚拟节点方法,即,每个Memcached服务器视为多个虚拟的节点,在圆上分配多个点交由其负责,则更大限度的抑制分布不均匀。