您的位置:首页 > 数据库 > Memcache

浅谈Memcache++Client

2012-08-20 14:55 267 查看
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服务器视为多个虚拟的节点,在圆上分配多个点交由其负责,则更大限度的抑制分布不均匀。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息