使用memcached进行内存缓存
通常的网页缓存方式有动态缓存和静态缓存等几种,在ASP.NET中已经可以实现对页面局部进行缓 存,而使用memcached的缓存比ASP.NET的局部缓存更加灵活,可以缓存任意的对象,不管是否在页面上输出。而memcached最大的优点是 可以分布式的部署,这对于大规模应用来说也是必不可少的要求。
LiveJournal.com使用了memcached在前端进行缓存,取得了良好的效果,而像wikipedia,sourceforge等也采用了或即将采用memcached作为缓存工具。memcached可以大规模网站应用发挥巨大的作用。
Memcached是什么?
Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
Memcached由Danga Interactive开发,用于提升LiveJournal.com访问速度的。LJ每秒动态页面访问量几千次,用户700万。Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。
如何使用memcached-Server端?
在服务端运行:
# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211
这将会启动一个占用2G内存的进程,并打开11211端口用于接收请求。由于32位系统只能处理4G内存的寻址,所以在大于4G内存使用PAE的32位服务器上可以运行2-3个进程,并在不同端口进行监听。
如何使用memcached-Client端?
在应用端包含一个用于描述Client的Class后,就可以直接使用,非常简单。
PHP Example:
$options["servers"] = array("192.168.1.41:11211",
"192.168.1.42:11212");
$options["debug"] = false;
$memc = new MemCachedClient($options);
$myarr = array("one","two", 3);
$memc->set("key_one", $myarr);
$val = $memc->get("key_one");
print $val[0]."\n"; // prints ‘one‘
print $val[1]."\n"; // prints ‘two‘
print $val[2]."\n"; // prints 3
为什么不使用数据库做这些?
暂且不考虑使用什么样的数据库(MS-SQL, Oracle, Postgres, MysQL-InnoDB,
etc..), 实现事务(ACID,Atomicity,
Consistency, Isolation, and Durability )需要大量开销,特别当使用到硬盘的时候,这就意味着查询可能会阻塞。当使用不包含事务的数据库(例如Mysql-MyISAM),上面的开销不存在,但 读线程又可能会被写线程阻塞。
Memcached从不阻塞,速度非常快。
为什么不使用共享内存?
最初的缓存做法是在线程内对对象进行缓存,但这样进程间就无法共享缓存,命中率非常低,导致缓存效率极低。后来出现了共享内存的缓存,多个进程或者线程共享同一块缓存,但毕竟还是只能局限在一台机器上,多台机器做相同的缓存同样是一种资源的浪费,而且命中率也比较低。
Memcached Server和Clients共同工作,实现跨服务器分布式的全局的缓存。并且可以与Web Server共同工作,Web Server对CPU要求高,对内存要求低,Memcached Server对CPU要求低,对内存要求高,所以可以搭配使用。
Mysql
4.x的缓存怎么样?
Mysql查询缓存不是很理想,因为以下几点:
当指定的表发生更新后,查询缓存会被清空。在一个大负载的系统上这样的事情发生的非常频繁,导致查询缓存效率非常低,有的情况下甚至还不如不开,因为它对cache的管理还是会有开销。
在32位机器上,Mysql对内存的操作还是被限制在4G以内,但memcached可以分布开,内存规模理论上不受限制。
Mysql上的是查询缓存,而不是对象缓存,如果在查询后还需要大量其它操作,查询缓存就帮不上忙了。
如果要缓存的数据不大,并且查询的不是非常频繁,这样的情况下可以用Mysql 查询缓存,不然的话memcached更好。
数据库同步怎么样?
这里的数据库同步是指的类似Mysql Master-Slave模式的靠日志同步实现数据库同步的机制。
你可以分布读操作,但无法分布写操作,但写操作的同步需要消耗大量的资源,而且这个开销是随着slave服务器的增长而不断增长的。
下一步是要对数据库进行水平切分,从而让不同的数据分布到不同的数据库服务器组上,从而实现分布的读写,这需要在应用中实现根据不同的数据连接不同的数据库。
当这一模式工作后(我们也推荐这样做),更多的数据库导致更多的让人头疼的硬件错误。
Memcached可以有效的降低对数据库的访问,让数据库用主要的精力来做不频繁的写操作,而这是数据库自己控制的,很少会自己阻塞 自己。
Memcached快吗?
非常快,它使用libevent,可以应付任意数量打开的连接(使用epoll,而非poll),使用非阻塞网络IO,分布式散列对象到不同的服务器,查询复杂度是O(1)。
我是mixi株式会社 开发部系统运营组的长野。日常负责程序的运营。从今天开始,将分几次针对最近在Web应用的可扩展性领域的热门话题memcached,与我公司开发部研究开发组的前坂一起,说明其内部结构和使用。
memcached是什么?
memcached 是以LiveJournal
旗下Danga Interactive 公司的Brad Fitzpatric
为首开发的一款软件。现在已成为 mixi 、 hatena 、
Facebook
、 Vox 、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
图1 一般情况下memcached的用途
memcached的特征
memcached作为高速运行的分布式缓存服务器,具有以下的特点。
- 协议简单
- 基于libevent的事件处理
- 内置内存存储方式
- memcached不互相通信的分布式
协议简单
memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据、取得数据。下面是例子。
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is ‘^]‘.
set foo 0 0 3 (保存命令)
bar (数据)
STORED (结果)
get foo (取得命令)
VALUE foo 0 3 (数据)
bar (数据)
协议文档位于memcached的源代码内,也可以参考以下的URL。
基于libevent的事件处理
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数 增加,也能发挥O(1)的性能。 memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。关于事件处理这里就不再详细介 绍,可以参考Dan Kegel的The C10K Problem。
- libevent : http://www.monkey.org/~provos/libevent/
- The C10K Problem : http://www.kegel.com/c10k.html
内置内存存储方式
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启 memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。关于内存存储的详细信息,本连载的第二讲以后前坂会进行介绍,请届 时参考。
memcached不互相通信的分布式
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现。本连载也将介绍memcached的分布式。
图2 memcached的分布式
接下来简单介绍一下memcached的使用方法。
安装memcached
memcached的安装比较简单,这里稍加说明。
memcached支持许多平台。
- Linux
- FreeBSD
- Solaris (memcached 1.2.5以上版本)
- Mac OS X
另外也能安装在Windows上。这里使用Fedora Core 8进行说明。
memcached的安装
运行memcached需要本文开头介绍的libevent库。Fedora 8中有现成的rpm包,通过yum命令安装即可。
$ sudo yum install libevent libevent-devel
memcached的源代码可以从memcached网站上下载。本文执笔时的最新版本为1.2.5。 Fedora 8虽然也包含了memcached的rpm,但版本比较老。因为源代码安装并不困难,这里就不使用rpm了。
- 下载memcached :http://www.danga.com/memcached/download.bml
memcached安装与一般应用程序相同,configure、make、make install就行了。
$ wget http://www.danga.com/memcached/dist/memcached-1.2.5.tar.gz
$ tar zxf memcached-1.2.5.tar.gz
$ cd memcached-1.2.5
$ ./configure
$ make
$ sudo make install
默认情况下memcached安装到/usr/local/bin下。
memcached的启动
从终端输入以下命令,启动memcached。
$ /usr/local/bin/memcached -p 11211 -m 64m -vv
slab class 1: chunk size 88 perslab 11915
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 144 perslab 7281
中间省略
slab class 38: chunk size 391224 perslab 2
slab class 39: chunk size 489032 perslab 2
<23 server listening
<24 send buffer was 110592, now 268435456
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
<24 server listening (udp)
这里显示了调试信息。这样就在前台启动了memcached,监听TCP端口11211 最大内存使用量为64M。调试信息的内容大部分是关于存储的信息,下次连载时具体说明。
作为daemon后台启动时,只需
$ /usr/local/bin/memcached -p 11211 -m 64m -d
这里使用的memcached启动选项的内容如下。
选项 |
说明 |
-p |
使用的TCP端口。默认为11211 |
-m |
最大内存大小。默认为64M |
-vv |
用very vrebose模式启动,调试信息和错误输出到控制台 |
-d |
作为daemon在后台启动 |
上面四个是常用的启动选项,其他还有很多,通过
$ /usr/local/bin/memcached -h
命令可以显示。许多选项可以改变memcached的各种行为,推荐读一读。
用客户端连接
许多语言都实现了连接memcached的客户端,其中以Perl、PHP为主。仅仅memcached网站上列出的语言就有
- Perl
- PHP
- Python
- Ruby
- C#
- C/C++
- Lua
等等。
- memcached客户端API :http://www.danga.com/memcached/apis.bml
这里介绍通过mixi正在使用的Perl库链接memcached的方法。
使用Cache::Memcached
Perl的memcached客户端有
- Cache::Memcached
- Cache::Memcached::Fast
- Cache::Memcached::libmemcached
等几个CPAN模块。这里介绍的Cache::Memcached是memcached的作者Brad Fitzpatric的作品,应该算是memcached的客户端中应用最为广泛的模块了。
- Cache::Memcached - search.cpan.org : http://search.cpan.org/dist/Cache-Memcached/
使用Cache::Memcached连接memcached
下面的源代码为通过Cache::Memcached连接刚才启动的memcached的例子。
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
my $key = "foo";
my $value = "bar";
my $expires = 3600; # 1 hour
my $memcached = Cache::Memcached->new({
servers => ["127.0.0.1:11211"],
compress_threshold => 10_000
});
$memcached->add($key, $value, $expires);
my $ret = $memcached->get($key);
print "$ret\n";
在这里,为Cache::Memcached指定了memcached服务器的IP地址和一个选项,以生成实例。 Cache::Memcached常用的选项如下所示。
选项 |
说明 |
servers |
用数组指定memcached服务器和端口 |
compress_threshold |
数据压缩时使用的值 |
namespace |
指定添加到键的前缀 |
另外,Cache::Memcached通过Storable模块可以将Perl的复杂数据序列化之后再保存,因此散列、数组、对象等都可以直接保存到memcached中。
保存数据
向memcached保存数据的方法有
- add
- replace
- set 它们的使用方法都相同:
my $add = $memcached->add( ‘键‘, ‘值‘, ‘期限‘ );
my $replace = $memcached->replace( ‘键‘, ‘值‘, ‘期限‘ );
my $set = $memcached->set( ‘键‘, ‘值‘, ‘期限‘ );
向memcached保存数据时可以指定期限(秒)。不指定期限时,memcached按照LRU算法保存数据。这三个方法的区别如下:
选项 |
说明 |
add |
仅当存储空间中不存在键相同的数据时才保存 |
replace |
仅当存储空间中存在键相同的数据时才保存 |
set |
与add和replace不同,无论何时都保存 |
获取数据
获取数据可以使用get和get_multi方法。
my $val = $memcached->get(‘键‘);
my $val = $memcached->get_multi(‘键1‘, ‘键2‘, ‘键3‘, ‘键4‘, ‘键5‘);
一次取得多条数据时使用get_multi。get_multi可以非同步地同时取得多个键值,其速度要比循环调用get快数十倍。
删除数据
删除数据使用delete方法,不过它有个独特的功能。
$memcached->delete(‘键‘, ‘阻塞时间(秒)‘);
删除第一个参数指定的键的数据。第二个参数指定一个时间值,可以禁止使用同样的键保存新数据。此功能可以用于防止缓存数据的不完整。但是要注意,set函数忽视该阻塞,照常保存数据
增一和减一操作
可以将memcached上特定的键值作为计数器使用。
my $ret = $memcached->incr(‘键‘);
$memcached->add(‘键‘, 0) unless defined $ret;
增一和减一是原子操作,但未设置初始值时,不会自动赋成0。因此,应当进行错误检查,必要时加入初始化操作。而且,服务器端也不会对超过2<sup>32</sup>时的行为进行检查。
总结
这次简单介绍了memcached,以及它的安装方法、Perl客户端Cache::Memcached的用法。只要知道,memcached的使用方法十分简单就足够了。
下次由前坂来说明memcached的内部结构。了解memcached的内部构造,就能知道如何使用memcached才能使Web应用的速度更上一层楼。欢迎继续阅读下一章。
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载。缓存是解决这个问题的好办法。
Memcached是什么?
Memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
Memcached能缓存什么?
通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
Memcached快么?
非常快。Memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O, 对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配器和哈希表,
因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1).。
Danga Interactive为提升Danga Interactive的速度研发了Memcached。目前,LiveJournal.com每天已经在向一百万用户提供多达两千万次的页面访问。而这 些,是由一个由web服务器和数据库服务器组成的集群完成的。Memcached几乎完全放弃了任何数据都从数据库读取的方式,同时,它还缩短了用户查看
页面的速度、更好的资源分配方式,以及Memcache失效时对数据库的访问速度。
Memcached的特点
Memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问,
因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。
Memcached的使用
一 、Memcached服务器端的安装 (此处将其作为系统服务安装)
下载文件:memcached 1.2.1 for Win32
binaries (Dec 23, 2006)
1 解压缩文件到c:\memcached
2 命令行输入 ‘c:\memcached\memcached.exe -d
install‘
3 命令行输入 ‘c:\memcached\memcached.exe -d
start‘ ,该命令启动 Memcached ,默认监听端口为 11211
通过 memcached.exe -h 可以查看其帮助
二、客户端使用
下载memcached java client:http://www.whalin.com/memcached/#download
1 解压后将java_memcached-release_2.0.1.jar jar包添加到工程的classpath中
2 利用memcached java
client 一个简单的应用
Java代码
- package com.danga.MemCached.test;
- import java.util.Date;
- import com.danga.MemCached.MemCachedClient;
- import com.danga.MemCached.SockIOPool;
- public class Test {
- protected static MemCachedClient mcc = new MemCachedClient();
- static {
- String[] servers ={"192.168.40.4:12000"};
- Integer[] weights = { 3 };
- //创建一个实例对象SockIOPool
- SockIOPool pool = SockIOPool.getInstance();
- // set the servers and the weights
- //设置Memcached Server
- pool.setServers( servers );
- pool.setWeights( weights );
- // set some basic pool settings
- // 5 initial, 5 min, and 250 max conns
- // and set the max idle time for a conn
- // to 6 hours
- pool.setInitConn( 5 );
- pool.setMinConn( 5 );
- pool.setMaxConn( 250 );
- pool.setMaxIdle( 1000 * 60 * 60 * 6 );
- // set the sleep for the maint thread
- // it will wake up every x seconds and
- // maintain the pool size
- pool.setMaintSleep( 30 );
- // Tcp的规则就是在发送一个包之前,本地机器会等待远程主机
- // 对上一次发送的包的确认信息到来;这个方法就可以关闭套接字的缓存,
- // 以至这个包准备好了就发;
- pool.setNagle( false );
- //连接建立后对超时的控制
- pool.setSocketTO( 3000 );
- //连接建立时对超时的控制
- pool.setSocketConnectTO( 0 );
- // initialize the connection pool
- //初始化一些值并与MemcachedServer段建立连接
- pool.initialize();
- // lets set some compression on for the client
- // compress anything larger than 64k
- mcc.setCompressEnable( true );
- mcc.setCompressThreshold( 64 * 1024 );
- }
- public static void bulidCache(){
- //set(key,value,Date) ,Date是一个过期时间,如果想让这个过期时间生效的话,这里传递的new Date(long date) 中参数date,需要是个大于或等于1000的值。
- //因为java client的实现源码里是这样实现的 expiry.getTime() / 1000 ,也就是说,如果 小于1000的值,除以1000以后都是0,即永不过期
- mcc.set( "test", "This is a test String" ,new Date(11211));
- //十秒后过期
- }
- public static void output() {
- //从cache里取值
- String value = (String) mcc.get( "test" );
- System.out.println(value);
- }
- public static void main(String[] args){
- bulidCache();
- output();
- }
- }
79.Memcached官方:http://danga.com/memcached/
80.关于Memcached的介绍请参考:Memcached深度分析
81.下载Windows的Server端
82.下载地址:http://code.jellycan.com/memcached/
83.安装Memcache
Server(也可以不安装直接启动)
84.1. 下载memcached的windows稳定版,解压放某个盘下面,比如在c:\memcached
2. 在CMD下输入
"c:\memcached\memcached.exe -d install" 安装.
3. 再输入:"c:\memcached\memcached.exe -d start" 启动。NOTE: 以后memcached将作为windows的一个服务每次开机时自动启动。这样服务器端已经安装完毕了。
85.如果下载的是二进制的版本,直接运行就可以了,可以加上参数来加以设置。
86.
常用设置:
-p <num> 监听的端口
-l <ip_addr> 连接的IP地址, 默认是本机
-d start 启动memcached服务
-d restart 重起memcached服务
-d stop|shutdown 关闭正在运行的memcached服务
-d install 安装memcached服务
-d uninstall 卸载memcached服务
-u <username> 以<username>的身份运行 (仅在以root运行的时候有效)
-m <num> 最大内存使用,单位MB。默认64MB
-M
内存耗尽时返回错误,而不是删除项
-c <num> 最大同时连接数,默认是1024
-f <factor> 块大小增长因子,默认是1.25
-n <bytes> 最小分配空间,key+value+flags默认是48
-h
显示帮助
87.然后就可以用.net 的memcached客户端来试一下了。
88.C# 下可用的API(每个客户端API中都有详细的说明和注释)
89.https://sourceforge.net/projects/memcacheddotnet/
http://www.codeplex.com/EnyimMemcached/
- Client developed in .NET 2.0 keeping performance and extensibility in
90.mind.
(Supports consistent hashing.)
http://code.google.com/p/beitmemcached/
- Client developed by BeIT with many new features
91.Memcached深度分析
92.Memcached是danga.com(运营LiveJournal的技术团队)开发的一套分布式内存对象缓存系统,用于在动态系统中减 少数据库负载,提升性能。关于这个东西,相信很多人都用过,本文意在通过对memcached的实现及代码分析,获得对这个出色的开源软件更深入的了解, 并可以根据我们的需要对其进行更进一步的优化。末了将通过对BSM_Memcache扩展的分析,加深对memcached的使用方式理解。
93.本文的部分内容可能需要比较好的数学基础作为辅助。
94.◎Memcached是什么
95.在阐述这个问题之前,我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用,虽然 memcached使用了同样的“Key=>Value”方式组织数据,但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是 分布式的,也就是说它不是本地的。它基于网络连接(当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程
(Daemon方式)。
96.Memcached使用libevent库实现网络连接服务,理论上可以处理无限多的连接,但是它和Apache不同,它更多的时候是面向 稳定的持续连接的,所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200,这和Linux线程能力有关系,这个数值 是可以调整的。关于libevent可以参考相关文档。 Memcached内存使用方式也和APC不同。APC是基于共享内存和MMAP的,memcachd有自己的内存分配算法和管理方式,它和共享内存没有
关系,也没有共享内存的限制,通常情况下,每个memcached进程可以管理2GB的内存空间,如果需要更多的空间,可以增加进程数。
97.◎Memcached适合什么场合
98.在很多时候,memcached都被滥用了,这当然少不了对它的抱怨。我经常在论坛上看见有人发贴,类似于“如何提高效率”,回复是“用memcached”,至于怎么用,用在哪里,用来干什么一句没有。memcached不是万能的,它也不是适用在所有场合。
99.Memcached是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的 应用,memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX本地连接也一样。 在我之前的测试数据中显示,memcached本地读写速度要比直接PHP内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。可见,如果只是
本地级缓存,使用memcached是非常不划算的。
- Memcached在很多时候都是作为数据库前端cache使用的。因为它比数据库少了很多SQL解析、磁盘操作等开销,而且它是使用内存 来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的,memcached可以大大降低数据库压力,使系统 执行效率提升。另外,memcached也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在 memcached中,被多个应用共享。
- 需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以 memcached不能用来持久保存数据。很多人的错误理解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用
内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接 的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。 - ◎Memcached的工作方式
- 以下的部分中,读者最好能准备一份memcached的源代码。
- Memcached是传统的网络服务程序,如果启动的时候使用了-d参数,它会以守护进程的方式执行。创建守护进程由daemon.c完成,这个程序只有一个daemon函数,这个函数很简单(如无特殊说明,代码以1.2.1为准):
105.
#include
<fcntl.h>
106.
#include
<stdlib.h>
107.
#include
<unistd.h>
- 108.
109.
int
daemon(nochdir, noclose)
- 110.
{ int
nochdir, noclose; - 111.
- 112.
int fd; - 113.
- 114.
switch (fork()) { - 115.
case -1: - 116.
return (-1); - 117.
case 0: - 118.
break; - 119.
default: - 120.
_exit(0); - 121.
} - 122.
- 123.
if (setsid() == -1) - 124.
return (-1); - 125.
- 126.
if (!nochdir) - 127.
(void)chdir("/"); - 128.
- 129.
if (!noclose && (fd =
open("/dev/null", O_RDWR, 0)) != -1) { - 130.
(void)dup2(fd, STDIN_FILENO); - 131.
(void)dup2(fd, STDOUT_FILENO); - 132.
(void)dup2(fd, STDERR_FILENO); - 133.
if (fd > STDERR_FILENO) - 134.
(void)close(fd); - 135.
} - 136.
return (0);
137.
}
- 这个函数 fork 了整个进程之后,父进程就退出,接着重新定位 STDIN 、 STDOUT 、
STDERR 到空设备, daemon 就建立成功了。 - Memcached 本身的启动过程,在 memcached.c 的 main 函数中顺序如下:
- 1 、调用 settings_init() 设定初始化参数
2 、从启动命令中读取参数来设置 setting 值
3 、设定 LIMIT 参数
4 、开始网络 socket 监听(如果非
socketpath 存在)( 1.2 之后支持 UDP 方式)
5 、检查用户身份( Memcached 不允许 root 身份启动)
6 、如果有 socketpath 存在,开启 UNIX 本地连接(Sock 管道)
7 、如果以 -d 方式启动,创建守护进程(如上调用
daemon 函数)
8 、初始化 item 、 event 、状态信息、 hash 、连接、 slab
9 、如设置中 managed 生效,创建 bucket 数组
10 、检查是否需要锁定内存页
11 、初始化信号、连接、删除队列
12 、如果 daemon 方式,处理进程 ID
13 、event 开始,启动过程结束, main 函数进入循环。 - 在 daemon 方式中,因为 stderr 已经被定向到黑洞,所以不会反馈执行中的可见错误信息。
- memcached.c 的主循环函数是 drive_machine ,传入参数是指向当前的连接的结构指针,根据 state 成员的状态来决定动作。
- Memcached 使用一套自定义的协议完成数据交换,它的 protocol 文档可以参考: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
- 在API中,换行符号统一为\r\n
- ◎Memcached的内存管理方式
- Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针。Memcached使用slab->chunk的组织方式管理内存。
- 1.1和1.2的slabs.c中的slab空间划分算法有一些不同,后面会分别介绍。
- Slab可以理解为一个内存块,一个slab是memcached一次申请内存的最小单位,在memcached中,一个slab的大小默 认为1048576字节(1MB),所以memcached都是整MB的使用内存。每一个slab被划分为若干个chunk,每个chunk里保存一个
item,每个item同时包含了item结构体、key和value(注意在memcached中的value是只有字符串的)。slab按照自己的 id分别组成链表,这些链表又按id挂在一个slabclass数组上,整个结构看起来有点像二维数组。slabclass的长度在1.1中是21,在 1.2中是200。 - slab有一个初始chunk大小,1.1中是1字节,1.2中是80字节,1.2中有一个factor值,默认为1.25
- 在1.1中,chunk大小表示为初始大小*2^n,n为classid,即:id为0的slab,每chunk大小1字节,id为1的 slab,每chunk大小2字节,id为2的slab,每chunk大小4字节……id为20的slab,每chunk大小为1MB,就是说id为20 的slab里只有一个chunk:
151.
void
slabs_init(size_t limit) {
- 152.
int
i; - 153.
int size=1; - 154.
- 155.
mem_limit = limit; - 156.
for(i=0; i<=POWER_LARGEST; i++, size*=2)
{ - 157.
slabclass[i].size = size; - 158.
slabclass[i].perslab = POWER_BLOCK /
size; - 159.
slabclass[i].slots = 0; - 160.
slabclass[i].sl_curr =
slabclass[i].sl_total = slabclass[i].slabs = 0; - 161.
slabclass[i].end_page_ptr = 0; - 162.
slabclass[i].end_page_free = 0; - 163.
slabclass[i].slab_list = 0; - 164.
slabclass[i].list_size = 0; - 165.
slabclass[i].killing = 0; - 166.
} - 167.
- 168.
/* for the test suite: faking of how much we‘ve already malloc‘d */ - 169.
{ - 170.
char *t_initial_malloc =
getenv("T_MEMD_INITIAL_MALLOC"); - 171.
if (t_initial_malloc) { - 172.
mem_malloced =
atol(getenv("T_MEMD_INITIAL_MALLOC")); - 173.
} - 174.
} - 175.
- 176.
/* pre-allocate slabs by default, unless
the environment variable - 177.
for testing is set to something non-zero
*/ - 178.
{ - 179.
char *pre_alloc =
getenv("T_MEMD_SLABS_ALLOC"); - 180.
if (!pre_alloc || atoi(pre_alloc)) { - 181.
slabs_preallocate(limit / POWER_BLOCK); - 182.
} - 183.
}
184.
}
- 在1.2中,chunk大小表示为初始大小*f^n,f为factor,在memcached.c中定义,n为classid,同时,201个头不
是全部都要初始化的,因为factor可变,初始化只循环到计算出的大小达到slab大小的一半为止,而且它是从id1开始的,即:id为1的slab, 每chunk大小80字节,id为2的slab,每chunk大小80*f,id为3的slab,每chunk大小80*f^2,初始化大小有一个修正值 CHUNK_ALIGN_BYTES,用来保证n-byte排列 (保证结果是CHUNK_ALIGN_BYTES的整倍数)。这样,在标准情况下,memcached1.2会初始化到id40,这个slab中每个 chunk大小为504692,每个slab中有两个chunk。最后,slab_init函数会在最后补足一个id41,它是整块的,也就是这个 slab中只有一个1MB大的chunk:
188.
void
slabs_init(size_t limit, double factor) {
- 189.
int i = POWER_SMALLEST - 1; - 190.
unsigned int size = sizeof(item) +
settings.chunk_size; - 191.
- 192.
/* Factor of 2.0 means use the default
memcached behavior */ - 193.
if (factor == 2.0 && size < 128) - 194.
size = 128; - 195.
- 196.
mem_limit = limit; - 197.
memset(slabclass, 0, sizeof(slabclass)); - 198.
- 199.
while (++i < POWER_LARGEST &&
size <= POWER_BLOCK / 2) { - 200.
/* Make sure items are always n-byte
aligned */ - 201.
if (size % CHUNK_ALIGN_BYTES) - 202.
size += CHUNK_ALIGN_BYTES - (size %
CHUNK_ALIGN_BYTES); - 203.
- 204.
slabclass[i].size = size; - 205.
slabclass[i].perslab = POWER_BLOCK /
slabclass[i].size; - 206.
size *= factor; - 207.
if (settings.verbose > 1) { - 208.
fprintf(stderr, "slab class
%3d: chunk size %6d perslab %5d\n", - 209.
i, slabclass[i].size,
slabclass[i].perslab); - 210.
} - 211.
} - 212.
- 213.
power_largest = i; - 214.
slabclass[power_largest].size =
POWER_BLOCK; - 215.
slabclass[power_largest].perslab = 1; - 216.
- 217.
/* for the test suite: faking of how much we‘ve already malloc‘d */ - 218.
{ - 219.
char *t_initial_malloc =
getenv("T_MEMD_INITIAL_MALLOC"); - 220.
if (t_initial_malloc) { - 221.
mem_malloced =
atol(getenv("T_MEMD_INITIAL_MALLOC")); - 222.
} - 223.
- 224.
} - 225.
226.
#ifndef
DONT_PREALLOC_SLABS
- 227.
{ - 228.
char *pre_alloc =
getenv("T_MEMD_SLABS_ALLOC"); - 229.
if (!pre_alloc || atoi(pre_alloc)) { - 230.
slabs_preallocate(limit /
POWER_BLOCK); - 231.
} - 232.
}
233.
#endif
234.
}
- 由上可以看出,memcached的内存分配是有冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就被丢弃了,如id40中,两个chunk占用了1009384字节,这个slab一共有1MB,那么就有39192字节被浪费了。
- Memcached使用这种方式来分配内存,是为了可以快速的通过item长度定位出slab的classid,有一点类似hash,因为
item的长度是可以计算的,比如一个item的长度是300字节,在1.2中就可以得到它应该保存在id7的slab中,因为按照上面的计算方
法,id6的chunk大小是252字节,id7的chunk大小是316字节,id8的chunk大小是396字节,表示所有252到316字节的 item都应该保存在id7中。同理,在1.1中,也可以计算得到它出于256和512之间,应该放在chunk_size为512的id9中(32位系
统)。 - Memcached初始化的时候,会初始化slab(前面可以看到,在main函数中调用了slabs_init())。它会在 slabs_init()中检查一个常量DONT_PREALLOC_SLABS,如果这个没有被定义,说明使用预分配内存方式初始化slab,这样在所
有已经定义过的slabclass中,每一个id创建一个slab。这样就表示,1.2在默认的环境中启动进程后要分配41MB的slab空间,在这个过 程里,memcached的第二个内存冗余发生了,因为有可能一个id根本没有被使用过,但是它也默认申请了一个slab,每个slab会用掉1MB内存 - 当一个slab用光后,又有新的item要插入这个id,那么它就会重新申请新的slab,申请新的slab时,对应id的slab链表就要增长,这个链表是成倍增长的,在函数grow_slab_list函数中,这个链的长度从1变成2,从2变成4,从4变成8……:
240.
static int
grow_slab_list (unsigned int id) {
- 241.
slabclass_t *p = &slabclass[id]; - 242.
if (p->slabs == p->list_size) { - 243.
size_t new_size = p->list_size ? p->list_size * 2 : 16; - 244.
void *new_list =
realloc(p->slab_list, new_size*sizeof(void*)); - 245.
if (new_list == 0) return 0; - 246.
p->list_size = new_size; - 247.
p->slab_list = new_list; - 248.
} - 249.
return 1;
250.
}
- 在定位item时,都是使用slabs_clsid函数,传入参数为item大小,返回值为classid,由这个过程可以看 出,memcached的第三个内存冗余发生在保存item的过程中,item总是小于或等于chunk大小的,当item小于chunk大小时,就又发 生了空间浪费。
- ◎Memcached的NewHash算法
- Memcached的item保存基于一个大的hash表,它的实际地址就是slab中的chunk偏移,但是它的定位是依靠对key做 hash的结果,在primary_hashtable中找到的。在assoc.c和items.c中定义了所有的hash和item操作。
- Memcached使用了一个叫做NewHash的算法,它的效果很好,效率也很高。1.1和1.2的NewHash有一些不同,主要的实现方式还是一样的,1.2的hash函数是经过整理优化的,适应性更好一些。
- NewHash的原型参考:http://burtleburtle.net/bob/hash/evahash.html。数学家总是有点奇怪,呵呵~
- 为了变换方便,定义了u4和u1两种数据类型,u4就是无符号的长整形,u1就是无符号char(0-255)。
- 具体代码可以参考1.1和1.2源码包。
- 注意这里的hashtable长度,1.1和1.2也是有区别的,1.1中定义了HASHPOWER常量为20,hashtable表长为 hashsize(HASHPOWER),就是4MB(hashsize是一个宏,表示1右移n位),1.2中是变量16,即hashtable表长 65536:
260.
typedef unsigned long
int ub4; /* unsigned 4-byte quantities */
261.
typedef unsigned
char ub1; /* unsigned 1-byte
quantities */
- 262.
263.
#define
hashsize(n) ((ub4)1<<(n))
264.
#define
hashmask(n) (hashsize(n)-1)
- 在assoc_init()中,会对primary_hashtable做初始化,对应的hash操作包括:assoc_find()、 assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete(),对应 于item的读写操作。其中assoc_find()是根据key和key长寻找对应的item地址的函数(注意在C中,很多时候都是同时直接传入字符串 和字符串长度,而不是在函数内部做strlen),返回的是item结构指针,它的数据地址在slab中的某个chunk上。
- items.c是数据项的操作程序,每一个完整的item包括几个部分,在item_make_header()中定义为:
- key:键
nkey:键长
flags:用户定义的flag(其实这个flag在memcached中没有启用)
nbytes:值长(包括换行符号\r\n)
suffix:后缀Buffer
nsuffix:后缀长 - 一个完整的item长度是键长+值长+后缀长+item结构大小(32字节),item操作就是根据这个长度来计算slab的classid的。
- hashtable中的每一个桶上挂着一个双链表,item_init()的时候已经初始化了heads、tails、sizes三个数组
为0,这三个数组的大小都为常量LARGEST_ID(默认为255,这个值需要配合factor来修改),在每次item_assoc()的时候,它会 首先尝试从slab中获取一块空闲的chunk,如果没有可用的chunk,会在链表中扫描50次,以得到一个被LRU踢掉的item,将它 unlink,然后将需要插入的item插入链表中。 - 注意item的refcount成员。item被unlink之后只是从链表上摘掉,不是立刻就被free的,只是将它放到删除队列中(item_unlink_q()函数)。
- item对应一些读写操作,包括remove、update、replace,当然最重要的就是alloc操作。
- item还有一个特性就是它有过期时间,这是memcached的一个很有用的特性,很多应用都是依赖于memcached的item过 期,比如session存储、操作锁等。item_flush_expired()函数就是扫描表中的item,对过期的item执行unlink操作, 当然这只是一个回收动作,实际上在get的时候还要进行时间判断:
274.
/* expires items
that are more recent than the oldest_live setting. */
275.
void
item_flush_expired() {
- 276.
int i; - 277.
item *iter, *next; - 278.
if (! settings.oldest_live) - 279.
return; - 280.
for (i = 0; i < LARGEST_ID; i++) { - 281.
/* The LRU is sorted in decreasing time
order, and an item‘s timestamp - 282.
* is never newer than its last access
time, so we only need to walk - 283.
* back until we hit an item older than
the oldest_live time. - 284.
* The oldest_live checking will
auto-expire the remaining items. - 285.
*/ - 286.
for (iter = heads[i]; iter != NULL;
iter = next) { - 287.
if (iter->time >=
settings.oldest_live) { - 288.
next = iter->next; - 289.
if ((iter->it_flags &
ITEM_SLABBED) == 0) { - 290.
item_unlink(iter); - 291.
} - 292.
} else { - 293.
/* We‘ve hit the first old
item. Continue to the next queue. */ - 294.
break; - 295.
} - 296.
} - 297.
}
298.
}
299.
/* wrapper around
assoc_find which does the lazy expiration/deletion logic */
300.
item *get_item_notedeleted(char
*key, size_t nkey, int *delete_locked) {
- 301.
item *it = assoc_find(key, nkey); - 302.
if (delete_locked) *delete_locked = 0; - 303.
if (it && (it->it_flags &
ITEM_DELETED)) { - 304.
/* it‘s flagged as delete-locked. let‘s see if that condition - 305.
is past due, and the 5-second
delete_timer just hasn‘t - 306.
gotten to it yet... */ - 307.
if (! item_delete_lock_over(it)) { - 308.
if (delete_locked) *delete_locked =
1; - 309.
it = 0; - 310.
} - 311.
} - 312.
if (it && settings.oldest_live
&& settings.oldest_live <= current_time && - 313.
it->time <= settings.oldest_live)
{ - 314.
item_unlink(it); - 315.
it = 0; - 316.
} - 317.
if (it && it->exptime &&
it->exptime <= current_time) { - 318.
item_unlink(it); - 319.
it = 0; - 320.
} - 321.
return it;
322.
}
- Memcached的内存管理方式是非常精巧和高效的,它很大程度上减少了直接alloc系统内存的次数,降低函数开销和内存碎片产生几率,虽然这种方式会造成一些冗余浪费,但是这种浪费在大型系统应用中是微不足道的。
- ◎Memcached的理论参数计算方式
- 影响 memcached 工作的几个参数有:
- 常量REALTIME_MAXDELTA
60*60*24*30
最大30天的过期时间 - conn_init()中的freetotal(=200)
最大同时连接数 - 常量KEY_MAX_LENGTH
250
最大键长 - settings.factor(=1.25)
factor将影响chunk的步进大小 - settings.maxconns(=1024)
最大软连接 - settings.chunk_size(=48)
一个保守估计的key+value长度,用来生成id1中的chunk长度(1.2)。id1的chunk长度等于这个数值加上item结构体的长度(32),即默认的80字节。 - 常量POWER_SMALLEST
1
最小classid(1.2) - 常量POWER_LARGEST
200
最大classid(1.2) - 常量POWER_BLOCK
1048576
默认slab大小 - 常量CHUNK_ALIGN_BYTES
(sizeof(void *))
保证chunk大小是这个数值的整数倍,防止越界(void *的长度在不同系统上不一样,在标准32位系统上是4) - 常量ITEM_UPDATE_INTERVAL
60
队列刷新间隔 - 常量LARGEST_ID 255
最大item链表数(这个值不能比最大的classid小) - 变量hashpower(在1.1中是常量HASHPOWER)
决定hashtable的大小 - 根据上面介绍的内容及参数设定,可以计算出的一些结果:
- 1、在memcached中可以保存的item个数是没有软件上限的,之前我的100万的说法是错误的。
2、假设NewHash算法碰撞均匀,查找item的循环次数是item总数除以hashtable大小(由hashpower决定),是线性的。
3、Memcached限制了可以接受的最大item是1MB,大于1MB的数据不予理会。
4、Memcached的空间利用率和数据特性有很大的关系,又与DONT_PREALLOC_SLABS常量有关。
在最差情况下,有198个slab会被浪费(所有item都集中在一个slab中,199个id全部分配满)。 - ◎Memcached的定长优化
- 根据上面几节的描述,多少对memcached有了一个比较深入的认识。在深入认识的基础上才好对它进行优化。
- Memcached本身是为变长数据设计的,根据数据特性,可以说它是“面向大众”的设计,但是很多时候,我们的数据并不是这样的“普 遍”,典型的情况中,一种是非均匀分布,即数据长度集中在几个区域内(如保存用户 Session);另一种更极端的状态是等长数据(如定长键值,定长数据,多见于访问、在线统计或执行锁)。
- 这里主要研究一下定长数据的优化方案(1.2),集中分布的变长数据仅供参考,实现起来也很容易。
- 解决定长数据,首先需要解决的是slab的分配问题,第一个需要确认的是我们不需要那么多不同chunk长度的slab,为了最大限度地利用资源,最好chunk和item等长,所以首先要计算item长度。
- 在之前已经有了计算item长度的算法,需要注意的是,除了字符串长度外,还要加上item结构的长度32字节。
- 假设我们已经计算出需要保存200字节的等长数据。
- 接下来是要修改slab的classid和chunk长度的关系。在原始版本中,chunk长度和classid是有对应关系的,现在如果 把所有的chunk都定为200个字节,那么这个关系就不存在了,我们需要重新确定这二者的关系。一种方法是,整个存储结构只使用一个固定的id,即只使 用199个槽中的1个,在这种条件下,就一定要定义DONT_PREALLOC_SLABS来避免另外的预分配浪费。另一种方法是建立一个hash关系,
来从item确定classid,不能使用长度来做键,可以使用key的NewHash结果等不定数据,或者直接根据key来做hash(定长数据的 key也一定等长)。这里简单起见,选择第一种方法,这种方法的不足之处在于只使用一个id,在数据量非常大的情况下,slab链会很长(因为所有数据都 挤在一条链上了),遍历起来的代价比较高。 - 前面介绍了三种空间冗余,设置chunk长度等于item长度,解决了第一种空间浪费问题,不预申请空间解决了第二种空间浪费问题,那么对 于第一种问题(slab内剩余)如何解决呢,这就需要修改POWER_BLOCK常量,使得每一个slab大小正好等于chunk长度的整数倍,这样一个 slab就可以正好划分成n个chunk。这个数值应该比较接近1MB,过大的话同样会造成冗余,过小的话会造成次数过多的alloc,根据chunk长 度为200,选择1000000作为POWER_BLOCK的值,这样一个slab就是100万字节,不是1048576。三个冗余问题都解决了,空间利 用率会大大提升。
- 修改 slabs_clsid 函数,让它直接返回一个定值(比如 1 ):
353.
unsigned int
slabs_clsid(size_t size) {
- 354.
return 1;
355.
}
- 修改slabs_init函数,去掉循环创建所有classid属性的部分,直接添加slabclass[1]:
357.
slabclass[1].size
= 200; //每chunk200字节
358.
slabclass[1].perslab
= 5000; //1000000/200
- ◎Memcached客户端
- Memcached是一个服务程序,使用的时候可以根据它的协议,连接到memcached服务器上,发送命令给服务进程,就可以操作上面
的数据。为了方便使用,memcached有很多个客户端程序可以使用,对应于各种语言,有各种语言的客户端。基于C语言的有libmemcache、
APR_Memcache;基于Perl的有Cache::Memcached;另外还有Python、Ruby、Java、C#等语言的支持。PHP的 客户端是最多的,不光有mcache和PECL memcache两个扩展,还有大把的由PHP编写的封装类,下面介绍一下在PHP中使用memcached的方法: - mcache扩展是基于libmemcache再封装的。libmemcache一直没有发布stable版本,目前版本是1.4.0- rc2,可以在这里找到。libmemcache有一个很不好的特性,就是会向stderr写很多错误信息,一般的,作为lib使用的时候,stderr 一般都会被定向到其它地方,比如Apache的错误日志,而且libmemcache会自杀,可能会导致异常,不过它的性能还是很好的。
- mcache扩展最后更新到1.2.0-beta10,作者大概是离职了,不光停止更新,连网站也打不开了(~_~),只能到其它地方去获 取这个不负责的扩展了。解压后安装方法如常:phpize
& configure & make & make install,一定要先安装libmemcache。使用这个扩展很简单:
364.
<?php
365.
$mc =
memcache(); // 创建一个memcache连接对象,注意这里不是用new!
366.
$mc->add_server(‘localhost‘,
11211); // 添加一个服务进程
367.
$mc->add_server(‘localhost‘,
11212); // 添加第二个服务进程
368.
$mc->set(‘key1‘,
‘Hello‘); // 写入key1 => Hello
369.
$mc->set(‘key2‘,
‘World‘, 10); // 写入key2 => World,10秒过期
370.
$mc->set(‘arr1‘,
array(‘Hello‘, ‘World‘)); // 写入一个数组
371.
$key1 =
$mc->get(‘key1‘); // 获取‘key1‘的值,赋给$key1
372.
$key2 =
$mc->get(‘key2‘); // 获取‘key2‘的值,赋给$key2,如果超过10秒,就取不到了
373.
$arr1 =
$mc->get(‘arr1‘); // 获取‘arr1‘数组
374.
$mc->delete(‘arr1‘); // 删除‘arr1‘
375.
$mc->flush_all(); // 删掉所有数据
376.
$stats =
$mc->stats(); // 获取服务器信息
377.
var_dump($stats); // 服务器信息是一个数组
378.
?>
- 这个扩展的好处是可以很方便地实现分布式存储和负载均衡,因为它可以添加多个服务地址,数据在保存的时候是会根据hash结果定位到某台服务器上 的,这也是libmemcache的特性。libmemcache支持集中hash方式,包括CRC32、ELF和Perl hash。
- PECL memcache是PECL发布的扩展,目前最新版本是2.1.0,可以在pecl网站得到。memcache扩展的使用方法可以在新一些的PHP手册中找到,它和mcache很像,真的很像:
382.
<?php
- 383.
384.
$memcache = new
Memcache;
385.
$memcache->connect(‘localhost‘,
11211) or die ("Could not connect");
- 386.
387.
$version =
$memcache->getVersion();
388.
echo
"Server‘s version: ".$version."n";
- 389.
390.
$tmp_object = new
stdClass;
391.
$tmp_object->str_attr
= ‘test‘;
392.
$tmp_object->int_attr
= 123;
- 393.
394.
$memcache->set(‘key‘,
$tmp_object, false, 10) or die ("Failed to save data at the server");
395.
echo "Store
data in the cache (data will expire in 10 seconds)n";
- 396.
397.
$get_result =
$memcache->get(‘key‘);
398.
echo "Data
from the cache:n";
- 399.
400.
var_dump($get_result);
- 401.
402.
?>
- 这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善,也不 支持add_server这种分布操作,但是因为它不依赖其它的外界程序,兼容性要好一些,也比较稳定。至于效率,差别不是很大。
- 另外,有很多的PHP class可以使用,比如MemcacheClient.inc.php,phpclasses.org上可以找到很多,一般都是对perl client API的再封装,使用方式很像。
- ◎BSM_Memcache
- 从C client来说,APR_Memcache是一个很成熟很稳定的client程序,支持线程锁和原子级操作,保证运行的稳定性。不过它是基于APR的 (APR将在最后一节介绍),没有libmemcache的应用范围广,目前也没有很多基于它开发的程序,现有的多是一些Apache
Module,因为它不能脱离APR环境运行。但是APR倒是可以脱离Apache单独安装的,在APR网站上可以下载APR和APR-util,不需要 有Apache,可以直接安装,而且它是跨平台的。 - BSM_Memcache是我在BS.Magic项目中开发的一个基于APR_Memcache的PHP扩展,说起来有点拗口,至少它把APR扯进了PHP扩展中。这个程序很简单,也没做太多的功能,只是一种形式的尝试,它支持服务器分组。
- 和mcache扩展支持多服务器分布存储不同,BSM_Memcache支持多组服务器,每一组内的服务器还是按照hash方式来分布保存
数据,但是两个组中保存的数据是一样的,也就是实现了热备,它不会因为一台服务器发生单点故障导致数据无法获取,除非所有的服务器组都损坏(例如机房停 电)。当然实现这个功能的代价就是性能上的牺牲,在每次添加删除数据的时候都要扫描所有的组,在get数据的时候会随机选择一组服务器开始轮询,一直到找 到数据为止,正常情况下一次就可以获取得到。 - BSM_Memcache只支持这几个函数:
412.
zend_function_entry
bsm_memcache_functions[] =
413.
{
- 414.
PHP_FE(mc_get, NULL) - 415.
PHP_FE(mc_set, NULL) - 416.
PHP_FE(mc_del, NULL) - 417.
PHP_FE(mc_add_group, NULL) - 418.
PHP_FE(mc_add_server, NULL) - 419.
PHP_FE(mc_shutdown, NULL) - 420.
{NULL, NULL, NULL}
421.
};
- mc_add_group函数返回一个整形(其实应该是一个object,我偷懒了~_~)作为组ID,mc_add_server的时候要提供两个参数,一个是组ID,一个是服务器地址(ADDR : PORT)。
424.
/**
425.
* Add a server
group
426.
*/
427.
PHP_FUNCTION(mc_add_group)
428.
{
- 429.
apr_int32_t group_id; - 430.
apr_status_t rv; - 431.
- 432.
if (0 != ZEND_NUM_ARGS()) - 433.
{ - 434.
WRONG_PARAM_COUNT; - 435.
RETURN_NULL(); - 436.
} - 437.
- 438.
group_id = free_group_id(); - 439.
if (-1 == group_id) - 440.
{ - 441.
RETURN_FALSE; - 442.
} - 443.
- 444.
apr_memcache_t *mc; - 445.
rv = apr_memcache_create(p, MAX_G_SERVER,
0, &mc); - 446.
- 447.
add_group(group_id, mc); - 448.
- 449.
RETURN_DOUBLE(group_id);
450.
}
451.
/**
452.
* Add a server
into group
453.
*/
454.
PHP_FUNCTION(mc_add_server)
455.
{
- 456.
apr_status_t rv; - 457.
apr_int32_t group_id; - 458.
double g; - 459.
char *srv_str; - 460.
int srv_str_l; - 461.
- 462.
if (2 != ZEND_NUM_ARGS()) - 463.
{ - 464.
WRONG_PARAM_COUNT; - 465.
} - 466.
- 467.
if (zend_parse_parameters(ZEND_NUM_ARGS()
TSRMLS_CC, "ds", &g, &srv_str, &srv_str_l) == FAILURE) - 468.
{ - 469.
RETURN_FALSE; - 470.
} - 471.
- 472.
group_id = (apr_int32_t) g; - 473.
- 474.
if (-1 == is_validate_group(group_id)) - 475.
{ - 476.
RETURN_FALSE; - 477.
} - 478.
- 479.
char *host, *scope; - 480.
apr_port_t port; - 481.
- 482.
rv = apr_parse_addr_port(&host,
&scope, &port, srv_str, p); - 483.
if (APR_SUCCESS == rv) - 484.
{ - 485.
// Create this server object - 486.
apr_memcache_server_t *st; - 487.
rv = apr_memcache_server_create(p,
host, port, 0, 64, 1024, 600, &st); - 488.
if (APR_SUCCESS == rv) - 489.
{ - 490.
if (NULL == mc_groups[group_id]) - 491.
{ - 492.
RETURN_FALSE; - 493.
} - 494.
- 495.
// Add server - 496.
rv =
apr_memcache_add_server(mc_groups[group_id], st); - 497.
- 498.
if (APR_SUCCESS == rv) - 499.
{ - 500.
RETURN_TRUE; - 501.
} - 502.
} - 503.
} - 504.
- 505.
RETURN_FALSE;
506.
}
- 在set和del数据的时候,要循环所有的组:
509.
/**
510.
* Store item into
all groups
511.
*/
512.
PHP_FUNCTION(mc_set)
513.
{
- 514.
char *key, *value; - 515.
int key_l, value_l; - 516.
double ttl = 0; - 517.
double set_ct = 0; - 518.
- 519.
if (2 != ZEND_NUM_ARGS()) - 520.
{ - 521.
WRONG_PARAM_COUNT; - 522.
} - 523.
- 524.
if (zend_parse_parameters(ZEND_NUM_ARGS()
TSRMLS_CC, "ss|d", &key, &key_l, &value, &value_l,
ttl) == FAILURE) - 525.
{ - 526.
RETURN_FALSE; - 527.
} - 528.
- 529.
// Write data into every object - 530.
apr_int32_t i = 0; - 531.
if (ttl < 0) - 532.
{ - 533.
ttl = 0; - 534.
} - 535.
- 536.
apr_status_t rv; - 537.
- 538.
for (i = 0; i < MAX_GROUP; i++) - 539.
{ - 540.
if (0 == is_validate_group(i)) - 541.
{ - 542.
// Write it! - 543.
rv = apr_memcache_add(mc_groups[i],
key, value, value_l, (apr_uint32_t) ttl, 0); - 544.
if (APR_SUCCESS == rv) - 545.
{ - 546.
set_ct++; - 547.
} - 548.
} - 549.
} - 550.
- 551.
RETURN_DOUBLE(set_ct);
552.
}
- 在mc_get中,首先要随机选择一个组,然后从这个组开始轮询:
554.
/**
555.
* Fetch a item
from a random group
556.
*/
557.
PHP_FUNCTION(mc_get)
558.
{
- 559.
char *key, *value = NULL; - 560.
int key_l; - 561.
apr_size_t value_l; - 562.
- 563.
if (1 != ZEND_NUM_ARGS()) - 564.
{ - 565.
WRONG_PARAM_COUNT; - 566.
} - 567.
- 568.
if (zend_parse_parameters(ZEND_NUM_ARGS()
TSRMLS_CC, "s", &key, &key_l) == FAILURE) - 569.
{ - 570.
RETURN_MULL(); - 571.
} - 572.
- 573.
// I will try ... - 574.
// Random read - 575.
apr_int32_t curr_group_id = random_group(); - 576.
apr_int32_t i = 0; - 577.
apr_int32_t try = 0; - 578.
apr_uint32_t flag; - 579.
apr_memcache_t *oper; - 580.
apr_status_t rv; - 581.
- 582.
for (i = 0; i < MAX_GROUP; i++) - 583.
{ - 584.
try = i + curr_group_id; - 585.
try = try % MAX_GROUP; - 586.
if (0 == is_validate_group(try)) - 587.
{ - 588.
// Get a value - 589.
oper = mc_groups[try]; - 590.
rv =
apr_memcache_getp(mc_groups[try], p, (const char *) key, &value,
&value_l, 0); - 591.
if (APR_SUCCESS == rv) - 592.
{ - 593.
RETURN_STRING(value, 1); - 594.
} - 595.
} - 596.
} - 597.
- 598.
RETURN_FALSE;
599.
}
600.
/**
601.
* Random group id
602.
* For mc_get()
603.
*/
604.
apr_int32_t
random_group()
605.
{
- 606.
struct timeval tv; - 607.
struct timezone tz; - 608.
int usec; - 609.
- 610.
gettimeofday(&tv, &tz); - 611.
- 612.
usec = tv.tv_usec; - 613.
- 614.
int curr = usec % count_group(); - 615.
- 616.
return (apr_int32_t) curr;
617.
}
- BSM_Memcache的使用方式和其它的client类似:
619.
<?php
620.
$g1 =
mc_add_group(); // 添加第一个组
621.
$g2 =
mc_add_group(); // 添加第二个组
622.
mc_add_server($g1,
‘localhost:11211‘); // 在第一个组中添加第一台服务器
623.
mc_add_server($g1,
‘localhost:11212‘); // 在第一个组中添加第二台服务器
624.
mc_add_server($g2,
‘10.0.0.16:11211‘); // 在第二个组中添加第一台服务器
625.
mc_add_server($g2,
‘10.0.0.17:11211‘); // 在第二个组中添加第二台服务器
- 626.
627.
mc_set(‘key‘,
‘Hello‘); // 写入数据
628.
$key =
mc_get(‘key‘); // 读出数据
629.
mc_del(‘key‘); // 删除数据
630.
mc_shutdown(); // 关闭所有组
- ?>
- APR_Memcache的相关资料可以在这里找到,BSM_Memcache可以在网络上找下载。
- ◎APR环境介绍
- APR的全称:Apache Portable Runtime。它是Apache软件基金会创建并维持的一套跨平台的C语言库。它从Apache httpd1.x中抽取出来并独立于httpd之外,Apache httpd2.x就是建立在APR上。APR提供了很多方便的API接口可供使用,包括如内存池、字符串操作、网络、数组、hash表等实用的功能。开发 Apache2 Module要接触很多APR函数,当然APR可以独立安装独立使用,可以用来写自己的应用程序,不一定是Apache httpd的相关开发。
- ◎后记
- 这是我在农历丙戌年(我的本命年)的最后一篇文章,由于Memcached的内涵很多,仓促整理一定有很多遗漏和错误。感谢新浪网提供的研究机会,感谢部门同事的帮助。
- APR_Memcache的相关资料可以在这里找到,BSM_Memcache可以在网络上找下载。
- ◎APR环境介绍
- APR的全称:Apache Portable Runtime。它是Apache软件基金会创建并维持的一套跨平台的C语言库。它从Apache httpd1.x中抽取出来并独立于httpd之外,Apache httpd2.x就是建立在APR上。APR提供了很多方便的API接口可供使用,包括如内存池、字符串操作、网络、数组、hash表等实用的功能。开发 Apache2 Module要接触很多APR函数,当然APR可以独立安装独立使用,可以用来写自己的应用程序,不一定是Apache httpd的相关开发。
- ◎后记
- 这是我在农历丙戌年(我的本命年)的最后一篇文章,由于Memcached的内涵很多,仓促整理一定有很多遗漏和错误。感谢新浪网提供的研究机会,感谢部门同事的帮助。
- 637.
分布式缓存系统Memcached简介与实践 - 一 Memcached服务器端的安装
(此处将其作为系统服务安装) - 下载文件:memcached
1.2.1 for Win32 binaries (Dec 23, 2006) - 1 解压缩文件到c:memcached
- 2命令行输入 ‘c:memcachedmemcached.exe -d install‘
- 3 命令行输入 ‘c:memcachedmemcached.exe -d start‘,该命令启动
Memcached ,默认监听端口为 11211 - 通过
memcached.exe -h 可以查看其帮助 - 二 .NET
memcached client library - 下载文件:https://sourceforge.net/projects/memcacheddotnet/
- 里面有.net1.1 和 .net2.0的两种版本 还有一个不错的例子。
- 三 应用
- 1 将Commons.dll,ICSharpCode.SharpZipLib.dll,log4net.dll,Memcached.ClientLibrary.dll 等放到bin目录
- 2 引用Memcached.ClientLibrary.dll
- 3 代码
- 1namespaceMemcached.MemcachedBench
2{
3 usingSystem;
4 usingSystem.Collections;
5
6 usingMemcached.ClientLibrary;
7
8 publicclassMemcachedBench
9 {
10 [STAThread]
11 publicstaticvoidMain(String[]args)
12 {
13 string[]serverlist={"10.0.0.131:11211","10.0.0.132:11211"};
14
15 //初始化池
16 SockIOPoolpool=SockIOPool.GetInstance();
17 pool.SetServers(serverlist);
18
19 pool.InitConnections=3;
20 pool.MinConnections=3;
21 pool.MaxConnections=5;
22
23 pool.SocketConnectTimeout=1000;
24 pool.SocketTimeout=3000;
25
26 pool.MaintenanceSleep=30;
27 pool.Failover=true;
28
29 pool.Nagle=false;
30 pool.Initialize();
31
32 //获得客户端实例
33 MemcachedClientmc=newMemcachedClient();
34 mc.EnableCompression=false;
35
36 Console.WriteLine("------------测 试-----------");
37 mc.Set("test","myvalue"); //存储数据到缓存服务器,这里将字符串"myvalue"缓存,key是"test"
38
39 if(mc.KeyExists("test")) //测试缓存存在key为test的项目
40 {
41 Console.WriteLine("testisExists");
42 Console.WriteLine(mc.Get("test").ToString()); //在缓存中获取key为test的项目
43 }
44 else
45 {
46 Console.WriteLine("testnotExists");
47 }
48
49 Console.ReadLine();
50
51 mc.Delete("test"); //移除缓存中key为test的项目
52
53 if(mc.KeyExists("test"))
54 {
55 Console.WriteLine("testisExists");
56 Console.WriteLine(mc.Get("test").ToString());
57 }
58 else
59 {
60 Console.WriteLine("testnotExists");
61 }
62 Console.ReadLine();
63
64 SockIOPool.GetInstance().Shutdown(); //关闭池,关闭sockets
65 }
66 }
67} - 4 运行结果
- 后记: 是个不错的东西 ,使用起来也很方便,php ,ruby 的项目中用这个的很多,但是.net项目中用的较少(恕俺孤陋寡闻)
。希望有兴趣的朋友们 多多交流 。 - 655.
在Win开发环境下面配置了一下Memcached - 开发环境win下面配置使用Memcached方法概述
- 再简单的事情没有做一遍都不能明白其中时候如此,今天配置Memcached就发现这个问题。帮助很全,先是在memcached for
Windows获取到了需要的win下面的Memcached,按照方法: - 引用
- Unzip the binaries in your desired directory (eg. c:memcached)
Install the
service using the command: ‘c:memcachedmemcached.exe -d install‘ from the
command line
Start the server
from the Microsoft Management Console or by running the following command:
‘c:memcachedmemcached.exe -d start‘
Use the server, by
default listening to port 11211 - 然后
- 在php.ini 加入一行 ‘extension=php_memcache.dll‘
- 然后到
http://pecl4win.php.net/ext.php/php_memcache.dll获取php_memcache.dll - 并复制到 ext 中(记住版本不要错了!)
- 重启Apache,发现PHPInfo就是提示出不来memcache,真是无语了,代码测试总是提示
- 引用
- Fatal error:
Class ‘Memcache‘ not found in D:xampplitehtdocsmemcacheindex.php on line 20 - 开始觉得奇怪,于是搜索在官方网站发现了http://www.php.net/manual/zh/ref.memcache.php
- Hi there:
For run memcached
in a windows box: (tested with latest php,apache and memcache in xp sp2)
a) download the
php_memcache.dll it can be found in the pecl file.
b) put the dll in
the extension folder (c:/php/extension for example). You cannot miss this
folder because they are filled with php*.dll files. In some cases the extension folder
used is the system32, a non-standard way to put dll but still works.
c)configure the
php.ini
; i put this like
the latest extension
extension=php_memcache.dll
; i‘m not sure
about this but don‘t hurts..
[Memcache]
memcache.allow_failover
= 1
memcache.max_failover_attempts=20
memcache.chunk_size
=8192
memcache.default_port
= 11211
d)This is
important, memcached works with a EXTERNAL service. This service must be
downloaded and installed prior to use the memcache. I use: http://jehiah.cz/projects/memcached-win32/
e)Remember to
install the service and to start the service memcached.exe -d install for
install and run services.msc for start the memcached service (or restart the
system).
f) check the
firewall ports.
Finally restart
the apache/iis and runs some test. At least in phpinfo must show some info
about the memcache.
Final notes :The
"awe" about memcache is not only can help for speed some process (or
reduce the cpu use), also can be used like a global session for store a whole
object also this "global session" is shared among all the users, like
APPLICATION used in ASP. So (for example) it‘s possible to do a user counter without needing
of database or writing a file. - 试试态度加上看了一下,看到了熟悉的东西了:
- view plaincopy
to clipboardprint? - $mem=newMemcache;
- $mem->connect(‘127.0.0.1‘,11211);
- $mem->set(‘key‘,‘Thisisatest!‘,0,60);
- $val=$mem->get(‘key‘);
- echo$val;
- $mem = new Memcache;
$mem->connect(‘127.0.0.1‘,
11211);
$mem->set(‘key‘,
‘This is a test!‘, 0, 60);
$val =
$mem->get(‘key‘);
echo $val; - 在浏览器输出了
- This is a
test! - 另外作为服务器开发者,自然很关注的一个问题就是这个Memcached的原理问题,在老农如是想,如是说,如是为博客中提到:
- 引用
- Memcached 本身的启动过程,在 memcached.c 的 main 函数中顺序如下:
- 1 、调用 settings_init() 设定初始化参数
- 2 、从启动命令中读取参数来设置 setting 值
- 3 、设定 LIMIT 参数
- 4 、开始网络 socket 监听(如果非 socketpath 存在)( 1.2 之后支持 UDP 方式)
- 5 、检查用户身份( Memcached 不允许 root 身份启动)
- 6 、如果有 socketpath 存在,开启 UNIX 本地连接(Sock 管道)
- 7 、如果以 -d 方式启动,创建守护进程(如上调用 daemon 函数)
- 8 、初始化 item 、 event 、状态信息、
hash 、连接、 slab - 9 、如设置中 managed 生效,创建 bucket 数组
- 10 、检查是否需要锁定内存页
- 11 、初始化信号、连接、删除队列
- 12 、如果 daemon 方式,处理进程 ID
- 13 、event 开始,启动过程结束, main 函数进入循环。
- posted @
2009-01-14 13:01 linFen 阅读(321) 评论(0) 编辑 - 699.
利用memcached构建高性能的Web应用程序 - 面临的问题
- 对于高并发高访问的Web应用程序来说,数据库存取瓶颈一直是个令人头疼的问题。特别当你的程序架构还是建立在单数据库模式,而一个数据池连接数峰值已经达到500的时候,那你的程序运行离崩溃的边缘也不远了。很多小网站的开发人员一开始都将注意力放在了产品需求设计上,缺忽视了程序整体性能,可扩展性等方面的考虑,结果眼看着访问量一天天网上爬,可突然发现有一天网站因为访问量过大而崩溃了,到时候哭都来不及。所以我们一定要未雨绸缪,在数据库还没罢工前,想方设法给它减负,这也是这篇文章的主要议题。
- 大家都知道,当有一个request过来后,web服务器交给app服务器,app处理并从db中存取相关数据,但db存取的花费是相当高昂 的。特别是每次都取相同的数据,等于是让数据库每次都在做高耗费的无用功,数据库如果会说话,肯定会发牢骚,你都问了这么多遍了,难道还记不住吗?是啊,
如果app拿到第一次数据并存到内存里,下次读取时直接从内存里读取,而不用麻烦数据库,这样不就给数据库减负了?而且从内存取数据必然要比从数据库媒介
取快很多倍,反而提升了应用程序的性能。 - 因此,我们可以在web/app层与db层之间加一层cache层,主要目的:1.
减少数据库读取负担;2. 提高数据读取速度。而且,cache存取的媒介是内存,而一台服务器的内存容量一般都是有限制的,不像硬盘容量可以做到TB级别。所以,可以考虑采用分布 式的cache层,这样更易于破除内存容量的限制,同时又增加了灵活性。 - Memcached 介绍
- Memcached是开源的分布式cache系统,现在很多的大型web应用程序包括 facebook,youtube,wikipedia,yahoo等等都在使用memcached来支持他们每天数亿级的页面访问。通过把cache层 与他们的web架构集成,他们的应用程序在提高了性能的同时,还大大降低了数据库的负载。
- 具体的memcached资料大家可以直接从它的官方网站[1]上得到。这里我就简单给大家介绍一下memcached的工作原理:
- Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。
- Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先通过计算key 的hash值来确定kv对所处在的ms位置。当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。因为这之间没有交互以及多播 协议,所以memcached交互带给网络的影响是最小化的。
- 举例说明:考虑以下这个场景,有三个mc分别是X,Y,Z,还有三个ms分别是A,B,C:
- 设置kv对
- X想设置key=”foo”,value=”seattle”
- X拿到ms列表,并对key做hash转化,根据hash值确定kv对所存的ms位置
- B被选中了
- X连接上B,B收到请求,把(key=”foo”,value=”seattle”)存了起来
- 获取kv对
- Z想得到key=”foo”的value
- Z用相同的hash算法算出hash值,并确定key=”foo”的值存在B上
- Z连接上B,并从B那边得到value=”seattle”
- 其他任何从X,Y,Z的想得到key=”foo”的值的请求都会发向B
- Memcached服务器(ms)
- 内存分配
- 默认情况下,ms是用一个内置的叫“块分配器”的组件来分配内存的。舍弃c++标准的malloc/free的内存分配,而采用块分配器的主要 目的是为了避免内存碎片,否则操作系统要花费更多时间来查找这些逻辑上连续的内存块(实际上是断开的)。用了块分配器,ms会轮流的对内存进行大块的分 配,并不断重用。当然由于块的大小各不相同,当数据大小和块大小不太相符的情况下,还是有可能导致内存的浪费。
同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制 --- 1MB。 - 因为mc所使用的hash算法,并不会考虑到每个ms的内存大小。理论上mc会分配概率上等量的kv对给每个ms,这样如果每个ms的内存都不 太一样,那可能会导致内存使用率的降低。所以一种替代的解决方案是,根据每个ms的内存大小,找出他们的最大公约数,然后在每个ms上开n个容量=最大公 约数的instance,这样就等于拥有了多个容量大小一样的子ms,从而提供整体的内存使用率。
- 缓存策略
- 当ms的hash表满了之后,新的插入数据会替代老的数据,更新的策略是LRU(最近最少使用),以及每个kv对的有效时限。Kv对存储有效时限是在mc端由app设置并作为参数传给ms的。
- 同时ms采用是偷懒替代法,ms不会开额外的进程来实时监测过时的kv对并删除,而是当且仅当,新来一个插入的数据,而此时又没有多余的空间放了,才会进行清除动作。
- 缓存数据库查询
- 现在memcached最流行的一种使用方式是缓存数据库查询,下面举一个简单例子说明:
- App需要得到userid=xxx的用户信息,对应的查询语句类似:
- “SELECT * FROM
users WHERE userid = xxx” - App先去问cache,有没有“user:userid”(key定义可预先定义约束好)的数据,如果有,返回数据;如果没有,App会从数据库中读取数据,并调用cache的add函数,把数据加入cache中。
- 当取的数据需要更新,app会调用cache的update函数,来保持数据库与cache的数据同步。
- 从上面的例子我们也可以发现,一旦数据库的数据发现变化,我们一定要及时更新cache中的数据,来保证app读到的是同步的正确数 据。当然我们可以通过定时器方式记录下cache中数据的失效时间,时间一过就会激发事件对cache进行更新,但这之间总会有时间上的延迟,导致app 可能从cache读到脏数据,这也被称为狗洞问题。(以后我会专门描述研究这个问题)
- 数据冗余与故障预防
- 从设计角度上,memcached是没有数据冗余环节的,它本身就是一个大规模的高性能cache层,加入数据冗余所能带来的只有设计的复杂性和提高系统的开支。
- 当一个ms上丢失了数据之后,app还是可以从数据库中取得数据。不过更谨慎的做法是在某些ms不能正常工作时,提供额外的ms来支持cache,这样就不会因为app从cache中取不到数据而一下子给数据库带来过大的负载。
- 同时为了减少某台ms故障所带来的影响,可以使用“热备份”方案,就是用一台新的ms来取代有问题的ms,当然新的ms还是要用原来ms的IP地址,大不了数据重新装载一遍。
- 另外一种方式,就是提高你ms的节点数,然后mc会实时侦查每个节点的状态,如果发现某个节点长时间没有响应,就会从mc的可用server列 表里删除,并对server节点进行重新hash定位。当然这样也会造成的问题是,原本key存储在B上,变成存储在C上了。所以此方案本身也有其弱点, 最好能和“热备份”方案结合使用,就可以使故障造成的影响最小化。
- Memcached客户端(mc)
- Memcached客户端有各种语言的版本供大家使用,包括java,c,php,.net等等,具体可参见memcached api page[2]。
- 大家可以根据自己项目的需要,选择合适的客户端来集成。
- 缓存式的Web应用程序架构
- 有了缓存的支持,我们可以在传统的app层和db层之间加入cache层,每个app服务器都可以绑定一个mc,每次数据的读取都可以从ms中 取得,如果没有,再从db层读取。而当数据要进行更新时,除了要发送update的sql给db层,同时也要将更新的数据发给mc,让mc去更新ms中的
数据。
假设今后我们的数据库可以和ms进行通讯了,那可以将更新的任务统一交给db层,每次数据库更新数据的同时会自动去更新ms中的数据,这样就可以进一步减少app层的逻辑复杂度。如下图:- 不过每次我们如果没有从cache读到数据,都不得不麻烦数据库。为了最小化数据库的负载压力,我们可以部署数据库复写,用slave数据库来 完成读取操作,而master数据库永远只负责三件事:1.更新数据;2.同步slave数据库;3.更新cache。如下图:
- 以上这些缓存式web架构在实际应用中被证明是能有效并能极大地降低数据库的负载同时又能提高web的运行性能。当然这些架构还可以根据具体的应用环境进行变种,以达到不同硬件条件下性能的最优化。
- 未来的憧憬
- Memcached的出现可以说是革命性的,第一次让我们意识到可以用内存作为存储媒介来大规模的缓存数据以提高程序的性能。不过它毕竟还是比较新的东西,还需要很多有待优化和改进的地方,例如:
- 如何利用memcached实现cache数据库,让数据库跑在内存上。这方面,tangent software 开发的memcached_engine[3]已经做了不少工作,不过现在的版本还只是处于实验室阶段。
- 如何能方便有效的进行批量key清理。因为现在key是散列在不同的server上的,所以对某类key进行大批量清理是很麻烦的。因为 memcached本身是一个大hash表,是不具备key的检索功能的。所以memcached是压根不知道某一类的key到底存了多少个,都存在哪些 server上。而这类功能在实际应用中却是经常用到。
- posted @
2009-01-14 12:49 linFen 阅读(190) 评论(0) 编辑 - 756.
Memcached 实例 - 一、memcached 简介
- 在很多场合,我们都会听到
memcached 这个名字,但很多同学只是听过,并没有用过或实际了解过,只知道它是一个很不错的东东。这里简单介绍一下,memcached 是高效、快速的分布式内存对象缓存系统,主要用于加速 WEB 动态应用程序。 - 二、memcached 安装
- 首先是下载
memcached 了,目前最新版本是 1.1.12,直接从官方网站即可下载到 memcached-1.1.12.tar.gz。除此之外,memcached 用到了 libevent,我下载的是 libevent-1.1a.tar.gz。 - 接下来是分别将
libevent-1.1a.tar.gz 和 memcached-1.1.12.tar.gz 解开包、编译、安装: - # tar -xzf libevent-1.1a.tar.gz
# cd libevent-1.1a
# ./configure
--prefix=/usr
# make
# make install
# cd ..
# tar -xzf
memcached-1.1.12.tar.gz
# cd memcached-1.1.12
# ./configure
--prefix=/usr
# make
# make install - 安装完成之后,memcached
应该在 /usr/bin/memcached。 - 三、运行 memcached
守护程序 - 运行 memcached 守护程序很简单,只需一个命令行即可,不需要修改任何配置文件(也没有配置文件给你修改
): - /usr/bin/memcached -d -m 128 -l 192.168.1.1 -p 11211 -u httpd
- 参数解释:
- -d 以守护程序(daemon)方式运行 memcached;
-m 设置 memcached 可以使用的内存大小,单位为 M;
-l 设置监听的 IP 地址,如果是本机的话,通常可以不设置此参数;
-p 设置监听的端口,默认为 11211,所以也可以不设置此参数;
-u 指定用户,如果当前为 root 的话,需要使用此参数指定用户。
当然,还有其它参数可以用,man
memcached 一下就可以看到了。 - 四、memcached 的工作原理
- 首先 memcached 是以守护程序方式运行于一个或多个服务器
中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。 - 三、PHP 如何作为 memcached 客户端
- 有两种方法可以使 PHP 作为 memcached 客户端,调用 memcached 的服务进行对象存取操作。
- 第一种,PHP 有一个叫做 memcache 的扩展,Linux 下编译时需要带上 –enable-memcache[=DIR] 选项,Window 下则在 php.ini 中去掉 php_memcache.dll 前边的注释符,使其可用。
- 除此之外,还有一种方法,可以避开扩展、重新编译所带来的麻烦,那就是直接使用 php-memcached-client。
- 本文选用第二种方式,虽然效率会比扩展库稍差一些,但问题不大。
- 四、PHP
memcached 应用示例 - 首先 下载
memcached-client.php,在下载了 memcached-client.php 之后,就可以通过这个文件中的类“memcached”对 memcached 服务进行操作了。其实代码调用非常简单,主要会用到的方法有 add()、get()、replace()
和 delete(),方法说明如下:
add ($key, $val, $exp = 0) - 往 memcached 中写入对象,$key 是对象的唯一标识符,$val 是写入的对象数据,$exp 为过期时间,单位为秒,默认为不限时间;
- get ($key)
- 从 memcached 中获取对象数据,通过对象的唯一标识符 $key 获取;
- replace ($key,
$value, $exp=0) - 使用 $value 替换 memcached 中标识符为 $key 的对象内容,参数与 add() 方法一样,只有 $key 对象存在的情况下才会起作用;
- delete ($key,
$time = 0) - 删除 memcached 中标识符为 $key 的对象,$time 为可选参数,表示删除之前需要等待多长时间。
- 下面是一段简单的测试代码,代码中对标识符为 ‘mykey‘ 的对象数据进行存取操作:
- <pre>
<?php
// 包含memcached类文件
require_once(‘memcached-client.php‘);
// 选项设置
$options=array(
‘servers‘=>array(‘192.168.1.1:11211‘),//memcached服务的地址、端口,可用多个数组元素表示多个memcached服务
‘debug‘=>true, //是否打开debug
‘compress_threshold‘=>10240, //超过多少字节的数据时进行压缩
‘persistant‘=>false //是否使用持久连接
);
// 创建memcached对象实例
$mc=newmemcached($options);
// 设置此脚本使用的唯一标识符
$key=‘mykey‘;
// 往memcached中写入对象
$mc->add($key,‘somerandomstrings‘);
$val=$mc->get($key);
echo"n".str_pad(‘$mc->add()‘,60,‘_‘)."n";
var_dump($val);
// 替换已写入的对象数据值
$mc->replace($key,array(‘some‘=>‘haha‘,‘array‘=>‘xxx‘));
$val=$mc->get($key);
echo"n".str_pad(‘$mc->replace()‘,60,‘_‘)."n";
var_dump($val);
// 删除memcached中的对象
$mc->delete($key);
$val=$mc->get($key);
echo"n".str_pad(‘$mc->delete()‘,60,‘_‘)."n";
var_dump($val);
?>
</pre>
是 不是很简单,在实际应用中,通常会把数据库查询的结果集保存到 memcached 中,下次访问时直接从 memcached 中获取,而不再做数据库查询操作,这样可以在很大程度上减轻数据库的负担。通常会将 SQL 语句 md5() 之后的值作为唯一标识符 key。下边是一个利用 memcached 来缓存数据库查询结果集的示例(此代码片段紧接上边的示例代码): - <?php
$sql=‘SELECT*FROMusers‘;
$key=md5($sql); //memcached对象标识符
if(!($datas=$mc->get($key))){
// 在memcached中未获取到缓存数据,则使用数据库查询获取记录集。
echo"n".str_pad(‘ReaddatasfromMySQL.‘,60,‘_‘)."n";
$conn=mysql_connect(‘localhost‘,‘test‘,‘test‘);
mysql_select_db(‘test‘);
$result=mysql_query($sql);
while($row=mysql_fetch_object($result))
$datas[]=$row;
// 将数据库中获取到的结果集数据保存到memcached中,以供下次访问时使用。
$mc->add($key,$datas);
}else{
echo"n".str_pad(‘Readdatasfrommemcached.‘,60,‘_‘)."n";
}
var_dump($datas);
?> - 可以看出,使用
memcached 之后,可以减少数据库连接、查询操作,数据库负载下来了,脚本的运行速度也提高了。 - 之前我曾经写过一篇名为《PHP
实现多服务器共享 SESSION 数据》文章,文中的
SESSION 是使用数据库保存的,在并发访问量大的时候,服务器的负载会很大,经常会超出 MySQL 最大连接数,利用 memcached,我们可以很好地解决这个问题,工作原理如下: - 用户访问网页时,查看
memcached 中是否有当前用户的 SESSION 数据,使用 session_id() 作为唯一标识符;如果数据存在,则直接返回,如果不存在,再进行数据库连接,获取 SESSION 数据,并将此数据保存到 memcached 中,供下次使用; - 当前的 PHP 运行结束(或使用了 session_write_close())时,会调用 My_Sess::write()
方法,将数据写入数据库,这样的话,每次仍然会有数据库操作,对于这个方法,也需要进行优化。使用一个全局变量,记录用户进入页面时的 SESSION 数据,然后在 write() 方法内比较此数据与想要写入的 SESSION 数据是否相同,不同才进行数据库连接、写入数据库,同时将
memcached 中对应的对象删除,如果相同的话,则表示 SESSION 数据未改变,那么就可以不做任何操作,直接返回了; - 那么用户 SESSION 过期时间怎么解决呢?记得 memcached 的 add() 方法有个过期时间参数 $exp 吗?把这个参数值设置成小于 SESSION 最大存活时间即可。另外别忘了给那些一直在线的用户延续 SESSION 时长,这个可以在 write() 方法中解决,通过判断时间,符合条件则更新数据库数据。
- posted @
2009-01-14 12:44 linFen 阅读(547) 评论(0) 编辑 - 795.
memcached 高级应用 - 1. Key &
Hashcode - 当有多台 memcached
Server 存在时,memcached client library 依据以下规则来选择目标服务器。 - (1) 如果只有一台服务器,则直接返回。
- (2) 如果 hashcode == null,则使用 key.GetHashCode,有多种 HashingAlgorithm 可供选择。
- (3) 通过计算 hashcode % servers.count 来决定使用哪一台服务器。
- (4) 如果该服务器连接失败,则调整相关参数进行循环,直到获取可用服务器。
- 伪代码
- SockIO GetServer(key, hashcode)
{
if
(servers.count == 1) return GetConnection(servers[0]);
int hv =
hashcode != null ? hashcode : key.GetHashcode();
int tries =
0;
while
(++tires <= servers.count)
{
int index =
hv % servers.count;
if (index
< 0) index += servers.count;
SockIO sock
= GetConnection(servers[index]);
if (sock !=
null)
return
sock;
else
hv =
Modify(hv, key);
}
} - 我们可以通过
SockIOPool.HashingAlgorithm 属性设置不同的哈希算法。 - public enum HashingAlgorithm
{
///<summary>
/// native
String.hashCode() - fast (cached) but not compatible with other clients
///</summary>
Native = 0,
///<summary>
/// original
compatibility hashing alg (works with other clients)
///</summary>
OldCompatibleHash
= 1,
///<summary>
/// new
CRC32 based compatibility hashing algorithm (fast and works with other clients)
///</summary>
NewCompatibleHash
= 2
}
如果有必要的话,我们可以用下面这样的代码获知 (key, hashcode) 会被存储到哪台服务器上。 - var sock = SockIOPool.GetInstance().GetSock("a");
Console.WriteLine(sock.Host);
sock.Close(); - 获取对象的时候,也是用同样的算法首先决定从哪台server上获取数据。 相关细节可参考 SockIOPool.cs 文件 "public SockIO GetSock(string key, object hashCode)" 方法。在 MemcachedClient 中,所有的方法都会通过 "SockIOPool.GetInstance(poolName).GetSock(key,
hashCode);" 来和服务器进行交互,每种操作都提供了是否显式提供 hashcode 参数的方法重载。 - public bool Delete(string key);
public bool
Delete(string key, object hashCode, DateTime expiry);
... ... - 必要的时候,我们可以显式指定
hashcode。 - MemcachedClient mc = new MemcachedClient();
string key =
"cacheKey1";
object value =
SomeClass.getObject();
int hash = 45;
mc.set(key, value,
hash); - 2.
Serializable - MemcachedClient
使用二进制序列化引擎(BinaryFormatter)将要存储的对象转换为 byte 数组,因此非基元类型需要添加可序列化特性。 - [Serializable]
class MyClass
{
public int I
{ get; set; }
}
var mc = new
MemcachedClient();
mc.Set("a",
new MyClass { I = 1234});
var o =
mc.Get("a") as MyClass;
Console.WriteLine(o.I); - 相关细节可参考
MemcachedClient.cs "private bool Set(string cmdname, string key, object
obj, DateTime expiry, object hashCode, bool asString)" 方法。
3. Compression - MemcachedClient
通过调用 ICSharpCode.SharpZipLib.GZipOutputStream 对数据进行压缩处理。EnableCompression 属性就是压缩是否启用的开关,同时
CompressionThreshold 属性提供一个阀值,只有长度大于该阀值的数据才会被压缩 (默认 15KB)。 - 我们做一个试验。
- var mc = new MemcachedClient { EnableCompression = false };
for (int i = 0; i
< 300; i++)
{
mc.Set(i.ToString(),
new byte[1024 * 500]);
}
Console.WriteLine(mc.KeyExists("0"));
// ---------------
mc.FlushAll();
// ---------------
mc.EnableCompression
= true;
for (int i = 0; i
< 300; i++)
{
mc.Set(i.ToString(),
new byte[1024 * 500]);
}
Console.WriteLine(mc.KeyExists("0")); - 当我们不使用压缩时,每次传输
500KB 数据,很快 memcached Server 就达到内存上限,最先存入的数据会被移除。而启用压缩后,我们可以存储更多的数据。 - 相关细节可参考
MemcachedClient.cs "private bool Set(string cmdname, string key, object
obj, DateTime expiry, object hashCode, bool asString)" 方法。 - 5. Expiry
- MemcachedClient
允许我们在操作数据时设定失效时间。我们做个小实验看看效果。 - Console.WriteLine(DateTime.Now);
Console.WriteLine("---------------");
var mc = new
MemcachedClient();
mc.Set("a",
1234, DateTime.Now.AddSeconds(10));
for (int i = 0; i
< 20; i++)
{
Thread.Sleep(1000);
Console.WriteLine("{0}
- {1}", DateTime.Now, mc.KeyExists("a"));
} - 虽然这个实验的计时不太准确,但输出结果基本验证了失效时间的设定。
- 4. SockIO
- 我们可以使用 SockIO 直接操作协议,通过发送相关命令进行数据读写操作。
- 演示: 获取指定服务器的版本信息。
- var sock =
SockIOPool.GetInstance().GetConnection("192.168.1.101:1234");
try
{
sock.Write(Encoding.UTF8.GetBytes("versionrn"));
sock.Flush();
Console.WriteLine(sock.ReadLine());
}
catch (IOException
ioe)
{
Console.WriteLine(ioe);
}
finally
{
sock.Close();
} - posted @
2009-01-14 12:41 linFen 阅读(251) 评论(1) 编辑 - 829.
自己实现memcached客户端库 - What‘s memcached ?
- memcached是一个以key-value的形式缓存数据的缓存系统。通过将数据缓存到内存中,从而提高数据的获取速度。
- memcached以key-value的形式来保存数据,你可以为你每一段数据关联一个key,然后以后可以通过这个key获取
- 这段数据。
- memcached是一个库还是什么?memcached其实是一个单独的网络服务器程序。它的网络底层基于libevent,你可以
- 将其运行在网络中的一台服务器上,通过网络,在遵循memcached的协议的基础上与memcached服务器进行通信。
- What do we
want to wrap ? - 我们需要做什么?我们只需要遵循memcached的协议(参见该文档),封装网络层的通信,让上层可以通过调用诸如
- add/get之类的接口即可实现往memcached服务器缓存数据,以及取数据。上层程序员根本不知道这些数据在网络
- 上存在过。
- 这个东西,也就是memcached官方所谓的client apis。你可以使用现成的客户端库,但是你也可以将这种重造轮子
- 的工作当作一次网络编程的练习。it‘s up to you.:D
- Where to start
? - 很遗憾,对于windows用户而言,memcached官方没有给出一个可以执行或者可以直接F7即可得到可执行文件的下载
- (如果你是vc用户)。幸运的是,已经有人做了这个转换工作。
- 你可以从http://jehiah.cz/projects/memcached-win32/这里下载到memcached的windows版本,包括可执行程序和
- 源代码。
- 我们直接可以运行memcached.exe来安装/开启memcached服务器,具体步骤在以上页面有所提及:
- 安装:memcached.exe-dinstall,这会在windows服务里添加一个memcached服务
行:memcached.exe-dstart,你也可以通过windows的服务管理运行。 - 然后,你可以在任务管理器里看到一个‘memcached‘的进程,很占内存,因为这是memcached。
- So, here we go
... - 通过以上步骤运行的memcached,默认在11211端口监听(是个TCP连接,可以通过netstat查看)。接下来,我们就可
- 以connect到该端口上,然后send/recv数据了。发送/接收数据只要遵循memcached的协议格式,一切都很简单。
- 使用最简单的阻塞socket连接memcached服务器:
- SOCKET s
= socket( AF_INET, SOCK_STREAM, 0 );
struct
sockaddr_in addr;
memset(
&addr, 0, sizeof( addr ) );
addr.sin_family
= AF_INET;
addr.sin_port
= htons( 11211 );
addr.sin_addr.s_addr
= inet_addr( "127.0.0.1" ); - ret = connect( s, (struct sockaddr*) &addr, sizeof( addr ) );
if( ret
== 0 )
{
printf(
"connect okn" );
} - About the
protocol - 简单地提一下memcached的协议。
- 可以说,memcached的协议是基于行的协议,因为无论是客户端请求还是服务器端应答,都是以"rn"作为结束符。
memcached的协议将数据(send/recv操作的数据)分为两种类型:命令和用户数据。 - 命令用于服务器和客户端进行交互;而用户数据,很显然,就是用户想要缓存的数据。
- 关于用户数据,你只需要将其编码成字节流(说白了,只要send函数允许即可),并附带数据结束标志"rn"发送即可。
- 关于命令,memcached分为如下几种命令:存储数据、删除数据、取出数据、其他一些获取信息的命令。其实你换个角度
- 想想,memcached主要就是将数据存储到内存里,所以命令也多不了哪去,基本就停留在add/get/del上。
- 关于key,memcached用key来标识数据,每一个key都是一个不超过255个字符的字符串。
- 到这里,你可以发现memcached对于数据的存储方式(暴露给上层)多少有点像std::map,如果你愿意,你可以将客户端
- API封装成map形式。= =#
- 具体实现
- 接下来可以看看具体的实现了。
- 首先看看存储数据命令,存储数据命令有:add/set/replace/append/prepend/cas。存储命令的格式为:
- <command
name> <key> <flags> <exptime> <bytes> [noreply]rn - 具体字段的含义参看protocol.txt文件,这里我对set举例,如下代码,阻塞发送即可:
- charcmd[256];
chardata[]="testdata";
sprintf(cmd,"setTestKey00%drn",strlen(data));
ret=send(s,cmd,strlen(cmd),0); - 注意:noreply选项对于有些memcached版本并不被支持,例如我们使用的1.2.2版本。注意官方的changelog即可。
- 当你发送了存储命令后,memcached会等待客户端发送数据块。所以我们继续发送数据块:
- ret=send(s,data,strlen(data),0);
ret=send(s,"rn",2,0);//数据结束符 - 然后,正常的话,memcached服务器会返回应答信息给客户端。
- charreply[256];
ret=recv(s,reply,sizeof(reply)-1,0);
reply[ret]=0;
printf("serverreply:%sn",reply); - 如果存储成功,服务器会返回STORED字符串。memcached所有应答信息都是以字符串的形式给出的。所以可以直接printf出来。
- 关于其他的操作,我就不在这里列举例子了。我提供了我封装的memcached客户端库的完整代码下载,使用的是阻塞socket,
- 对应着memcached的协议看,很容易看懂的。
- It‘s a story
about a programmer... - 最近发觉自己有点极端,要么写纯C的代码,要么写满是template的泛型代码。