memcached
如何通过源代码先大体学习库忽略具体实现细节呢?
1、首先学会库中开放出来程序员可调用函数API,学习其参数配置,大体先忽略被库内部的调用大量函数。
1、memcached
介绍
memcached是一个高性能的内存缓存对象系统,其实质为一个键值对的hashmap索引,其事件处理和网络通信均是基于libevent。memcached区别于libevent,因为memcached是一个运行程序,不需要编译成动态连接库,供其他程序调用。memcached通常作为C/S模型中的S,也就是服务器端,客户端通过命令缓存数据。
基本特点:
- 协议简单:使用基于文本行的协议,二进制协议使用比较少。
- 基于内存存储:数据存储在内存中,所以读取速度很快。
- 事件处理:基于libevent开发,所以可以应对C10问题。
- 不互相通信的分布式:多台memcached服务器之间不互相通信,由客户端实现分布式算法,所以通常客户端使用一致性hash策略,通常拥有快隔离,慢恢复的特性。
安装与启动
memcached区别于libevent,因为memcached是一个运行程序,不需要编译成动态连接库,供其他程序调用。所以不需要指定最后库文件生成的目录,仅仅需要指定memcached应用程序放置的目录,以及编译过程中libevent库文件的目录。
本机设定应用程序防止默认目录/usr/local/bin
libevent库目录为/usr/loacl/bin
。因此安装很容易了。
ar -zxv -f memcached-1.4.21.tar.gz
cd memcached-1.4.21
./configure --with-libevent=/usr/local/bin CFLAGS="-g -O0"//指定libevent库目录以及 编译成debug版本。
make
make install//install之后,在/usr/local/bin会出现memcached应用程序。
//下面是make install之后的输出,可以看到。
1、memcached.1 放到了 '/usr/local/share/man/man1',以后可以通过man memcached访问帮助文档。
2、protocol_binary.h协议头文件放到了 '/usr/local/include/memcached'
memcached可执行文件放到了'/usr/local/bin'
3、通常一个开源代码,里面会有man文件夹,也就是通过man程序可以打开的文档,libmemcached-1.0.18里面一样有,通常make install会将这些文件都放到linux对应的文件夹,然后就可以通过man寻找帮助信息了,很方便。
make install-recursive
make[1]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21'
Making install in doc
make[2]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make install-am
make[3]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make[4]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make[4]: Nothing to be done for 'install-exec-am'.
/bin/mkdir -p '/usr/local/share/man/man1'
/usr/bin/install -c -m 644 memcached.1 '/usr/local/share/man/man1'
make[4]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make[3]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make[2]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21/doc'
make[2]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21'
make[3]: Entering directory '/home/wangjun/Desktop/memcached-1.4.21'
/bin/mkdir -p '/usr/local/bin'
/usr/bin/install -c memcached '/usr/local/bin'
/bin/mkdir -p '/usr/local/include/memcached'
/usr/bin/install -c -m 644 protocol_binary.h '/usr/local/include/memcached'
make[3]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21'
make[2]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21'
make[1]: Leaving directory '/home/wangjun/Desktop/memcached-1.4.21'
启动:
简单的启动命令如下,设置-l和-p分别用来设置ip和监听的端口。-vv是输出一些运行信息。
$memcached -l 192.168.1.112 -p 8888 -vv
-vv指示输出相应的调试信息,看了源代码就知道是怎么回事了。
工具memcached-tool
#!/usr/bin/perl
#
# memcached-tool:
# stats/management tool for memcached.
#
# Author:
# Brad Fitzpatrick <[email protected]>
#
# License:
# public domain. I give up all rights to this
# tool. modify and copy at will.
#
use strict;
use IO::socket::INET;
my $host = shift;
my $mode = shift || "display";
my ($from, $to);
if ($mode eq "display") {
undef $mode if @ARGV;
} elsif ($mode eq "move") {
$from = shift;
$to = shift;
undef $mode if $from < 6 || $from > 17;
undef $mode if $to < 6 || $to > 17;
print STDERR "ERROR: parameters out of range\n\n" unless $mode;
} elsif ($mode eq 'dump') {
;
} elsif ($mode eq 'stats') {
;
} else {
undef $mode;
}
undef $mode if @ARGV;
die
"Usage: memcached-tool <host[:port]> [mode]\n
memcached-tool 10.0.0.5:11211 display # shows slabs
memcached-tool 10.0.0.5:11211 # same. (default is display)
memcached-tool 10.0.0.5:11211 stats # shows general stats
memcached-tool 10.0.0.5:11211 move 7 9 # takes 1MB slab from class #7
# to class #9.
You can only move slabs around once memory is totally allocated, and only
once the target class is full. (So you can't move from #6 to #9 and #7
to #9 at the same itme, since you'd have to wait for #9 to fill from
the first reassigned page)
" unless $host && $mode;
$host .= ":11211" unless $host =~ /:\d+/;
my $sock = IO::Socket::INET->new(PeerAddr => $host,
Proto => 'tcp');
die "Couldn't connect to $host\n" unless $sock;
if ($mode eq "move") {
my $tries = 0;
while (1) {
print $sock "slabs reassign $from $to\r\n";
my $res = <$sock>;
$res =~ s/\s+//;
if ($res eq "DONE") {
print "Success.\n";
exit 0;
} elsif ($res eq "CANT") {
print "Error: can't move from $from to $to. Destination not yet full? See usage docs.\n";
exit;
} elsif ($res eq "BUSY") {
if (++$tries == 3) {
print "failed to move after 3 tries. Try again later.\n";
exit;
}
print "Page busy, retrying...\n";
sleep 1;
}
}
exit;
}
if ($mode eq 'dump') {
my %items;
my $totalitems;
print $sock "stats items\r\n";
while (<$sock>) {
last if /^END/;
if (/^STAT items:(\d*):number (\d*)/) {
$items{$1} = $2;
$totalitems += $2;
}
}
print STDERR "Dumping memcache contents\n";
print STDERR " Number of buckets: " . scalar(keys(%items)) . "\n";
print STDERR " Number of items : $totalitems\n";
foreach my $bucket (sort(keys(%items))) {
print STDERR "Dumping bucket $bucket - " . $items{$bucket} . " total items\n";
print $sock "stats cachedump $bucket $items{$bucket} 1\r\n";
my %keyexp;
while (<$sock>) {
last if /^END/;
# return format looks like this
# ITEM foo [6 b; 1176415152 s]
if (/^ITEM (\S+) \[.* (\d+) s\]/) {
$keyexp{$1} = $2;
}
}
foreach my $k (keys(%keyexp)) {
my $val;
print $sock "get $k\r\n";
my $response = <$sock>;
$response =~ /VALUE (\S+) (\d+) (\d+)/;
my $flags = $2;
my $len = $3;
read $sock, $val , $len;
# get the END
$_ = <$sock>;
$_ = <$sock>;
print "add $k $flags $keyexp{$k} $len\r\n$val\r\n";
}
}
exit;
}
if ($mode eq 'stats') {
my %items;
print $sock "stats\r\n";
while (<$sock>) {
last if /^END/;
chomp;
if (/^STAT\s+(\S*)\s+(.*)/) {
$items{$1} = $2;
}
}
printf ("#%-17s %5s %11s\n", $host, "field", "Value");
foreach my $name (sort(keys(%items))) {
printf ("%24s %12s\n", $name, $items{$name});
}
exit;
}
# display mode:
my %items; # class -> { number, age, chunk_size, chunks_per_page,
# total_pages, total_chunks, used_chunks,
# free_chunks, free_chunks_end }
print $sock "stats items\r\n";
while (<$sock>) {
last if /^END/;
if (/^STAT items:(\d+):(\w+) (\d+)/) {
$items{$1}{$2} = $3;
}
}
print $sock "stats slabs\r\n";
while (<$sock>) {
last if /^END/;
if (/^STAT (\d+):(\w+) (\d+)/) {
$items{$1}{$2} = $3;
}
}
print " # Item_Size Max_age 1MB_pages Count Full?\n";
foreach my $n (1..40) {
my $it = $items{$n};
next if (0 == $it->{total_pages});
my $size = $it->{chunk_size} < 1024 ? "$it->{chunk_size} B " :
sprintf("%.1f kB", $it->{chunk_size} / 1024.0);
my $full = $it->{free_chunks_end} == 0 ? "yes" : " no";
printf "%3d %8s %7d s %7d %7d %7s\n",
$n, $size, $it->{age}, $it->{total_pages},
$it->{number}, $full;
}
1、memcached-tool脚本可以方便地获得slab的使用情况。
2、使用方法也极其简单:perl memcached-tool server_ip:prot option
。
3、有了这个工具就需要自己写脚本,连接客户端,发送相应的信息了,这个就很容易使用了,其实获取信息的方法就是给服务器端发送相应的指令。
Memcached启动命令行参数详解
启动Memcached时候,可以通过命令行参数配置。主要通过修改struct settings
中的变量值,修改整个memcached工作参数。以下介绍struct settings
结构体以及其默认初始值。
//源代码内部使用的设置结构体
struct settings {
size_t maxbytes;
int maxconns;
int port;
int udpport;
char *inter;
int verbose;
rel_time_t oldest_live; /* ignore existing items older than this */
int evict_to_free;
char *socketpath; /* path to unix socket if using local socket */
int access; /* access mask (a la chmod) for unix domain socket */
double factor; /* chunk size growth factor */
int chunk_size;
int num_threads; /* number of worker (without dispatcher) libevent threads to run */
int num_threads_per_udp; /* number of worker threads serving each udp socket */
char prefix_delimiter; /* character that marks a key prefix (for stats) */
int detail_enabled; /* nonzero if we're collecting detailed stats */
int reqs_per_event; /* Maximum number of io to process on each
io-event. */
bool use_cas;
enum protocol binding_protocol;
int backlog;
int item_size_max; /* Maximum item size, and upper end for slabs */
bool sasl; /* SASL on/off */
bool maxconns_fast; /* Whether or not to early close connections */
bool lru_crawler; /* Whether or not to enable the autocrawler thread */
bool slab_reassign; /* Whether or not slab reassignment is allowed */
int slab_automove; /* Whether or not to automatically move slabs */
int hashpower_init; /* Starting hash power level */
bool shutdown_command; /* allow shutdown command */
int tail_repair_time; /* LRU tail refcount leak repair time */
bool flush_enabled; /* flush_all enabled */
char *hash_algorithm; /* Hash algorithm in use */
int lru_crawler_sleep; /* Microsecond sleep between items */
uint32_t lru_crawler_tocrawl; /* Number of items to crawl per run */
};
struct settings settings;//全局结构体变量
//memcached的main函数启动,首先会此函数,初始化默认设置。
static void settings_init(void) {
//开启CAS业务,如果开启了那么在item里面就会多一个用于CAS的字段。可以在启动memcached的时候通过-C选项禁用
settings.use_cas = true;
settings.access = 0700; //unix socket的权限位信息
settings.port = 11211;//memcached监听的tcp端口
settings.udpport = 11211;//memcached监听的udp端口
//memcached绑定的ip地址。如果该值为NULL,那么就是INADDR_ANY。否则该值指向一个ip字符串
settings.inter = NULL;
settings.maxbytes = 64 * 1024 * 1024; //memcached能够使用的最大内存
settings.maxconns = 1024; //最多允许多少个客户端同时在线。不同于settings.backlog
settings.verbose = 0;//运行信息的输出级别.该值越大输出的信息就越详细
settings.oldest_live = 0; //flush_all命令的时间界限。插入时间小于这个时间的item删除。
settings.evict_to_free = 1; //标记memcached是否允许LRU淘汰机制。默认是可以的。可以通过-M选项禁止
settings.socketpath = NULL;//unix socket监听的socket路径.默认不使用unix socket
settings.factor = 1.25; //item的扩容因子
settings.chunk_size = 48; //最小的一个item能存储多少字节的数据(set、add命令中的数据)
settings.num_threads = 4; //worker线程的个数
//多少个worker线程为一个udp socket服务 number of worker threads serving each udp socket
settings.num_threads_per_udp = 0;
settings.prefix_delimiter = ':'; //分隔符
settings.detail_enabled = 0;//是否自动收集状态信息
//worker线程连续为某个客户端执行命令的最大命令数。这主要是为了防止一个客户端霸占整个worker线程
//,而该worker线程的其他客户端的命令无法得到处理
settings.reqs_per_event = 20;
settings.backlog = 1024;//listen函数的第二个参数,不同于settings.maxconns
//用户命令的协议,有文件和二进制两种。negotiating_prot是协商,自动根据命令内容判断
settings.binding_protocol = negotiating_prot;
settings.item_size_max = 1024 * 1024;//slab内存页的大小。单位是字节
settings.maxconns_fast = false;//如果连接数超过了最大同时在线数(由-c选项指定),是否立即关闭新连接上的客户端。
//用于指明memcached是否启动了LRU爬虫线程。默认值为false,不启动LRU爬虫线程。
//可以在启动memcached时通过-o lru_crawler将变量的值赋值为true,启动LRU爬虫线程
settings.lru_crawler = false;
settings.lru_crawler_sleep = 100;//LRU爬虫线程工作时的休眠间隔。单位为微秒
settings.lru_crawler_tocrawl = 0; //LRU爬虫检查每条LRU队列中的多少个item,如果想让LRU爬虫工作必须修改这个值
//哈希表的长度是2^n。这个值就是n的初始值。可以在启动memcached的时候通过-o hashpower_init
//设置。设置的值要在[12, 64]之间。如果不设置,该值为0。哈希表的幂将取默认值16
settings.hashpower_init = 0; /* Starting hash power level */
settings.slab_reassign = false;//是否开启调节不同类型item所占的内存数。可以通过 -o slab_reassign选项开启
settings.slab_automove = 0;//自动检测是否需要进行不同类型item的内存调整,依赖于settings.slab_reassign的开启
settings.shutdown_command = false;//是否支持客户端的关闭命令,该命令会关闭memcached进程
//用于修复item的引用数。如果一个worker线程引用了某个item,还没来得及解除引用这个线程就挂了
//那么这个item就永远被这个已死的线程所引用而不能释放。memcached用这个值来检测是否出现这种
//情况。因为这种情况很少发生,所以该变量的默认值为0(即不进行检测)。
//在启动memcached时,通过-o tail_repair_time xxx设置。设置的值要大于10(单位为秒)
//TAIL_REPAIR_TIME_DEFAULT 等于 0。
settings.tail_repair_time = TAIL_REPAIR_TIME_DEFAULT;
settings.flush_enabled = true;//是否运行客户端使用flush_all命令
}
//然后通过命令行参数,修改对应的值。
while (-1 != (c = getopt(argc, argv,
"a:" /* access mask for unix socket */
"A" /* enable admin shutdown commannd */
"p:" /* TCP port number to listen on */
"s:" /* unix socket path to listen on */
"U:" /* UDP port number to listen on */
"m:" /* max memory to use for items in megabytes */
"M" /* return error on memory exhausted */
"c:" /* max simultaneous connections */
"k" /* lock down all paged memory */
"hi" /* help, licence info */
"r" /* maximize core file limit */
"v" /* verbose */
"d" /* daemon mode */
"l:" /* interface to listen on */
"u:" /* user identity to run as */
"P:" /* save PID in file */
"f:" /* factor? */
"n:" /* Minimum space allocated for key+value+flags */
"t:" /* threads */
"D:" /* prefix delimiter? */
"L" /* Large memory pages */
"R:" /* max requests per event */
"C" /* disable use of CAS */
"b:" /* backlog queue limit */
"B:" /* Binding protocol */
"I:" /* Max item size */
"S" /* Sasl ON */
"F" /* Disable flush_all */
"o:" /* Extended generic options */
))) {
switch (c) {
........
}
}
从上面分析流程可知,首先定义结构体变量,然后初始化其部分变量,最后通过循环处理命令行参数,设定对应的值。很简单的过程,下面详细解释每个命令行参数对应的功能,加粗的部分是比较常用的指令,现在对于这些指令不太理解每关系,搞懂了后面的源码剖析,那么命令就非常懂了。
- “A”:是否运行客户端使用shutdown命令。默认是不允许的。该选项将允许。客户端的shutdown命令会将memcached进程杀死。该选项会将
settings.shutdown_command
赋值为true
。 - “a”:unix socket的权限位信息(访问掩码)。该选项的参数赋值给
settings.access
。 - “U:”:大写U。memcached监听的UDP端口值,默认端口为11211。该选项的参数赋值给
settings.udpport
。 - “p:”:小写p,memcached监听的tcp端口。默认端口为11211, 该选项的参数赋值给
settings.port
。 - “s:”:小写s。unix socket监听的socket路径。该选项的参数赋值给
settings.socketpath
。 - “m:”:小写m。memcached能够使用的最大内存值,默认是64MB。参数单位为MB。该参数赋值给
settings.maxbytes
。 - “M”:大写M。默认情况下,当memcached的内存使用完后,将进行LRU机制淘汰item以腾出空间。如果使用本选项那么将关闭LRU功能。当然关闭LRU不代表不能存储新数据。如果memcached里面存有过期失效的item,那么就可以存储新数据。否则将无法存储。该选项将
settings.evict_to_free
赋值为0。 - “c:”:小写c。每个线程最多允许多少个客户端同时在线(这个值不等价于listen函数的第二个参数),该选项和后面的b选项有所不同。 默认值为1024个。该选项参数赋值给
settings.maxconns
。 - “h”:显示帮助信息。
- “i”:显示memcached和libevent的版权信息。
- “k”:小写k。将memcached使用到的内存锁定在内存中,不准OS把memcached的内存移动到虚拟内存。因为当OS把memcached的内存移动到虚拟内存可能会导致页错误,降低memcached的响应时间。
- “v”:小写v。输出memcached运行时的一些信息。-v -vv -vvv输出的信息依次增加。该选项会增加
settings.verbose
的值。 - “l:”:小写L。memcached绑定的ip地址。如果不设置这个选项,那么memcached将使用
INADDR_ANY
。如果想指定多个IP地址,那么该选项的参数可以由多个ip组成,ip之间用逗号分隔。也可以多次使用这个选项,此时端口应该尾随ip而不是单独用-p选项指定。例如-l 127.0.0.1:8888,192.168.1.112:9999 或者 -l 127.0.0.1:8888 -l 192.168.1.112:9999该选项参数将赋值给settings.inter
。 - “d”:以守护进程的形式运行memcached。
- “r”:将core文件大小设置为不受限制。
- “R:”:worker线程连续为某个客户端执行命令的最大命令数。该选项的参数赋值给
settings.reqs_per_event
。 - “u:”:小写u。当以root用户启动memcached的时候需要指定memcached的所属用户,其他用户启动memcached不需要此选项。
- “P:”:大写p。该选项的参数指明memcached的pid保存文件。要和-d选项配合使用。注意运行的用户是否有权限写对应的文件。
- “f:”:item的扩容因子。默认值为1.25。该选项的参数值可以是小数但必须大于1.0。该选项参数将赋值给
settings.factor
。 - “n:”:设置最小的item(key+value+flags)能存储多少字节的数据。该选项参数赋值给
settings.chunk_size
。 - “t:”:该选项的参数用于指定worker线程的个数,不建议超过64个。如果不设置该选项默认有4个线程。该参数会赋值给
settings.num_threads
。 - “D:”:参数字符作为前缀和ID的分隔符。使用了该选项才会自动收集状态信息。也可以在启动memcached后,客户端使用stats detail on命令开启,此时默认的分隔符为冒号”:”。该选项参数会赋值为
settings.prefix_delimiter
,并将settings.detail_enabled
赋值为1。 - “L”:如果OS允许的话,那么向OS申请更大的内存页。OS的默认内存页为4KB。大的内存页可以有效降低页表的大小,提高效率。此选项会使得memcached预先先OS全部所需的申请内存。当然这些内存尽量是用大内存页分配的。
- “C:” :大写C。memcached默认是使用CAS的,本选项是禁用CAS。本选项会将
settings.use_cas
赋值为false。 - “b:”:listen函数的第二个参数。该选项的参数赋值给settings.backlog。如果不设置该选项,那么默认为1024。该选项和前面的c选项有所不同。
- “B:”:memcached支持文本协议和二进制协议。该选项的参数用于指定使用的协议。默认情况下是根据客户端的命令而自动判断(也叫协商),参数只能取auto、binary、ascii这三个字符串值。将参数将赋值给
settings.binding_protocol
。 - “I:”:大写i。slab分配器中,每一个页的大小。这个选项的参数是一个数值表示页的大小。默认单位是B也可以在数值后面带K或者M(大小写都行),表示KB和MB。页的大小小于1KB或者大于128MB都是不允许的。不推荐使用该选项。本选项参数会赋值给
settings.item_size_max
。 - “S”:大写S。打开sasl安全协议。会将settings.sasl赋值为true。
- “F”:禁止客户端的
flush_all
命令。默认是允许客户端的flush_all
命令的。该选项将settings.flush_enabled
赋值为false。 - “o:”:小写o。启动扩展选项,有下面几个子选项可以设置。这个选项是用来优化的。
- maxconns_fast:如果连接数超过了最大同时在线数(由-c选项指定),立即关闭新连接上的客户端。该选项将
settings.maxconns_fast
赋值为true。 - hashpower:哈希表的长度是2^n。可以通过选项hashpower设置指数n的初始值。如果不设置将取默认值16。该选项必须有参数,参数取值范围只能为[12, 64]。本选项参数值赋值给settings.hashpower_init。
- slab_reassign: 该选项没有参数。用于调节不同类型的item所占的内存。不同类型是指大小不同。某一类item已经很少使用了,但仍占用着内存。可以通过开启slab_reassign调度内存,减少这一类item的内存。如果使用了本选项,settings.slab_reassign赋值为true。
- slab_automove:依赖于slab_reassign。用于主动检测是否需要进行内存调度。该选项的参数是可选的。参数的取值范围只能为0、1、2。参数2是不建议的。本选项参数赋值给settings.slab_automove。如果本选项没有参数,那么settings.slab_automove赋值为1。
- hash_algorithm:用于指定哈希算法。该选项必须带有参数。并且参数只能是字符串jenkins或者murmur3。
- tail_repair_time:用于检测是否有item被已死线程所引用。一般不会出现这种情况,所以默认不开启这种检测。如果需要开启这种检测,那么需要使用本选项。本选项需要一个参数,参数值必须不小于10。该参数赋值给settings.tail_repair_time。
- lru_crawler:本选项用于启动LRU爬虫线程。该选项不需要参数。本选项会导致settings.lru_crawler赋值为true。
- lru_crawler_sleep:LRU爬虫线程工作时的休眠间隔。本选项需要一个参数作为休眠时间,单位为微秒,取值范围是[0, 1000000]。该参数赋值给settings.lru_crawler_sleep。
- lru_crawler_tocrawl:LRU爬虫检查每条LRU队列中的多少个item。该选项带有一个参数。参数会赋值给settings.lru_crawler_tocrawl。
- maxconns_fast:如果连接数超过了最大同时在线数(由-c选项指定),立即关闭新连接上的客户端。该选项将
Memcached连接及存储命令
////////////////////////////////////////1、启动
memcached -m 64 -p 11211 -vvv //slabs大小 端口 打印信息等级
////////////2、连接,客户端和服务器连接比较简单,基于文本而不是二进制,所以基于使telent即可
$ telnet localhost 11211 //连接
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get foo //输入指令
VALUE foo 0 2
hi
END
stats
STAT pid 8861
quit //telnet退出指令
///////////////////////////////3、命令介绍
First, the client sends a command line which looks like this:
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
<command name>是"set", "add", "replace", "APPend" or "prepend"
"set" :“存储这个数据”,一般是更新已有的缓存,也可以用于新增。
"add" :新增缓存,缓存中不存在新增的KEY。
"Replace":替换现有的缓存,缓存中一定已经存储KEY
"append":在现有的缓存数据后添加缓存数据。
"Prepend":在现有的缓存数据前添加缓存数据
"Cas":check and set操作,存储缓存,前提是在check后没有其它人修改过数据,用于多客户端同时设置相同的KEY时的原子操作。
"key":缓存的KEY
"flags":最开始是16位的无符号整数,现在的版本一般是32位。用户客户端存储自定义标记数据,客户端自定义这个数据如何存储,例如我经过压缩存储标记为1,那么这个返回的时候flag会是1,然后就知道是经过压缩的,那么客户端库会反压缩给应用使用。一种标记作用而已。具体如何处理,需要客户端处理。
"exptime":缓存过期时间。0表示不自动失效,可以是Unix time或当前服务器时间的偏移量(秒为单位),如果你想设置当前时间后1分钟过期,则此参数为60。
设置秒数:从设定开始数,第n秒后失效。
时间戳, 到指定的时间戳后失效。
"bytes":缓存数据的长度
"cas unique":unique 64-bit value of an existing entry,cas操作的时候回传的值,用于服务器端判断缓存是否改变。
"noreply":服务器不响应处理结果。
After this line, the client sends the data block:
<data block>\r\n //\r\n结束
After sending the command line and the data blockm the client awaits
the reply, which may be:
"STORED\r\n":表示存储成功。
"NOT_STORED\r\n":表示未存储,但并不是错误。如:对已经有的KEY使用add。
"exists\r\n":表示使用cas命令设置数据未成功,在你最后一次获取数据后,数据已经被其它人修改。
"NOT_FOUND\r\n":表示使用cas存储数据时候,key不存储。
//4、命令使用介绍
////////////////////////////////////////////////
delete删除一个已经存在的键
get key
VALUE key 0 12
prepend12345
END
delete key
DELETED
get key
END
/////////////////////////////////////
add增加一个不存在的key
存在,则返回错误。
不存在则成功。
telnet localhost 11211
add key 0 0 5
12345
STORED
add key 0 0 1
1
NOT_STORED
/////////////////////////////////////
repalce替换已经存在的key值
不存在,则错误
存在,则修改
replace key 0 0 3
123
STORED
replace key1 0 0 3
123
NOT_STORED //key1不存在
///////////////////////////////////
set设置键
存在,则修改
不存在,则添加,总和add和repalce功能。
set key 0 0 2
12
STORED
/////////////////////////////////////
append :是在现有缓存数据后面新增数据。如果 key 不存在,服务器响应 NOT_STORED
get key
VALUE key 0 3
123
END
append key 0 0 2
45
STORED
get key
VALUE key 0 5
12345
END
prepend 是在现有缓存数据前面新增数据。如果 key 不存在,服务器响应 NOT_STORED
get key
VALUE key 0 5
12345
prepend key 0 0 7
prepend
STORED
get key
VALUE key 0 12
prepend12345
END
prepend key1 0 0 1
1
NOT_STORED
////////////////////////////////////////////////////////////////////
incr,decr 命令:增加/减少值的大小
如果缓存数据中存储的是数字形式的字符串,则可以使用incr/decr对数据进行递增和递减操作,服务器响应操作过的结果。操作后的值不会为负数。
格式: incr/decr key number\r\n
add key 0 0 2
10
STORED
incr key 1 // 递增 1
11
decr key 2 // 递减 2
9
add key1 0 0 2
aa
STORED
incr key1 1 // 对非数字的缓存操作会返回错误
CLIENT_ERROR cannot increment or decrement non-numeric value
应用场景------秒杀功能,
一个人下单,要牵涉数据库读取,写入订单,更改库存,及事务要求, 对于传统型数据库来说,
压力是巨大的。
可以利用memcached的incr/decr功能, 在内存存储count库存量,秒杀1000台
每人抢单主要在内存操作,速度非常快,抢到count<=1000 的号人,得一个订单号,再去另一个页面慢慢支付,相当于通过这个实现了一个原子操作,因为在memcached内部访问同一个items是原子的,这种数据库分离的操作使用起来很方便。
////////////////////////////////////////////////////////////////////
stats命令
用于把memcached当前的运行信息全部统计出来。
stats item命令
用于查看条目状态。
stats slabs命令
查看内存情况
stats命令的显示如下图:
2、Libmemcached
介绍
memcached通常以服务器的形式运行,虽然称memcached是分布式数据库,但是其服务端本身不支持分布式业务,这就需要客户端自己实现分布管理以及内存池功能。Libmemcached是一个开源的Memcached客户端库,其内部实现了分布式管理、内存池等功能。通过API的形式提供出来,使用程序员可以专心上层业务逻辑,避免底层与memcached交互的细节,所以Libmemcached编译,安装之后就会以动态库的方式提供出来给程序员调用。注意链表的时候必须指定-lmemcached
。如果是QT创建工程,那么必须在*pro文件里面加入这句话LIBS += -L/usr/local/lib -lmemcached
。
Libmemcached特性:
- 异步和同步传输支持。
- 支持一致性hash分布式算法。
- 可调哈希算法来匹配密钥。
- 访问大对象支持。
- 本地复制。
- 提供了一些管理memcached服务器的工具命令
安装
下载最新版、解压、./configure 、 make 、 make install
三步。
响应的库文件和头文件分别位于/usr/local/lib、/usr/loacl/include/libmemcached
其中头文件夹中包含有三个文件。
1、memcached.h
:库对应的c接口
2、memcached.hpp
:将对应的c接口封装成c++接口,所以c++使用起来可能更加方便
3、util.h
:基于库实现的一些高级功能,例如连接池的功能,就在这个头文件中实现。
需要使用库,包含上述响应的头文件即可,可以肯定,库的作者已经帮助我们做好了一切,我们仅仅需要调用,就可以和服务器memcached通信存值。
这里写代码片
基本函数调用说明
程序员可调用的函数头文件全部位于源代码libmemcached-1.0
目录下面。
memcached_st的创建和释放
memcached_st
结构:用于保存memcached连接对象。
memcached_return_t
结构:用于保存memcached命令执行状态。
memcached_server_st
结构:用于存储memcached服务器列表。
#include <libmemcached/memcached.h>
memcached_st* memcached_create(memcached_st *ptr)
void memcached_free(memcached_st *ptr)
memcached_st* memcached_clone(memcached_st *destination, memcached_st *source)
void memcached_servers_reset(memcached_st)
1、memcached_create()
用于创建或初始化一个memcached_st
结构体变量,当此变量内存静态分配,则传参变量指针进入初始化;当此变量动态分配,则传参NULL即可,函数内部动态分配变量内存。
2、memcached_clone()
与memcached_create()
类似但是他拷贝的是memcached_st
的默认设置和服务器列表。如果传参source
是NULL,则功能和memcached_create()
一样;否则将source
拷贝到destination
,destination
内存可静态或由函数动态分配。
3、memcached_servers_reset()
将memcached_st
里面的服务器列表清空。
4、memcached_free
用于清空memcached_st
所占用的所有内存。
5、memcached_create()
和memcached_clone()
成功则返回初始化或者动态分配的memcached_st
指针;内存分配失败则返回NULL。
memcached_server_st
libmemcached是支持服务器分布式,所以需要通过memcached_server_st
结构管理服务器列表。此结构和memcached_st
一样,只能通过库提供函数来修改。
下列函数全部用来修改memcached_server_st
数组,后续还需要将其添加到memcached_st
中,让其对服务器进行管理。
#include <libmemcached/memcached.h>
/* Server List Public functions */
void memcached_server_list_free(memcached_server_list_st ptr);
memcached_return_t memcached_server_push(memcached_st *ptr, const memcached_server_list_st list);
memcached_server_list_st memcached_server_list_append(memcached_server_list_st ptr,
const char *hostname,
in_port_t port,
memcached_return_t *error);
memcached_server_list_st memcached_server_list_append_with_weight(memcached_server_list_st ptr,
const char *hostname,
in_port_t port,
uint32_t weight,
memcached_return_t *error);
uint32_t memcached_server_list_count(const memcached_server_list_st ptr);
memcached_server_list_free
释放存储memcached_server_list_st
服务器列表数组的内存。将memcached_server_list_st
添加进memcached_st
中,会在内部重新分配实例内存,所以当push成功之后,调用函数应该在外部手动释放memcached_server_list_st
占用的内存空间。
memcached_server_list_append
将服务器添加到 memcached_server_st
数组的末尾 。如果发生错误,将返回null
,并且您传递给函数的 memcached_return_t
指针将被设置为相应的错误。如果端口值为零,则将其设置为memcached服务器的默认端口11211。
memcached_server_list_append_with_weight
同memcached_server_list_append
只是其可以指定服务器权重。
memcached_server_list_count
返回结构体中服务器列表数目。
memcached_server_push
将memcached_server_list_st
结构体与memcached_st
关联,使得memcached_st
可以管理服务器列表。
memcached_server_st与memcached_st关联
memcached_server_fn
uint32_t memcached_server_count(memcached_st *ptr)
//memcached_st中当前服务器数量。
memcached_return_t memcached_server_add(memcached_st *ptr, const char *hostname, in_port_t port)
//直接往memcached_st中添加一个服务器。
memcached_return_t memcached_server_push(memcached_st *ptr, const memcached_server_st *list)
//将memcached_server_st服务器列表,添加到memcached_st。
const memcached_instance_st * memcached_server_by_key(memcached_st *ptr, const char *key, size_t key_length, memcached_return_t *error)
//通过key添加服务器
const memcached_instance_st * memcached_server_get_last_disconnect(const memcached_st *ptr)
//获取最后一个有连接问题的服务器。
memcached_return_t memcached_server_cursor(const memcached_st *ptr, const memcached_server_fn *callback, void *context, uint32_t number_of_callbacks)
//循环遍历服务器列表,然后执行其对应的回调函数。这里又是具有回调函数的功能。
操作memcached服务器数据
1、存储数据
memcached_return_t memcached_set(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)
//向memcached写入一条数据,存在即覆盖
memcached_return_t memcached_add(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)
//不存在即插入,存在返回错误
memcached_return_t memcached_replace(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)
//存在即覆盖,不存在返回错误
memcached_return_t memcached_prepend(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)
//在原来的字符串之前添加内容
memcached_return_t memcached_append(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)
//在原来的字符串末尾添加内容
2、获取数据
char * memcached_get(memcached_st *ptr, const char *key, size_t key_length, size_t *value_length, uint32_t *flags, memcached_return_t *error)
//获取单个数据
memcached_return_t memcached_mget(memcached_st *ptr, const char * const *keys, const size_t *key_length, size_t number_of_keys)
//获取多个数据,需要注意的是,当使用多个server时mget无法通过一次条用返回不同存储在不同server上的数据
char* memcached_fetch(memcached_st *ptr,char* keys, size_t *key_length,size_t* value_length,uint32_t* flags,memcached_return_t *error)
//从上面函数的结果中提取数据,通过while循环,知道返回NULL
memcached_return_t memcached_flush(memcached_st *ptr, time_t expiration);
//清空数据
memcached_return_t memcached_delete(memcached_st *ptr, const char *key, size_t key_length, time_t expiration)
//删除数据
memcached_return_t memcached_increment(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
//将指定key的value值加offset
memcached_return_t memcached_decrement(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
//将指定key的value值减offset
添加键值对到服务器
char *key= "foo";
char *value= "value";
memcached_return_t rc= memcached_set(memc, key, strlen(key), value, value_length, (time_t)0, (uint32_t)0);
if (rc != MEMCACHED_SUCCESS)
{
... // handle failure
}
最好处理下函数的返回值,因为可以判断成功或者失败。
从服务器获取多个键值对
memcached_return_t rc;
char *keys[]= {"fudge", "son", "food"};//键
size_t key_length[]= {5, 3, 4};//键长
unsigned int x;
uint32_t flags;
char return_key[MEMCACHED_MAX_KEY];
size_t return_key_length;
char *return_value;
size_t return_value_length;
rc= memcached_mget(memc, keys, key_length, 3);
x= 0;
while ((return_value= memcached_fetch(memc, return_key, &return_key_length,
&return_value_length, &flags, &rc)))
{
free(return_value);
x++;
}
配置libmemcached
#include <libmemcached-1.0/memcached.h>
LIBMEMCACHED
memcached_st *memcached(const char *string, size_t string_length)
memcached_return_t libmemcached_check_configuration(const char *option_string, size_t length, char *error_buffer, size_t error_buffer_size)
Compile and link with -lmemcached
Libmemcached实现了用于配置和修改服务器的自定义语言。通过传入选项字符串,您可以生成一个memcached_st
对象,然后可以直接在应用程序中使用该对象。
--SERVER=<servername>:<optional_port>/?<optional_weight>
提供一个服务器名称供客户端使用。提供权重会导致每台服务器的默认权重为1的所有主机
1const char *memcached_last_error_message(memcached_st *)
用于获取服务器最新的错误信息,信息包含发送错误主机的主机名称。
递增或递减值
#include <libmemcached/memcached.h>
memcached_return_t memcached_increment(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
memcached_return_t memcached_decrement(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
memcached_return_t memcached_increment_with_initial(memcached_st *ptr, const char *key, size_t key_length, uint64_t offset, uint64_t initial, time_t expiration, uint64_t *value)
memcached_return_t memcached_decrement_with_initial(memcached_st *ptr, const char *key, size_t key_length, uint64_t offset, uint64_t initial, time_t expiration, uint64_t *value)
memcached_return_t memcached_increment_by_key(memcached_st *ptr, const char *group_key, size_t group_key_length, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
memcached_return_t memcached_decrement_by_key(memcached_st *ptr, const char *group_key, size_t group_key_length, const char *key, size_t key_length, uint32_t offset, uint64_t *value)
memcached_return_t memcached_increment_with_initial_by_key(memcached_st *ptr, const char *group_key, size_t group_key_length, const char *key, size_t key_length, uint64_t offset, uint64_t initial, time_t expiration, uint64_t *value)
memcached_return_t memcached_decrement_with_initial_by_key(memcached_st *ptr, const char *group_key, size_t group_key_length, const char *key, size_t key_length, uint64_t offset, uint64_t initial, time_t expiration, uint64_t *value)
Compile and link with -lmemcached
memcached_increment
和memcached_decrement
通过传入键、键长、offset
偏移量修改值,value
指向修改后数据地址,由调用函数分配内存,值传递结果。
memcached_increment_with_initial
和memcached_decrement_with_initial
仅仅在二进制下可用。
上述函数调用成功,返回MEMCACHED_SUCCESS
,否则返回错误。
删除键值对
#include <libmemcached/memcached.h>
memcached_return_t memcached_delete(memcached_st *ptr, const char *key, size_t key_length, time_t expiration)
memcached_return_t memcached_delete_by_key(memcached_st *ptr, const char *group_key, size_t group_key_length, const char *key, size_t key_length, time_t expiration)
从服务器删除特定的key。
确认键值对是否存在
#include <libmemcached / memcached.h>
memcached_return_t memcached_exist (memcached_st * ptr,char * key,size_t * key_length )
memcached_return_t memcached_exist_by_key (memcached_st * ptr,char * group_key,size_t * group_key_length,char * key,size_t * key_length )
确认key是否存在,存在和返回MEMCACHED_SUCCESS
,否则返回MEMCACHED_notfound
。
刷新客户端缓冲区
memcached_return_t memcached_flush_buffers(memcached_st *ptr)
将memcached_flush_buffers
与MEMCACHED_BEHAVIOR_BUFFER_REQUESTS
配合使用,发送命令非服务器,刷新服务器缓冲区。成功则返回MEMCACHED_SUCCESS
,失败则可以通过memcached_strerror()
将错误码转换为字符串。
实验一致性hash之负载均衡
通过一致性hash可以实现某个服务器挂了,快速隔离将其映射都其他服务器; 慢恢复策略,当服务器重新启动了,则慢速恢复,这个时间可以确定。保证数据的正确性。
1、一致性hash原理
若我们在后台使用Nosql集群,必然会涉及到key的分配问题,集群中某台机器宕机时如何key又该如何分配的问题。可以使用一种简单的方法,n=hash( key)%N来选择n号服务器,一切都运行正常,但是出现下述问题就很麻烦:
- 一个服务器m宕机了(在实际应用中必须要考虑这种情况),这样所有映射到服务器m的对象都会失效,怎么办,需要把m从集群中移除,这时候集群是N-1台,映射公式变成了 hash(object)%(N-1),导致每个服务器先前映射的缓存大部分都失效了。
- 由于访问加重,需要添加服务器,这时候cache 是 N+1 台,映射公式变成了 hash(object)%(N+1) ,导致每个服务器先前映射的缓存大部分都失效了。
上述意味着突然之间几乎所有的cache都失效了。对于服务器而言,这是一场灾难,洪水般的访问都会直接冲向后台服务器;
- 再来考虑一个问题,由于硬件能力越来越强,你可能想让后面添加的节点多做点活,显然上面的 hash 算法也做不到。
- 以上三个问题,可以用一致性hash算法来解决。关于一致性hash算法的理论网上很多,这里分析几种一致性hash算法的实现。
2、ketama一致性hash
//ketama一致性hash
static int
ketama_create_continuum( key_t key, char* filename )
{
unsigned int numservers = 0;//服务器数目
unsigned long memory;//总权重
serverinfo* slist;
slist = read_server_definitions( filename, &numservers, &memory );//读取服务器信息
//检查服务器数目
if ( numservers < 1 )
{
sprintf( k_error, "No valid server definitions in file %s", filename );
return 0;
}
else if ( slist == 0 ) // 若服务器信息数组为空,错误
{
/* read_server_definitions must've set error message. */
return 0;
}
/* Continuum will hold one mcs for each point on the circle: */
//开始构建hash环虚拟节点
mcs continuum[ numservers * 160 ];//默认一个服务器160个节点。因此存储hash值和服务器索引的msc数组为numservers * 160
unsigned int i, k, cont = 0;
//遍历所有服务器开始在环上部点
for( i = 0; i < numservers; i++ )
{
float pct = (float)slist[i].memory / (float)memory;//当前权重
unsigned int ks = floorf( pct * 40.0 * (float)numservers );//确定循环次数,因为一次循环hash 4次,所以这个是pct * 160* (float)numservers/4
// 计算出总次数,每次可以得到4个点
for( k = 0; k < ks; k++ )
{
char ss[30];//字符串缓存
unsigned char digest[16];//字符串长度变量
sprintf( ss, "%s-%d", slist[i].addr, k );
//"192.168.34.1-0"
ketama_md5_digest( ss, digest );
int h;
for( h = 0; h < 4; h++ )
{
// 把计算出来的连续4位的数字,进行移位。
// 把第一个数字一道一个整数的最高8位,后面的一次移动次高8位,后面一次补零,这样就得到了一个32位的整数值。移动后
continuum[cont].point = ( digest[3+h*4] << 24 )
| ( digest[2+h*4] << 16 )
| ( digest[1+h*4] << 8 )
| digest[h*4];
// 复制对应的ip地址到该点上
memcpy( continuum[cont].ip, slist[i].addr, 22 );
cont++;
//确保hash值尽量少冲突
}
}
}
free( slist );
//排序,容易查找
qsort( (void*) &continuum, cont, sizeof( mcs ), (compfn)ketama_compare );
// 到这里算法的实现就结束了,环上的点(0^32整数范围内)都已经建立起来,每个点都是0到2^32的一个整数和ip地址的结构。
// 这样查找的时候,只是需要hash(key),并在环上找到对应的数的位置,取得该节点的ip地址即可。
/// ... ...
}
unsigned int ketama_hashi( char* inString )
{
unsigned char digest[16];
// 对key的值做md5计算,得到一个有16个元素的unsigned char数组
ketama_md5_digest( inString, digest );
// 取数组中的前4个字符,并移位,形成一个整数作为hash得到的值返回
return (unsigned int)(( digest[3] << 24 )
| ( digest[2] << 16 )
| ( digest[1] << 8 )
| digest[0] );
}
//在环上查找相应的结点
mcs* ketama_get_server( char* key, ketama_continuum cont )
{
// 计算key的hash值,并保存到变量h中
unsigned int h = ketama_hashi( key );
// 该变量cont->numpoints是总的数组埋点数
int highp = cont->numpoints;
// 数组结点的值
mcs (*mcsarr)[cont->numpoints] = cont->array;
int lowp = 0, midp;
unsigned int midval, midval1;
// pide and conquer array search to find server with next biggest
// point after what this key hashes to
while ( 1 )
{
// 从数组的中间位置开始找
// 注意此时的数组是按照point的值排好序了
midp = (int)( ( lowp+highp ) / 2 );
// 若中间位置等于最大点数,直接绕回到0位置
if ( midp == cont->numpoints )
return &( (*mcsarr)[0] ); // if at the end, roll back to zeroth
// 取的中间位置的point值
midval = (*mcsarr)[midp].point;
// 再取一个值:若中间位置下标为0,直接返回0,若中间位置的下标不为0,直接返回上一个结点的point值
midval1 = midp == 0 ? 0 : (*mcsarr)[midp-1].point;
// 把h的值和取的两个值point值进行比较,若在这两个point值之间说明h值应该放在较大的那个point值的下标对应的ip地址上
if ( h <= midval && h > midval1 )
return &( (*mcsarr)[midp] );
// 否则继续2分
if ( midval < h )
lowp = midp + 1;
else
highp = midp - 1;
// 若没有找到,直接返回0位置的值,这种情况应该很少
if ( lowp > highp )
return &( (*mcsarr)[0] );
}
}
/*
添加服务器,重新构造hash环,因为虚拟节点,导致分摊了先前所有服务器的负载。
减少服务器,重新构造hash环,因为虚拟节点,所以服务器分摊了当前服务负载。
这就是虚拟节点实现了负载均衡,大部分节点缓存都是有效的。
且可以同时保证先前的缓存尽可能少的失效。
*/
3、libmemcached一致性hash
//通过服务器列表生成一致性hash环
static memcached_return_t update_continuum(Memcached *ptr)
{
uint32_t continuum_index= 0;
uint32_t pointer_counter= 0;
uint32_t pointer_per_server= MEMCACHED_POINTS_PER_SERVER;//每个服务器虚拟多少点,默认100
uint32_t pointer_per_hash= 1;
uint32_t live_servers= 0;
struct timeval now;
//......
memcached_instance_st* list= memcached_instance_list(ptr);//获取服务器列表首地址
/*
1、统计有效服务器的数目
*/
bool is_auto_ejecting= _is_auto_eject_host(ptr);
if (is_auto_ejecting)
{
live_servers= 0;
ptr->ketama.next_distribution_rebuild= 0;
for (uint32_t host_index= 0; host_index < memcached_server_count(ptr); ++host_index)
{
if (list[host_index].next_retry <= now.tv_sec)
{
live_servers++;
}
else
{
if (ptr->ketama.next_distribution_rebuild == 0 or list[host_index].next_retry < ptr->ketama.next_distribution_rebuild)
{
ptr->ketama.next_distribution_rebuild= list[host_index].next_retry;
}
}
}
}
else
{
live_servers= memcached_server_count(ptr);//总数目
}
//重新每个服务器虚拟点数,如果带有权重,则分配160个点。
uint32_t points_per_server= (uint32_t) (memcached_is_weighted_ketama(ptr) ? MEMCACHED_POINTS_PER_SERVER_KETAMA : MEMCACHED_POINTS_PER_SERVER);
if (live_servers == 0)
{
return MEMCACHED_SUCCESS;
}
/*
2、以及服务器数目及points_per_server创建存放环的内存空间
*/
if (live_servers > ptr->ketama.continuum_count)
{
memcached_continuum_item_st *new_ptr;//分配memcached_continuum_item_st数组
//MEMCACHED_CONTINUUM_ADDITION 额外多分配几个点
//为存储环上总的点数分配内存
new_ptr= libmemcached_xrealloc(ptr, ptr->ketama.continuum, (live_servers + MEMCACHED_CONTINUUM_ADDITION) * points_per_server, memcached_continuum_item_st);
if (new_ptr == 0)
{
return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
}
ptr->ketama.continuum= new_ptr;
ptr->ketama.continuum_count= live_servers + MEMCACHED_CONTINUUM_ADDITION;//环上服务器点的个数
}
assert_msg(ptr->ketama.continuum, "Programmer Error, empty ketama continuum");
/*
3、计算全部权重
*/
uint64_t total_weight= 0;
if (memcached_is_weighted_ketama(ptr))
{
for (uint32_t host_index = 0; host_index < memcached_server_count(ptr); ++host_index)
{
if (is_auto_ejecting == false or list[host_index].next_retry <= now.tv_sec)
{
total_weight += list[host_index].weight;
}
}
}
/*
4、构造hash环
*/
for (uint32_t host_index= 0; host_index < memcached_server_count(ptr); ++host_index)
{
if (is_auto_ejecting and list[host_index].next_retry > now.tv_sec)
{
continue;
}
if (memcached_is_weighted_ketama(ptr))
{
float pct= (float)list[host_index].weight / (float)total_weight;//当前服务器占的权重
pointer_per_server= (uint32_t) ((::floor((float) (pct * MEMCACHED_POINTS_PER_SERVER_KETAMA / 4 * (float)live_servers + 0.0000000001))) * 4);
//通过权重计算当前服务器在环上占的点数
pointer_per_hash= 4;//一个循环,计算4个服务器虚拟节点hash值
if (DEBUG)
{
printf("ketama_weighted:%s|%d|%llu|%u\n",
list[host_index]._hostname,
list[host_index].port(),
(unsigned long long)list[host_index].weight,
pointer_per_server);
}
}
if (ptr->distribution == MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY)
{//一般选择MEMCACHED_DISTRIBUTION_CONSISTENT算法,所以这段省略
//...............
}
else//直接到这里 MEMCACHED_DISTRIBUTION_CONSISTENT
{
//因为一次计算4个虚拟节点的hash值,所以对于服务器仅仅需要循环pointer_per_server/pointer_per_hash
for (uint32_t pointer_index= 1;
pointer_index <= pointer_per_server / pointer_per_hash;
pointer_index++)
{
char sort_host[MEMCACHED_NI_MAXHOST +1 +MEMCACHED_NI_MAXSERV +1 +MEMCACHED_NI_MAXSERV]= "";//分配存储字符串的内存
int sort_host_length;//长度,为了后续的hash
if (list[host_index].port() == MEMCACHED_DEFAULT_PORT)
{
sort_host_length= snprintf(sort_host, sizeof(sort_host),
"%s-%u",
list[host_index]._hostname,
pointer_index - 1);
//默认端口,则字符串依次是
//168.124.23.4-0
//168.124.23.4-1
//168.124.23.4-2
//168.124.23.4-3
//等等
}
else
{
sort_host_length= snprintf(sort_host, sizeof(sort_host),
"%s:%u-%u",
list[host_index]._hostname,
(uint32_t)list[host_index].port(),
pointer_index - 1);
//168.124.23.4:11213-0
//168.124.23.4:11213-1
//168.124.23.4:11213-2
//168.124.23.4:11213-3
// 等等 pointer_index-1
}
if (size_t(sort_host_length) >= sizeof(sort_host) or sort_host_length < 0)
{
return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT,
memcached_literal_param("snprintf(sizeof(sort_host)))"));
}
//带有权重,则一个字符串对应4个hash值
if (memcached_is_weighted_ketama(ptr))//支持权重,则使用ketama_server_hash一次hash4个点
{
for (uint32_t x = 0; x < pointer_per_hash; x++)//一共hash四次
{
//hash "168.124.23.4:11213-0",并将其值通过x偏移,获取4个点
uint32_t value= ketama_server_hash(sort_host, (size_t)sort_host_length, x);//如果带有权重,则用md5hash
ptr->ketama.continuum[continuum_index].index= host_index;
ptr->ketama.continuum[continuum_index++].value= value;
}
}
else//否则使用默认的hash函数 , 一个字符串对应一个hash值
{
uint32_t value= hashkit_digest(&ptr->hashkit, sort_host, (size_t)sort_host_length);//否则使用默认的hash函数
ptr->ketama.continuum[continuum_index].index= host_index;//记录服务器索引
ptr->ketama.continuum[continuum_index++].value= value;//记录服务器对应的hash值
}
}
}
pointer_counter+= pointer_per_server;
}
//从上面开始hash环构造成功,但是hash值并没有排序。
assert_msg(ptr, "Programmer Error, no valid ptr");
assert_msg(ptr->ketama.continuum, "Programmer Error, empty ketama continuum");
assert_msg(memcached_server_count(ptr) * MEMCACHED_POINTS_PER_SERVER <= MEMCACHED_CONTINUUM_SIZE, "invalid size information being given to qsort()");
ptr->ketama.continuum_points_counter= pointer_counter;
qsort(ptr->ketama.continuum, ptr->ketama.continuum_points_counter, sizeof(memcached_continuum_item_st), continuum_item_cmp);
//将对应的hash值排序 从小到大排序,每个节点上面存储了服务器ID和对应的hash值。
...
return MEMCACHED_SUCCESS;
}
4、使用一致性hash
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <string>
#include <iOStream>
#include <libmemcached/memcached.h>//C 接口
#include <libmemcached/util.h> //工具,包括pool功能
#include<libmemcached/memcached.hpp> //将c接口封装成的c++接口,
using namespace std;
/*
测试一致性hash
*/
int main(int argc, char *argv[])
{
memcached_st *memc;
memcached_return rc;
memcached_server_st *servers;
memc = memcached_create(NULL);
servers = memcached_server_list_append_with_weight(NULL, (char*)"localhost", 11211, 50 , &rc);
servers = memcached_server_list_append_with_weight(servers, (char*)"localhost", 11212 , 50 , &rc);
rc = memcached_server_push(memc, servers);
memcached_server_free(servers);
memcached_behavior_set(memc,MEMCACHED_BEHAVIOR_DISTRIBUTION,MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA);
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, true) ;//set失败,则快隔离将其标记为dead
memcached_behavior_set(memc , MEMCACHED_BEHAVIOR_DEAD_TIMEOUT , 5);//慢恢复,每隔多少s试探一次即可。
int time_sl = 0;
int times = 0;
while(times++ < 100000)
{
//save data
const char *keys[]= {"key1", "key2", "key3","key4" ,"key5","key6","key7"};
const size_t key_length[]= {4, 4, 4, 4 ,4 ,4 ,4 };
const char *values[] = {"key1Value", "key2Value", "key3Value","key4Value" , "key5Value" , "key6Value" , "key7Value"};
size_t val_length[]= {sizeof(values[0]), sizeof(values[1]), sizeof(values[2]), sizeof(values[3]),sizeof(values[4]),sizeof(values[5]),sizeof(values[6])};
for (int i = 0 ; i < 7; i++)
{
rc = memcached_set(memc, keys[i], key_length[i], values[i], val_length[i], (time_t)180,(uint32_t)0);
printf("key: %s rc:%s\n", keys[i], memcached_strerror(memc, rc)); // 输出状态
char* result = memcached_get(memc, keys[i], key_length[i], NULL, NULL, &rc);
if (rc == MEMCACHED_SUCCESS)
{
cout << "Get value:" << result << " sucessful!" << endl;
}else
cout << memcached_strerror(memc, rc) << endl;
}
printf("time: %d\n", time_sl++);
sleep(1);
}
memcached_free(memc);
return 0;
}
首先看libmemcached中最重要的结构体。
//通过此结构管理与服务器连接的全部信息,静态通常不有用户修改。
struct memcached_st {
/**
@note these are static and should not change without a call to behavior.
*/
/*
位域,节约内存的标志位,将多个字段保存在一个字节中。简化了对屏蔽码的一些操作。
:1 表示占用1位。直接通过结构体操作,简化了屏蔽码的操作。
*/
struct {
bool is_purging:1;
bool is_processing_input:1;
bool is_time_for_rebuild:1;
bool is_parsing:1;
} state;
//同样的标志位
struct {
// Everything below here is pretty static.
bool auto_eject_hosts:1;
bool binary_protocol:1;
bool buffer_requests:1;
bool hash_with_namespace:1; //默认初始化为false
bool no_block:1; // Don't block
bool reply:1;
bool randomize_replica_read:1;
bool support_cas:1;
bool tcp_nodelay:1;
bool use_sort_hosts:1;
bool use_udp:1;
bool verify_key:1;
bool tcp_keepalive:1;
bool is_aes:1;
bool is_fetching_version:1;
bool not_used:1;
} flags;//一些有用的标志位
memcached_server_distribution_t distribution;//选择何种一致性hash分布式算法
/*
enum memcached_server_distribution_t {
MEMCACHED_DISTRIBUTION_MODULA, //取模
MEMCACHED_DISTRIBUTION_CONSISTENT, //KETAMA一致性hash算法
MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA,//和2一模一样
MEMCACHED_DISTRIBUTION_RANDOM,
MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY,
MEMCACHED_DISTRIBUTION_CONSISTENT_WEIGHTED,
MEMCACHED_DISTRIBUTION_virtual_BUCKET,//带有虚拟节点的一致性hash算法
MEMCACHED_DISTRIBUTION_CONSISTENT_MAX
};
*/
hashkit_st hashkit;//使用的hash函数
struct {
unsigned int version;
} server_info;
uint32_t number_of_hosts;//服务器主机数目
memcached_instance_st *servers; //服务器列表实例数组
memcached_instance_st *last_disconnected_server;//最新端开的服务器实例
int32_t snd_timeout;
int32_t rcv_timeout;
uint32_t server_failure_limit;
uint32_t server_timeout_limit;
uint32_t io_msg_watermark;
uint32_t io_bytes_watermark;
uint32_t io_key_prefetch;
uint32_t tcp_keepidle;
int32_t poll_timeout;
int32_t connect_timeout; //因为默认socket是阻塞的,所以connect()之后肯定立即返回EINPROCESS,则用poll监听可读超时多久。
int32_t retry_timeout;
int32_t dead_timeout;
int send_size;
int recv_size;
void *user_data;
uint64_t query_id;
uint32_t number_of_replicas;
memcached_result_st result;
struct {
bool weighted_;//释放支持权重?
uint32_t continuum_count; // Ketama
uint32_t continuum_points_counter; // Ketama
time_t next_distribution_rebuild; // Ketama
struct memcached_continuum_item_st *continuum; // Ketama 一致性hash环中的元素
} ketama;//一致性hash算法ketama参数
/*
struct memcached_continuum_item_st
{
uint32_t index;
uint32_t value;
};
*/
struct memcached_virtual_bucket_t *virtual_bucket;
struct memcached_allocator_t allocators;//选定的内存分配器。
memcached_clone_fn on_clone;
memcached_cleanup_fn on_cleanup;
memcached_trigger_key_fn get_key_failure;
memcached_trigger_delete_key_fn delete_trigger;
memcached_callback_st *callbacks;
struct memcached_sasl_st sasl;
struct memcached_error_t *error_messages;
struct memcached_array_st *_namespace;
struct {
uint32_t initial_pool_size; //初始化连接池的大小
uint32_t max_pool_size; //连接池最大数
int32_t version; // This is used by pool and others to determine if the memcached_st is out of date.
struct memcached_array_st *filename;
} configure; //连接池结构体
struct {
bool is_allocated:1;
} options;
};
memcached_st
中包含了操作连接的重要信息。其中和服务器有关的就是指向服务器列表的指针servers
。每次key过来,则通过hash,通过hash值在hash环上选择合适的服务器(通过获取服务器列表的索引即可,使用服务器server[i])发送命令即可。当某个服务器宕机了,这次发送肯定会失败,然后将此服务器标记为dead,然后重新更新hash环,之后其他服务器就会均衡其负载,这就叫做快速隔离。前面设定了MEMCACHED_BEHAVIOR_DEAD_TIMEOUT
,在某个服务器dead后,会经过设定的秒数后会重试。如果成功,则标记此服务器可用,重新更新hash环,至此此服务器均衡其他服务器的负载,这就叫做慢恢复。下面是内存布局图。
创建连接池
1、连接池必要性
TCP建立连接和断开都是一个费时的活动,每次都得花费一定的时间,而且系统还要动态分配和释放内存资源。当并行性提高,直接造成服务器响应缓慢甚至崩溃。于是提出了连接池的概率解决上述问题。
连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
连接池方法:
全局的队列及互斥量就可以搞定一个连接池。libmemcached通用实现了连接池的功能,使得用起来非常方便。
2、keepalive
首先套接字是一种非常珍贵的资源。占用不释放会造成大量的资源浪费。
当连接双方通过三次握手成功后,双方的连接套接字以及缓冲区都会一直保留,直到断开或者错误发生,这样有可能浪费很多内存空间。
如果应用程序或者上层协议一直不发送数据,或者隔很长时间才发送一次数据,当连接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,连接还需不需要保持,这种情况在TCP协议设计中是需要考虑到。
于是就有了套接字keepalive选项,通过在一定时间内发送探测报文,试探对方是否在线,进而确定连接是否需要保持。
给一个TCP套接字设置保持存活(keepalive) 选项后,如果2小时内在该套接字的任一方向上都没有数据交换, TCP就自动给对端发送一个保持存活探测分节(keep-alive probe)。这是一个对踹必须响应的TCP分节,它会导致以下三种情况之一。
- 对端以期望的ACK响应,用户进程得不到通知(因为一切正常)。在又经过仍无动静的2小时后,TCP将再次发出一个探测分节,用于探测对方是否在线。
- 对端以RST响应,对端告知本端连接己崩溃或重新启动。该套接字的待处理错设
被置为ECONNREST,套接字本身则被关闭,如果需要发送数据则必须重新连接
- 对端对保持存活探测分节没有任何响应。源自Berkeley的TCP将另外发送8个探测分节,两两相隔75秒,试图得到一个响应。TCP在发出第一个探测分节后11分15秒内若没有得到任何响应则放弃,错误被设置为ETIMEOUT,套接字本身关闭,和该连接有关的内存释放。
- 本地项一般由服务器使用,不过客户也可以使用。服务器使用本选项是因为它们花大部分时间阻塞在等待TCP连接的输入上,也就是说在等待客户的请求。然而如果客户主机连接掉线、掉电或系统崩溃,服务器进程将永远不会知道,并将继续等待永远不会到达的输入,打开响应的套接字,占用内存和套接字数目,我们称这种情况为半开连接,保持存活选项将检测出这些半开连接并终止它们。
心跳包的设计。
3、libmemcached连接池
4、连接池使用
#define POOL_SIZE 100
int main(void)
{
int times = 0;
memcached_st *memc;
memcached_return rc;
memcached_server_st *servers;//临时结构体,最终将其数值赋值给memcached_instance_st相应成员
memcached_pool_st* ConnectionPool;
memc = memcached_create(NULL);
servers = memcached_server_list_append_with_weight(NULL, (char*)"localhost", 11211, 50 , &rc);
servers = memcached_server_list_append_with_weight(servers, (char*)"localhost", 11212 , 50 , &rc);
rc = memcached_server_push(memc, servers);//重点看一致性hash如何实现的。
memcached_server_free(servers);
ConnectionPool = memcached_pool_create(memc , 5 , POOL_SIZE);//初始大小,和最大值,连接池会动态扩展
//牛逼 一致性hash算法
memcached_pool_behavior_set(ConnectionPool,MEMCACHED_BEHAVIOR_DISTRIBUTION,MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA);
memcached_pool_behavior_set(ConnectionPool, MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, true);//快隔离,失败一次,将其标记为dead
memcached_pool_behavior_set(ConnectionPool , MEMCACHED_BEHAVIOR_DEAD_TIMEOUT , 5);//慢恢复,每隔多少s
while(times++ < POOL_SIZE)
{
//save data
memc = memcached_pool_pop(ConnectionPool , NULL , &rc);//从内存池取出连接
if (rc != MEMCACHED_SUCCESS)//内存池弹出失败,则直接返回
return 0;
const char *keys[]= {"key1", "key2", "key3","key4" ,"key5","key6","key7"};
const size_t key_length[]= {4, 4, 4, 4 ,4 ,4 ,4 };
const char *values[] = {"key1Value", "key2Value", "key3Value","key4Value" , "key5Value" , "key6Value" , "key7Value"};
size_t val_length[]= {sizeof(values[0]), sizeof(values[1]), sizeof(values[2]), sizeof(values[3]),sizeof(values[4]),sizeof(values[5]),sizeof(values[6])};
for (int i = 0 ; i < 7; i++)
{
rc = memcached_set(memc, keys[i], key_length[i], values[i], val_length[i], (time_t)180,(uint32_t)0);
printf("key: %s rc:%s\n", keys[i], memcached_strerror(memc, rc)); // 输出状态
char* result = memcached_get(memc, keys[i], key_length[i], NULL, NULL, &rc);
if (rc == MEMCACHED_SUCCESS)
cout << "Get value:" << result << " sucessful!" << endl;
else
cout << memcached_strerror(memc, rc) << endl;
}
memcached_pool_push(ConnectionPool , memc);//归还给内存池
printf("time: %d\n", times);
sleep(2);
}
memcached_pool_destroy(ConnectionPool);
return 0;
}
//连接池类
struct memcached_pool_st
{
pthread_mutex_t mutex;
pthread_cond_t cond;
memcached_st *master;//总得连接,管理多个server_pool[]。pop则从pool中弹出一个,push则压入连接。
memcached_st **server_pool;//指向对应的连接,每个连接一个memcached_st。
int firstfree;
const uint32_t size;
uint32_t current_size;
bool _owns_master;
struct timespec _timeout;
memcached_pool_st(memcached_st *master_arg, size_t max_arg) :
master(master_arg),
server_pool(NULL),
firstfree(-1),
size(uint32_t(max_arg)),
current_size(0),
_owns_master(false)
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
_timeout.tv_sec= 5;
_timeout.tv_nsec= 0;
}
const struct timespec& timeout() const
{
return _timeout;
}
bool release(memcached_st*, memcached_return_t& rc);
memcached_st *fetch(memcached_return_t& rc);
memcached_st *fetch(const struct timespec&, memcached_return_t& rc);
bool init(uint32_t initial);
~memcached_pool_st()//析构函数
{
for (int x= 0; x <= firstfree; ++x)
{
memcached_free(server_pool[x]);
server_pool[x]= NULL;
}
int error;
if ((error= pthread_mutex_destroy(&mutex)) != 0)
{
assert_vmsg(error != 0, "pthread_mutex_destroy() %s(%d)", strerror(error), error);
}
if ((error= pthread_cond_destroy(&cond)) != 0)
{
assert_vmsg(error != 0, "pthread_cond_destroy() %s", strerror(error));
}
delete [] server_pool;
if (_owns_master)
{
memcached_free(master);
}
}
void increment_version()
{
++master->configure.version;
}
bool compare_version(const memcached_st *arg) const
{
return (arg->configure.version == version());
}
int32_t version() const
{
return master->configure.version;
}
};
连接池的实现其实也很简答,首先创建一个memcached_st
结构体,并初始化其中的服务器列表,于是这个memcached_st
其实就可以与服务器连接了,为了创建连接池,然后通过memcached_pool_create(memc , 5 , POOL_SIZE)
在内部调用memcached_clone
将memc复制5个,也就是重新创建memcached_st
结构体,并且通过memc里面的数值初始化新创建的结构体,那么先前的设置,以及服务器列表都初始化成功。后续需用使用,则pop
一个memcached_st结构体即可,然后重新前一节的过程。假如服务器有两个,连接池大小为3个,那么连接都成功使用后,在内存构造如下图。
相关阅读
Memcache是什么?Memcache是一个自由和开放源代码、高性能、分配的内存对象缓存系统。用于加速动态web应用程序,减轻数据库负载。它