memcached使用文档

更新时间:2024-05-28 16:49:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

使用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[\\$options[\

$memc = new MemCachedClient($options); $myarr = array(\$memc->set(\$val = $memc->get(\

print $val[0].\n\ print $val[1].\n\ print $val[2].\为什么不使用数据库做这些?

暂且不考虑使用什么样的数据库(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的特征 o 协议简单

o 基于libevent的事件处理 o 内置内存存储方式

o memcached不互相通信的分布式 安装memcached

o memcached的安装 o memcached的启动 用客户端连接

使用Cache::Memcached

o 使用Cache::Memcached连接memcached o 保存数据 o 获取数据 o 删除数据

o 增一和减一操作 总结

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。

?

http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

基于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 = \my $value = \

my $expires = 3600; # 1 hour

my $memcached = Cache::Memcached->new({ servers => [\ compress_threshold => 10_000 });

$memcached->add($key, $value, $expires); my $ret = $memcached->get($key); print \

在这里,为Cache::Memcached指定了memcached服务器的IP地址和一个选项,以生成实例。 Cache::Memcached常用的选项如下所示。

选项 servers namespace

说明

用数组指定memcached服务器和端口 指定添加到键的前缀

compress_threshold 数据压缩时使用的值

另外,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 set

仅当存储空间中不存在键相同的数据时才保存 与add和replace不同,无论何时都保存 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。因此,应当进行错误检查,必要时加入初始化操作。而且,服务器端也不会对超过232时的行为进行检查。

总结

这次简单介绍了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代码

1. package com.danga.MemCached.test; 2.

3. import java.util.Date; 4.

5. import com.danga.MemCached.MemCachedClient; 6. import com.danga.MemCached.SockIOPool; 7. 8.

9. public class Test {

10. protected static MemCachedClient mcc = new MemCachedClient(); 11.

12. static {

13. String[] servers ={\}; 14.

15. Integer[] weights = { 3 }; 16.

17. //创建一个实例对象SockIOPool

18. SockIOPool pool = SockIOPool.getInstance(); 19.

20. // set the servers and the weights 21. //设置Memcached Server

22. pool.setServers( servers ); 23. pool.setWeights( weights ); 24.

25. // set some basic pool settings

26. // 5 initial, 5 min, and 250 max conns 27. // and set the max idle time for a conn 28. // to 6 hours

29. pool.setInitConn( 5 ); 30. pool.setMinConn( 5 ); 31. pool.setMaxConn( 250 );

32. pool.setMaxIdle( 1000 * 60 * 60 * 6 ); 33.

34. // set the sleep for the maint thread 35. // it will wake up every x seconds and

36. // maintain the pool size 37. pool.setMaintSleep( 30 ); 38.

39. // Tcp的规则就是在发送一个包之前,本地机器会等待远程主机

40. // 对上一次发送的包的确认信息到来;这个方法就可以关闭套接字的缓存,

41. // 以至这个包准备好了就发; 42. pool.setNagle( false ); 43. //连接建立后对超时的控制

44. pool.setSocketTO( 3000 ); 45. //连接建立时对超时的控制

46. pool.setSocketConnectTO( 0 ); 47.

48. // initialize the connection pool

49. //初始化一些值并与MemcachedServer段建立连接 50. pool.initialize(); 51. 52.

53. // lets set some compression on for the client

54. // compress anything larger than 64k 55. mcc.setCompressEnable( true );

56. mcc.setCompressThreshold( 64 * 1024 ); 57. } 58.

59. public static void bulidCache(){

60. //set(key,value,Date) ,Date是一个过期时间,如果想让这个过期时间生效的话,这里传递的new Date(long date) 中参数date,需要是个大于或等于1000的值。

61. //因为java client的实现源码里是这样实现

的 expiry.getTime() / 1000 ,也就是说,如果 小于1000的值,除以1000以后都是0,即永不过期

62. mcc.set( \, \ ,new Date(11211));

63. //十秒后过期 64. 65. } 66.

67. public static void output() { 68. //从cache里取值

69. String value = (String) mcc.get( \ ); 70. System.out.println(value); 71. }

72.

73. public static void main(String[] args){ 74. bulidCache(); 75. output(); 76. } 77. 78.}

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下输入 \安装. 3. 再输入:\启动。NOTE: 以后memcached将作为windows的一个服务每次开机时自动启动。这样服务器端已经安装完毕了。

85.如果下载的是二进制的版本,直接运行就可以了,可以加上参数来加以设置。 86.

常用设置:

-p 监听的端口

-l 连接的IP地址, 默认是本机 -d start 启动memcached服务 -d restart 重起memcached服务

-d stop|shutdown 关闭正在运行的memcached服务 -d install 安装memcached服务 -d uninstall 卸载memcached服务

-u 的身份运行 (仅在以root运行的时候有效)

-m 最大内存使用,单位MB。默认64MB -M 内存耗尽时返回错误,而不是删除项 -c 最大同时连接数,默认是1024 -f 块大小增长因子,默认是1.25

-n 最小分配空间,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是非常不划算的。

100. Memcached在很多时候都是作为数据库前端cache使用的。因为它比数据库少了很多SQL解析、磁盘操作等开销,而且它是使用内存 来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的,memcached可以大大降低数据库压力,使系统 执行效率提升。另外,memcached也经常作为服务器之间数据共享的

存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在 memcached中,被多个应用共享。

101. 需要注意的是,memcached使用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以 memcached不能用来持久保存数据。很多人的错误理解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用 内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接 的读写方式,它可以轻松应付非常大的数据交换量,所以经常会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。

102. ◎Memcached的工作方式

103. 以下的部分中,读者最好能准备一份memcached的源代码。 104. Memcached是传统的网络服务程序,如果启动的时候使用了-d参数,它会以守护进程的方式执行。创建守护进程由daemon.c完成,这个程序只有一个daemon函数,这个函数很简单(如无特殊说明,代码以1.2.1为准):

105. #include 106. #include 107. #include 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(\{

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. }

138. 这个函数 fork 了整个进程之后,父进程就退出,接着重新定位 STDIN 、 STDOUT 、 STDERR 到空设备, daemon 就建立成功了。 139. Memcached 本身的启动过程,在 memcached.c 的 main 函数中顺序如下:

140. 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 函数进入循环。

141. 在 daemon 方式中,因为 stderr 已经被定向到黑洞,所以不会反馈执行中的可见错误信息。

142. memcached.c 的主循环函数是 drive_machine ,传入参数是指向当前的连接的结构指针,根据 state 成员的状态来决定动作。

143. Memcached 使用一套自定义的协议完成数据交换,它的 protocol 文档可以参考:

http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

144. 在API中,换行符号统一为\\r\\n 145. ◎Memcached的内存管理方式

146. Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针。Memcached使用slab->chunk的组织方式管理内存。

147. 1.1和1.2的slabs.c中的slab空间划分算法有一些不同,后面会分别介绍。

148. 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。

149. slab有一个初始chunk大小,1.1中是1字节,1.2中是80字节,1.2中有一个factor值,默认为1.25

150. 在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(\171. if (t_initial_malloc) { 172. mem_malloced =

atol(getenv(\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(\180. if (!pre_alloc || atoi(pre_alloc)) {

181. slabs_preallocate(limit / POWER_BLOCK); 182. } 183. } 184. }

185. 在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: 186. 187.

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, \perslab ]\\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(\220. if (t_initial_malloc) { 221. mem_malloced =

atol(getenv(\222. } 223.

224. } 225.

226. #ifndef DONT_PREALLOC_SLABS 227. {

228. char *pre_alloc = getenv(\229. if (!pre_alloc || atoi(pre_alloc)) {

230. slabs_preallocate(limit / POWER_BLOCK); 231. } 232. } 233. #endif 234. }

235. 由上可以看出,memcached的内存分配是有冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就被丢弃了,如id40中,两个chunk占用了1009384字节,这个slab一共有1MB,那么就有39192字节被浪费了。

236. 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位系 统)。

237. Memcached初始化的时候,会初始化slab(前面可以看到,在main函数中调用了slabs_init())。它会在 slabs_init()中检查一个常量DONT_PREALLOC_SLABS,如果这个没有被定义,说明使用预分配内存方式初始化slab,这样在所 有已经定义过的slabclass中,每一个id创建

一个slab。这样就表示,1.2在默认的环境中启动进程后要分配41MB的slab空间,在这个过 程里,memcached的第二个内存冗余发生了,因为有可能一个id根本没有被使用过,但是它也默认申请了一个slab,每个slab会用掉1MB内存

238. 当一个slab用光后,又有新的item要插入这个id,那么它就会重新申请新的slab,申请新的slab时,对应id的slab链表就要增长,这个链表是成倍增长的,在函数grow_slab_list函数中,这个链的长度从1变成2,从2变成4,从4变成8??: 239.

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. }

251. 在定位item时,都是使用slabs_clsid函数,传入参数为item大小,返回值为classid,由这个过程可以看 出,memcached的第三个内存冗余发生在保存item的过程中,item总是小于或等于chunk大小的,当item小于chunk大小时,就又发 生了空间浪费。 252. ◎Memcached的NewHash算法

253. Memcached的item保存基于一个大的hash表,它的实际地址就是slab中的chunk偏移,但是它的定位是依靠对key做 hash的结果,在primary_hashtable中找到的。在assoc.c和items.c中定义了所有的hash和item操作。

254. Memcached使用了一个叫做NewHash的算法,它的效果很好,效率也很高。1.1和1.2的NewHash有一些不同,主要的实现方式还是一样的,1.2的hash函数是经过整理优化的,适应性更好一些。 255. NewHash的原型参考:

http://burtleburtle.net/bob/hash/evahash.html。数学家总是有点奇怪,呵呵~

256. 为了变换方便,定义了u4和u1两种数据类型,u4就是无符号的长整形,u1就是无符号char(0-255)。

257. 具体代码可以参考1.1和1.2源码包。

258. 注意这里的hashtable长度,1.1和1.2也是有区别的,1.1中定义了HASHPOWER常量为20,hashtable表长为 hashsize(HASHPOWER),就是4MB(hashsize是一个宏,表示1右移n位),1.2中是变量16,即hashtable表长 65536: 259.

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) 265. 在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上。

266. items.c是数据项的操作程序,每一个完整的item包括几个部分,在item_make_header()中定义为: 267. key:键 nkey:键长

flags:用户定义的flag(其实这个flag在memcached中没有启用) nbytes:值长(包括换行符号\\r\\n) suffix:后缀Buffer nsuffix:后缀长

268. 一个完整的item长度是键长+值长+后缀长+item结构大小(32字节),item操作就是根据这个长度来计算slab的classid的。 269. hashtable中的每一个桶上挂着一个双链表,item_init()的时候已经初始化了heads、tails、sizes三个数组 为0,这三个数组的大小都为常量LARGEST_ID(默认为255,这个值需要配合factor来修改),在每次item_assoc()的时候,它会 首先尝试从slab中获取一块空闲的chunk,如果没有可用的chunk,会在链表中扫描50次,以得到一个被LRU踢掉的item,将它 unlink,然后将需要插入的item插入链表中。 270. 注意item的refcount成员。item被unlink之后只是从链表上摘掉,不是立刻就被free的,只是将它放到删除队列中(item_unlink_q()函数)。 271. item对应一些读写操作,包括remove、update、replace,当然最重要的就是alloc操作。

272. item还有一个特性就是它有过期时间,这是memcached的一个很有用的特性,很多应用都是依赖于memcached的item过 期,比如session存储、操作锁等。item_flush_expired()函数就是扫描表中的item,对过期的item执行unlink操作, 当然这只是一个回收动作,实际上在get的时候还要进行时间判断: 273. 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. } 323.

324. Memcached的内存管理方式是非常精巧和高效的,它很大程度上减少了直接alloc系统内存的次数,降低函数开销和内存碎片产生几率,虽然这种方式会造成一些冗余浪费,但是这种浪费在大型系统应用中是微不足道的。

325. ◎Memcached的理论参数计算方式 326. 影响 memcached 工作的几个参数有: 327. 常量REALTIME_MAXDELTA 60*60*24*30 最大30天的过期时间

328. conn_init()中的freetotal(=200) 最大同时连接数

329. 常量KEY_MAX_LENGTH 250 最大键长

330. settings.factor(=1.25) factor将影响chunk的步进大小 331. settings.maxconns(=1024) 最大软连接

332. settings.chunk_size(=48) 一个保守估计的key+value长度,用来生成id1中的chunk长度(1.2)。id1的chunk长度等于这个数值加上item结构体的长度(32),即默认的80字节。

333. 常量POWER_SMALLEST 1 最小classid(1.2)

334. 常量POWER_LARGEST 200 最大classid(1.2)

335. 常量POWER_BLOCK 1048576 默认slab大小

336. 常量CHUNK_ALIGN_BYTES (sizeof(void *))

保证chunk大小是这个数值的整数倍,防止越界(void *的长度在不同系统上不一样,在标准32位系统上是4) 337. 常量ITEM_UPDATE_INTERVAL 60 队列刷新间隔

338. 常量LARGEST_ID 255

最大item链表数(这个值不能比最大的classid小) 339. 变量hashpower(在1.1中是常量HASHPOWER) 决定hashtable的大小

340. 根据上面介绍的内容及参数设定,可以计算出的一些结果: 341. 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全部分配满)。 342. ◎Memcached的定长优化 343. 根据上面几节的描述,多少对memcached有了一个比较深入的认识。在深入认识的基础上才好对它进行优化。

344. Memcached本身是为变长数据设计的,根据数据特性,可以说它是“面向大众”的设计,但是很多时候,我们的数据并不是这样的“普 遍”,典型的情况中,一种是非均匀分布,即数据长度集中在几个区域内(如保存用户 Session);另一种更极端的状态是等长数据(如定长键值,定长数据,多见于访问、在线统计或执行锁)。

345. 这里主要研究一下定长数据的优化方案(1.2),集中分布的变长数据仅供参考,实现起来也很容易。

346. 解决定长数据,首先需要解决的是slab的分配问题,第一个需要确认的是我们不需要那么多不同chunk长度的slab,为了最大限度地利用资源,最好chunk和item等长,所以首先要计算item长度。

347. 在之前已经有了计算item长度的算法,需要注意的是,除了字符串长度外,还要加上item结构的长度32字节。

348. 假设我们已经计算出需要保存200字节的等长数据。

349. 接下来是要修改slab的classid和chunk长度的关系。在原始版本中,chunk长度和classid是有对应关系的,现在如果 把所有的chunk都定为200个字节,那么这个关系就不存在了,我们需要重新确定这二者的关系。一种方法是,整个存储结构只使用一个固定的id,即只使 用199个槽中的1个,在这种条件下,就一定要定义DONT_PREALLOC_SLABS来避免另外的预分配浪费。另一种方法是建立一个hash关系, 来从item确定classid,不能使用长度来做键,可以使用key的NewHash结果等不定数据,或者直接根据key来做hash(定长数据的 key也一定等长)。这里简单起见,选择第一种方法,这种方法的不足之处在于只使用一个id,在数据量非常大的情况下,slab链会很长(因为所有数据都 挤在一条链上了),遍历起来的代价比较高。

350. 前面介绍了三种空间冗余,设置chunk长度等于item长度,解决了第一种空间浪费问题,不预申请空间解决了第二种空间浪费问题,那么对 于第一种问题(slab内剩余)如何解决呢,这就需要修改POWER_BLOCK

常量,使得每一个slab大小正好等于chunk长度的整数倍,这样一个 slab就可以正好划分成n个chunk。这个数值应该比较接近1MB,过大的话同样会造成冗余,过小的话会造成次数过多的alloc,根据chunk长 度为200,选择1000000作为POWER_BLOCK的值,这样一个slab就是100万字节,不是1048576。三个冗余问题都解决了,空间利 用率会大大提升。 351. 修改 slabs_clsid 函数,让它直接返回一个定值(比如 1 ): 352.

353. unsigned int slabs_clsid(size_t size) { 354. return 1; 355. }

356. 修改slabs_init函数,去掉循环创建所有classid属性的部分,直接添加slabclass[1]:

357. slabclass[1].size = 200; //每chunk200字节 358. slabclass[1].perslab = 5000; //1000000/200 359. ◎Memcached客户端

360. Memcached是一个服务程序,使用的时候可以根据它的协议,连接到memcached服务器上,发送命令给服务进程,就可以操作上面 的数据。为了方便使用,memcached有很多个客户端程序可以使用,对应于各种语言,有各种语言的客户端。基于C语言的有libmemcache、 APR_Memcache;基于Perl的有Cache::Memcached;另外还有Python、Ruby、Java、C#等语言的支持。PHP的 客户端是最多的,不光有mcache和PECL memcache两个扩展,还有大把的由PHP编写的封装类,下面介绍一下在PHP中使用memcached的方法:

361. mcache扩展是基于libmemcache再封装的。libmemcache一直没有发布stable版本,目前版本是1.4.0- rc2,可以在这里找到。libmemcache有一个很不好的特性,就是会向stderr写很多错误信息,一般的,作为lib使用的时候,stderr 一般都会被定向到其它地方,比如Apache的错误日志,而且libmemcache会自杀,可能会导致异常,不过它的性能还是很好的。

362. mcache扩展最后更新到1.2.0-beta10,作者大概是离职了,不光停止更新,连网站也打不开了(~_~),只能到其它地方去获 取这个不负责的扩展了。解压后安装方法如常:phpize & configure & make & make install,一定要先安装libmemcache。使用这个扩展很简单: 363. 364.

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. ?>

379. 这个扩展的好处是可以很方便地实现分布式存储和负载均衡,因为它可以添加多个服务地址,数据在保存的时候是会根据hash结果定位到某台服务器上 的,这也是libmemcache的特性。libmemcache支持集中hash方式,包括CRC32、ELF和Perl hash。

380. PECL memcache是PECL发布的扩展,目前最新版本是2.1.0,可以在pecl网站得到。memcache扩展的使用方法可以在新一些的PHP手册中找到,它和mcache很像,真的很像: 381. 382.

384. $memcache = new Memcache;

385. $memcache->connect('localhost', 11211) or die (\connect\386.

387. $version = $memcache->getVersion(); 388. echo \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 (\to save data at the server\

395. echo \data in the cache (data will expire in 10 seconds)n\396.

397. $get_result = $memcache->get('key'); 398. echo \399.

400. var_dump($get_result); 401. 402. ?>

403. 这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善,也不 支持add_server这种分布操作,但是因为它不依赖其它的外界程序,兼容性要好一些,也比较稳定。至于效率,差别不是很大。

404. 另外,有很多的PHP class可以使用,比如MemcacheClient.inc.php,phpclasses.org上可以找到很多,一般都是对perl client API的再封装,使用方式很像。 405.

406. ◎BSM_Memcache

407. 从C client来说,APR_Memcache是一个很成熟很稳定的client程序,支持线程锁和原子级操作,保证运行的稳定性。不过它是基于APR的 (APR将在最后一节介绍),没有libmemcache的应用范围广,目前也没有很多基于它开发的程序,现有的多是一些Apache Module,因为它不能脱离APR环境运行。但是APR倒是可以脱离Apache单独安装的,在APR网站上可以下载APR和APR-util,不需要 有Apache,可以直接安装,而且它是跨平台的。

408. BSM_Memcache是我在BS.Magic项目中开发的一个基于

APR_Memcache的PHP扩展,说起来有点拗口,至少它把APR扯进了PHP扩展中。这个程序很简单,也没做太多的功能,只是一种形式的尝试,它支持服务器分组。

409. 和mcache扩展支持多服务器分布存储不同,BSM_Memcache支持多组服务器,每一组内的服务器还是按照hash方式来分布保存 数据,但是两个组中保存的数据是一样的,也就是实现了热备,它不会因为一台服务器发生单点故障导致数据无法获取,除非所有的服务器组都损坏(例如机房停 电)。当然实现这个功能的代价就是性能上的牺牲,在每次添加删除数据的时候都要扫描所有的组,在get数据的时候会随机选择一组服务器开始轮询,一直到找 到数据为止,正常情况下一次就可以获取得到。 410. BSM_Memcache只支持这几个函数: 411.

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. };

422. mc_add_group函数返回一个整形(其实应该是一个object,我偷懒了~_~)作为组ID,mc_add_server的时候要提供两个参数,一个是组ID,一个是服务器地址(ADDR : PORT)。 423. 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, \&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. }

507. 在set和del数据的时候,要循环所有的组: 508. 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, \&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. }

553. 在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, \&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. }

618. BSM_Memcache的使用方式和其它的client类似: 619.

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(); // 关闭所有组 631. ?>

632. APR_Memcache的相关资料可以在这里找到,BSM_Memcache可以在网络上找下载。

633. ◎APR环境介绍 634. APR的全称:Apache Portable Runtime。它是Apache软件基金会创建并维持的一套跨平台的C语言库。它从Apache httpd1.x中抽取出来并独立于httpd之外,Apache httpd2.x就是建立在APR上。APR提供了很

多方便的API接口可供使用,包括如内存池、字符串操作、网络、数组、hash表等实用的功能。开发 Apache2 Module要接触很多APR函数,当然APR可以独立安装独立使用,可以用来写自己的应用程序,不一定是Apache httpd的相关开发。 635. ◎后记

636. 这是我在农历丙戌年(我的本命年)的最后一篇文章,由于Memcached的内涵很多,仓促整理一定有很多遗漏和错误。感谢新浪网提供的研究机会,感谢部门同事的帮助。

637. 分布式缓存系统Memcached简介与实践

638. 一 Memcached服务器端的安装 (此处将其作为系统服务安装) 639. 下载文件:memcached 1.2.1 for Win32 binaries (Dec 23, 2006) 640. 1 解压缩文件到c:memcached

641. 2命令行输入 'c:memcachedmemcached.exe -d install' 642. 3 命令行输入 'c:memcachedmemcached.exe -d start',该命令启动 Memcached ,默认监听端口为 11211

643. 通过 memcached.exe -h 可以查看其帮助 644. 二 .NET memcached client library 645. 下载文件:

https://sourceforge.net/projects/memcacheddotnet/

646. 里面有.net1.1 和 .net2.0的两种版本 还有一个不错的例子。

647. 三 应用

648. 1 将Commons.dll,ICSharpCode.SharpZipLib.dll,log4net.dll,Memcached.ClientLibrary.dll 等放到bin目录 649. 2 引用Memcached.ClientLibrary.dll 650. 3 代码

651. 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={\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(\//存储数据到缓存服务器,这里将字符串\缓存,key是\38

39 if(mc.KeyExists(\//测试缓存存在key为test的项目

40 {

41 Console.WriteLine(\

42 Console.WriteLine(mc.Get(\//在缓存中获取key为test的项目 43 } 44 else 45 {

46 Console.WriteLine(\47 } 48

49 Console.ReadLine(); 50

51 mc.Delete(\//移除缓存中key为test的项目 52

53 if(mc.KeyExists(\54 {

55 Console.WriteLine(\

56 Console.WriteLine(mc.Get(\

57 } 58 else 59 {

60 Console.WriteLine(\61 }

62 Console.ReadLine(); 63

64 SockIOPool.GetInstance().Shutdown(); //关闭池,关闭sockets 65 } 66 } 67}

652. 4 运行结果

653.

654. 后记: 是个不错的东西 ,使用起来也很方便,php ,ruby 的项目中用这个的很多,但是.net项目中用的较少(恕俺孤陋寡闻) 。希望有兴趣的朋友们 多多交流 。

655. 在Win开发环境下面配置了一下Memcached

656.

开发环境win下面配置使用Memcached方法概述

657. 再简单的事情没有做一遍都不能明白其中时候如此,今天配置Memcached就发现这个问题。帮助很全,先是在memcached for Windows获取到了需要的win下面的Memcached,按照方法: 658. 引用 659. 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 660. 然后

661. 在php.ini 加入一行 'extension=php_memcache.dll' 662. 然后到

http://pecl4win.php.net/ext.php/php_memcache.dll获取php_memcache.dll

663. 并复制到 ext 中(记住版本不要错了!)

664. 重启Apache,发现PHPInfo就是提示出不来memcache,真是无语了,代码测试总是提示 665. 引用

666. Fatal error: Class 'Memcache' not found in D:xampplitehtdocsmemcacheindex.php on line 20 667. 开始觉得奇怪,于是搜索在官方网站发现了http://www.php.net/manual/zh/ref.memcache.php 668.

669. 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 \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 \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. 670.

试试态度加上看了一下,看到了熟悉的东西了: 671. view plaincopy to clipboardprint? 672. $mem=newMemcache;

673. $mem->connect('127.0.0.1',11211); 674.

675. $mem->set('key','Thisisatest!',0,60); 676.

677. $val=$mem->get('key'); 678. echo$val;

679. $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;

680. 在浏览器输出了 681. This is a test!

682. 另外作为服务器开发者,自然很关注的一个问题就是这个

Memcached的原理问题,在老农如是想,如是说,如是为博客中提到: 683. 引用

684. Memcached 本身的启动过程,在 memcached.c 的 main 函数中顺序如下:

685. 1 、调用 settings_init() 设定初始化参数 686. 2 、从启动命令中读取参数来设置 setting 值 687. 3 、设定 LIMIT 参数

688. 4 、开始网络 socket 监听(如果非 socketpath 存在)( 1.2 之后支持 UDP 方式)

689. 5 、检查用户身份( Memcached 不允许 root 身份启动) 690. 6 、如果有 socketpath 存在,开启 UNIX 本地连接(Sock 管道)

691. 7 、如果以 -d 方式启动,创建守护进程(如上调用 daemon 函数)

692. 8 、初始化 item 、 event 、状态信息、 hash 、连接、 slab 693. 9 、如设置中 managed 生效,创建 bucket 数组 694. 10 、检查是否需要锁定内存页 695. 11 、初始化信号、连接、删除队列 696. 12 、如果 daemon 方式,处理进程 ID

697. 13 、event 开始,启动过程结束, main 函数进入循环。 698. posted @ 2009-01-14 13:01 linFen 阅读(321) 评论(0) 编辑

699. 利用memcached构建高性能的Web应用程序

700. 面临的问题

701. 对于高并发高访问的Web应用程序来说,数据库存取瓶颈一直是个令人头疼的问题。特别当你的程序架构还是建立在单数据库模式,而一个数据池连接数峰值已经达到500的时候,那你的程序运行离崩溃的边缘也不远了。很多小网站的开发人员一开始都将注意力放在了产品需求设计上,缺忽视了程序整体性能,可扩展性等方面的考虑,结果眼看着访问量一天天网上爬,可突然发现有一天网站因为访问量过大而崩溃了,到时

候哭都来不及。所以我们一定要未雨绸缪,在数据库还没罢工前,想方设法给它减负,这也是这篇文章的主要议题。

702. 大家都知道,当有一个request过来后,web服务器交给app服务器,app处理并从db中存取相关数据,但db存取的花费是相当高昂 的。特别是每次都取相同的数据,等于是让数据库每次都在做高耗费的无用功,数据库如果会说话,肯定会发牢骚,你都问了这么多遍了,难道还记不住吗?是啊, 如果app拿到第一次数据并存到内存里,下次读取时直接从内存里读取,而不用麻烦数据库,这样不就给数据库减负了?而且从内存取数据必然要比从数据库媒介 取快很多倍,反而提升了应用程序的性能。

703. 因此,我们可以在web/app层与db层之间加一层cache层,主要目的:1. 减少数据库读取负担;2. 提高数据读取速度。而且,cache存取的媒介是内存,而一台服务器的内存容量一般都是有限制的,不像硬盘容量可以做到TB级别。所以,可以考虑采用分布 式的cache层,这样更易于破除内存容量的限制,同时又增加了灵活性。 704. Memcached 介绍

705. Memcached是开源的分布式cache系统,现在很多的大型web应用程序包括 facebook,youtube,wikipedia,yahoo等等都在使用memcached来支持他们每天数亿级的页面访问。通过把cache层 与他们的web架构集成,他们的应用程序在提高了性能的同时,还大大降低了数据库的负载。 706.

具体的memcached资料大家可以直接从它的官方网站[1]上得到。这里我就简单给大家介绍一下memcached的工作原理:

707. Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。

708. Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先通过计算key 的hash值来确定kv对所处在的ms位置。当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。因为这之间没有交互以及多播 协议,所以memcached交互带给网络的影响是最小化的。

709. 举例说明:考虑以下这个场景,有三个mc分别是X,Y,Z,还有三个ms分别是A,B,C: 710. 设置kv对

711. X想设置key=”foo”,value=”seattle”

712. X拿到ms列表,并对key做hash转化,根据hash值确定kv对所存的ms位置 713. B被选中了 714. X连接上B,B收到请求,把(key=”foo”,value=”seattle”)存了起来

715. 获取kv对

716. Z想得到key=”foo”的value

717. Z用相同的hash算法算出hash值,并确定key=”foo”的值存在B上

718. Z连接上B,并从B那边得到value=”seattle” 719. 其他任何从X,Y,Z的想得到key=”foo”的值的请求都会发向B

720. 721. Memcached服务器(ms) 722. 内存分配

723. 默认情况下,ms是用一个内置的叫“块分配器”的组件来分配内存的。舍弃c++标准的malloc/free的内存分配,而采用块分配器的主要 目的是为了避免内存碎片,否则操作系统要花费更多时间来查找这些逻辑上连续的内存块(实际上是断开的)。用了块分配器,ms会轮流的对内存进行大块的分 配,并不断重用。当然由于块的大小各不相同,当数据大小和块大小不太相符的情况下,还是有可能导致内存的浪费。 同时,ms对key和data都有相应的限制,key的长度不能超过250字节,data也不能超过块大小的限制 --- 1MB。 724. 因为mc所使用的hash算法,并不会考虑到每个ms的内存大小。理论上mc会分配概率上等量的kv对给每个ms,这样如果每个ms的内存都不 太一样,那可能会导致内存使用率的降低。所以一种替代的解决方案是,根据每个ms的内存大小,找出他们的最大公约数,然后在每个ms上开n个容量=最大公 约数的instance,这样就等于拥有了多个容量大小一样的子ms,从而提供整体的内存使用率。 725. 缓存策略

本文来源:https://www.bwwdw.com/article/0m37.html

Top