PHP的缓存
关于PHP的缓存
0. 为什么要缓存
为什么需要缓存,缓存的目的就是加速页面的载入,因此使用缓存之后,你的请求将会直接去获取html文件,传统的动态页面从数据库中获取数据,然后 用程序 将数据显示出来的这个过程就被绕过去了,而这个过程是最消耗系统资源的过程,静态页面传送过程中,系统I/O所占用的资源相比之下就可以忽略了。当然,更 传统的其实还是静态页面,因为它出现在动态页面之前。有人说用缓存更新不及时,不够快,所以很不方便,这也是很有道理的,但是仅仅是因为这样就放弃使用缓 存,或许不太划算,特别是在你服务器压力比较大的情况下。
1. 什么是缓存
顾名思义,缓存是一块设定了缓冲功能的内存区域,用于提供数据的高速缓冲。无论是针对硬件的CPU的缓存,硬盘的缓存。都是起到减少不比要的I/O访问,提高数据访问速度的目的。
2. 缓存的形式
字面上,缓存就应该是内存的一个部分。但是,也有其他形式的缓存。
CPU上的缓存可以有2到3级RAM,是现在CPU设计中一个重要的组成部分。同样,硬盘上的Flash RAM芯片,承担了缓存的作用。
而针对应用软件系统,例如:Memcached这样的软件,从物理内存中划去了一部分空间,为各种应用程序提供缓存服务。
针对特殊的Web + DB的这种B/S业务系统,还有类似的生成静态页,减少数据库存取I/O操作的缓存方式。
3. PHP缓存的具体方法
针对PHP这种具体的B/S架构的脚本语言,有几种常见的缓存形式。
文件缓存:
文件缓存是PHP缓存的十分常见的一种形式。其主要目的是将页面中不经常变更的数据保存在文件中,当有用户请求访问的时候,直接将数据调出,避免了再次对数据库的请求的I/O操作,减少了数据库的负载压力。
一般情况下,PHP的文件缓存分为两种方法:
第一种,把需要缓存的数据进行处理,形成PHP可以直接执行的文件。在需要缓存数据的时候,通过include方式引入,并使用。
第二种,把需要的数据通过serialize函数序列化后直接保存到文件。在需要使用缓存数据的时候,通过反序列化读入文件内容并复制给需要的变量,然后 使用。当然,这里说到的serialize函数是PHP自带的serialize()函数。针对该函数的改进问题,后续的章节将会提及。
通过测试发现,第二种也就是serialize缓存数据的方式更加高效。
include方式读取缓存的时候,PHP需要执行几个过程
1.读取文件
2.解析所Include的文件
3.执行,给变量赋值
而serialize序列化方式读取缓存的时候:
1.读取数据
2.反序列化数据内容
3.给变量赋值
从以上内容对比的话,可能是由于解析PHP文件内的数组需要的时间超过unserialize反序列化数组的时间。
测试代码:
CacheTest_IncludeFile.php
- < ?php
- $t1 = gettimeofday();
- for ($i = 0; $i < 10000; $i++){
- include("CacheTest_IncludeData.php");
- }
- $t2 = gettimeofday();
- echo ($t2['sec'] - $t1['sec']) * 1000 + ($t2['usec'] - $t1['usec']) / 1000 . "\n";
- < ?php
- $testarray = array (
- 'mtime' => 0,
- 'values' =>
- array (
- 0 =>
- array (
- 'id' => '3',
- 'title' => 'Title NO.3',
- 'dateline' => '1191244506',
- ),
- 1 =>
- array (
- 'id' => '2',
- 'title' => 'Title NO.2',
- 'dateline' => '1191244505',
- ),
- 2 =>
- array (
- 'id' => '1',
- 'title' => 'Title NO.1',
- 'dateline' => '1191244504',
- ),
- ),
- 'multipage' => NULL,
- );
- < ?php
- function read_cache($filename) {
- if(@$fp = fopen($filename, 'r')) {
- @$data = fread($fp,filesize($cachefile));
- fclose($fp);
- }
- return $data;
- }
- $t1 = gettimeofday();
- for ($i = 0; $i < 10000; $i++){
- $x = read_cache("CacheTest_SerializeData.php");
- $x_r = unserialize($x);
- }
- $t2 = gettimeofday();
- echo ($t2['sec'] - $t1['sec']) * 1000 + ($t2['usec'] - $t1['usec']) / 1000 . "\n";
- a:3:{s:5:"mtime";i:0;s:6:"values";a:3:{i:0;a:3:{s:2:"id";s:1:"3";s:5:"title";s:10:"Title NO.3";s:8:"dateline";s:10:"1191244506";}i:1;a:3:{s:2:"id";s:1:"2";s:5:"title";s:10:"Title NO.2";s:8:"dateline";s:10:"1191244505";}i:2;a:3:{s:2:"id";s:1:"1";s:5:"title";s:10:"Title NO.1";s:8:"dateline";s:10:"1191244504";}}s:9:"multipage";N;}
运行结果对比:
- [root@localhost ~/lab/php/cachetest]# php -e CacheTest_IncludeFile.php
- 495.014
- [root@localhost ~/lab/php/cachetest]# php -e CacheTest_SerializeFile.php
- 235.883
总结分析:
第一种,include缓存的方式
优点:增加数据的保密性,和安全性,缓存内容不会被外界发现。
缺点:速度相对较慢。
用途:保存禁止系统外部得知的数据,比如web系统的设置,甚至MySQL信息等的保存
第二种,serialize序列化缓存的方式
优点:速度较快。
缺点:缓存系统文件路径一旦曝光,缓存内容会泄露。
用途:缓存最新文章,相关文章等不担心外部得知的数据的时候,可以使用这种方式。
备注:
当安装了ea、apc等PHP内存缓存之后,第一种通过include读取缓存的方式速度会高于第二种serialize序列化缓存的方式。
本部分参考: http://www.ccvita.com/311.html
Sqlite缓存:
Sqlite是嵌入式SQL数据库引擎SQLite(SQLite Embeddable SQL Database Engine)的一个扩展。SQLite是一个实现嵌入式SQL数据库引擎小型C语言库(C library),实现了独立的,可嵌入的,零配置的SQL数据库引擎。特性包括:事务操作是原子,一致,孤立,并且持久的,即使在系统崩溃和电源故障之 后。零配置——不需要安装和管理。实现了绝大多数SQL92标准。整个数据库存储在一个单一的文件中。数据库文件可以在不同字节序的机器之间自由地共享。 支持 最大可达2T的数据库。字符串和BLOB类型的大小只受限于可用内存。完整配置的少于250KB,忽略一些可选特性的少于150KB。在大多数常见操作上 比流行的客户/服务器数据库引擎更快。简单易于使用的API。内建TCL绑定。另外提供可用于许多其他语言的绑定。具有良好注释的源代码,代码95%有较 好的注释。独立:没有外部依赖。源代码位于公共域,可用于任何用途。用 SQLite连接的程序可以使用SQL数据库,但不需要运行一个单独的关系型数据库管理系统进程(separate RDBMS process)。SQLite不是一个用于连接到大型数据库服务器(big database server)的客户端库(client library),而是非常适合桌面程序和小型网站的数据库服务器。SQLite直接读写(reads and writes directly)在硬盘上的数据库文件。著名的浏览器Firefox的数据库就采用的Sqlite。Firefox所记录的配置,书签,历史记 录,Cookie和Password等信息,均存储在Sqlite中。
Apc缓存:
APC,严格的意义上讲应该成为加速器,全称Alternative PHP Cache,是 PHP 的一个免费公开的优化代码缓存。它用来提供免费,公开并且强健的架构来缓存和优化 PHP 的中间代码。对应的还有eAccelerator 和 Zend Optimizer两个加速器方案。
APC提供两种缓存功能,即缓存Opcode(目标文件),我们称之为apc_compiler_cache。同时它还提供一些接口用于PHP开发人员将用户数据驻留在内存中,我们称之为apc_user_cache。我们这里主要控讨php-apc的配置。
安装PHP APC:
作为测试环境,我们这里使用的是CentOS5.4(2.6.18-194.3.1.el5) + Apache2.2.3 + php5.2.10.我们可以去pecl apc下载APC-3.0.19.tgz
- # tar -xzvf APC-3.0.19.tgz
- #cd APC-3.0.19
- # /usr/bin/phpize
- # ./configure --enable-apc --enable-mmap --enable-apc-spinlocks --disable-apc-pthreadmutex
- #make
- #make install
注意:我们这里支持mmap,同时采用spinlocks自旋锁。Spinlocks是Facebook推荐使用,同时也是APC开发者推荐使用的锁机制。
APC PHP.ini配置选项详解
- [APC]
- ; Alternative PHP Cache 用于缓存和优化PHP中间代码
- apc.cache_by_default = On
- ;SYS
- ; 是否默认对所有文件启用缓冲。
- ; 若设为Off并与以加号开头的apc.filters指令一起用,则文件仅在匹配过滤器时才被缓存。
- apc.enable_cli = Off
- ;SYS
- ; 是否为CLI版本启用APC功能,仅用于测试和调试目的才打开此指令。
- apc.enabled = On
- ; 是否启用APC,如果APC被静态编译进PHP又想禁用它,这是唯一的办法。
- apc.file_update_protection = 2
- ;SYS
- ; 当你在一个运行中的服务器上修改文件时,你应当执行原子操作。
- ; 也就是先写进一个临时文件,然后将该文件重命名(mv)到最终的名字。
- ; 文本编辑器以及 cp, tar 等程序却并不是这样操作的,从而导致有可能缓冲了残缺的文件。
- ; 默认值 2 表示在访问文件时如果发现修改时间距离访问时间小于 2 秒则不做缓冲。
- ; 那个不幸的访问者可能得到残缺的内容,但是这种坏影响却不会通过缓存扩大化。
- ; 如果你能确保所有的更新操作都是原子操作,那么可以用 0 关闭此特性。
- ; 如果你的系统由于大量的IO操作导致更新缓慢,你就需要增大此值。
- apc.filters =
- ;SYS
- ; 一个以逗号分隔的POSIX扩展正则表达式列表。
- ; 如果源文件名与任意一个模式匹配,则该文件不被缓存。
- ; 注意,用来匹配的文件名是传递给include/require的文件名,而不是绝对路径。
- ; 如果正则表达式的第一个字符是"+"则意味着任何匹配表达式的文件会被缓存,
- ; 如果第一个字符是"-"则任何匹配项都不会被缓存。"-"是默认值,可以省略掉。
- apc.ttl = 0
- ;SYS
- ; 缓存条目在缓冲区中允许逗留的秒数。0 表示永不超时。建议值为7200~36000。
- ; 设为 0 意味着缓冲区有可能被旧的缓存条目填满,从而导致无法缓存新条目。
- apc.user_ttl = 0
- ;SYS
- ; 类似于apc.ttl,只是针对每个用户而言,建议值为7200~36000。
- ; 设为 0 意味着缓冲区有可能被旧的缓存条目填满,从而导致无法缓存新条目。
- apc.gc_ttl = 3600
- ;SYS
- ; 缓存条目在垃圾回收表中能够存在的秒数。
- ; 此值提供了一个安全措施,即使一个服务器进程在执行缓存的源文件时崩溃,
- ; 而且该源文件已经被修改,为旧版本分配的内存也不会被回收,直到达到此TTL值为止。
- ; 设为零将禁用此特性。
- apc.include_once_override = Off
- ;SYS
- ; 关于该指令目前尚无说明文档,参见:http://pecl.php.net/bugs/bug.php?id=8754
- ; 请保持为Off,否则可能导致意想不到的结果。
- apc.max_file_size = 1M
- ;SYS
- ; 禁止大于此尺寸的文件被缓存。
- apc.mmap_file_mask =
- ;SYS
- ; 如果使用–enable-mmap(默认启用)为APC编译了MMAP支持,
- ; 这里的值就是传递给mmap模块的mktemp风格的文件掩码(建议值为"/tmp/apc.XXXXXX")。
- ; 该掩码用于决定内存映射区域是否要被file-backed或者shared memory backed。
- ; 对于直接的file-backed内存映射,要设置成"/tmp/apc.XXXXXX"的样子(恰好6个X)。
- ; 要使用POSIX风格的shm_open/mmap就需要设置成"/apc.shm.XXXXXX"的样子。
- ; 你还可以设为"/dev/zero"来为匿名映射的内存使用内核的"/dev/zero"接口。
- ; 不定义此指令则表示强制使用匿名映射。
- apc.num_files_hint = 1000
- ;SYS
- ; Web服务器上可能被包含或被请求的不同源文件的大致数量(建议值为1024~4096)。
- ; 如果你不能确定,则设为 0 ;此设定主要用于拥有数千个源文件的站点。
- apc.optimization = 0
- ; 优化级别(建议值为 0 ) 。
- ; 正整数值表示启用优化器,值越高则使用越激进的优化。
- ; 更高的值可能有非常有限的速度提升,但目前尚在试验中。
- apc.report_autofilter = Off
- ;SYS
- ; 是否记录所有由于early/late binding原因而自动未被缓存的脚本。
- apc.shm_segments = 1
- ;SYS
- ; 为编译器缓冲区分配的共享内存块数量(建议值为1)。
- ; 如果APC耗尽了共享内存,并且已将apc.shm_size指令设为系统允许的最大值,
- ; 你可以尝试增大此值。
- apc.shm_size = 30
- ;SYS
- ; 每个共享内存块的大小(以MB为单位,建议值为128~256)。
- ; 有些系统(包括大多数BSD变种)默认的共享内存块大小非常少。
- apc.slam_defense = 0
- ;SYS(反对使用该指令,建议该用apc.write_lock指令)
- ; 在非常繁忙的服务器上,无论是启动服务还是修改文件,
- ; 都可能由于多个进程企图同时缓存一个文件而导致竞争条件。
- ; 这个指令用于设置进程在处理未被缓存的文件时跳过缓存步骤的百分率。
- ; 比如设为75表示在遇到未被缓存的文件时有75%的概率不进行缓存,从而减少碰撞几率。
- ; 鼓励设为 0 来禁用这个特性。
- apc.stat = On
- ;SYS
- ; 是否启用脚本更新检查。
- ; 改变这个指令值要非常小心。
- ; 默认值 On 表示APC在每次请求脚本时都检查脚本是否被更新,
- ; 如果被更新则自动重新编译和缓存编译后的内容。但这样做对性能有不利影响。
- ; 如果设为 Off 则表示不进行检查,从而使性能得到大幅提高。
- ; 但是为了使更新的内容生效,你必须重启Web服务器。
- ; 这个指令对于include/require的文件同样有效。但是需要注意的是,
- ; 如果你使用的是相对路径,APC就必须在每一次include/require时都进行检查以定位文件。
- ; 而使用绝对路径则可以跳过检查,所以鼓励你使用绝对路径进行include/require操作。
- apc.user_entries_hint = 100
- ;SYS
- ; 类似于num_files_hint指令,只是针对每个不同用户而言。
- ; 如果你不能确定,则设为 0 。
- apc.write_lock = On
- ;SYS
- ; 是否启用写入锁。
- ; 在非常繁忙的服务器上,无论是启动服务还是修改文件,
- ; 都可能由于多个进程企图同时缓存一个文件而导致竞争条件。
- ; 启用该指令可以避免竞争条件的出现。
- apc.rfc1867 = Off
- ;SYS
- ; 打开该指令后,对于每个恰好在file字段之前含有APC_UPLOAD_PROGRESS字段的上传文件,
- ; APC都将自动创建一个upload_的用户缓存条目(就是APC_UPLOAD_PROGRESS字段值)。
APC的相关函数
apc_cache_info – Retrieves cached information (and meta-data) from APC’s data store
apc_clear_cache – Clears the APC cache
apc_define_constants – Defines a set of constants for later retrieval and mass-definition
apc_delete – Removes a stored variable from the cache
apc_fetch – Fetch a stored variable from the cache
apc_load_constants – Loads a set of constants from the cache
apc_sma_info – Retrieves APC’s Shared Memory Allocation information
apc_store – Cache a variable in the data store
apc的用法比较简单,只有几个函数,列举如下。
apc_cache_info () 返回缓存信息
apc_clear_cache() 清除apc缓存内容。 默认(无参数)时,只清除系统缓存,要清除用户缓存,需用‚user‘参数。
apc_define_constants ( string key, array constants [, bool case_sensitive] ) 将数组constants以常量加入缓存。
apc_load_constants (string Key) 取出常量缓存。
apc_store ( string key, mixed var [, int ttl] ) 在缓存中保存数据。
apc_fetch ( string key ) 获得apc_store保存的缓存内容
apc_delete ( string key ) 删除apc_store保存的内容。
APC的管理:
到pecl.php.net下载apc源码包有个apc.php,copy到你的web server可以访问到的地方,浏览即可访问。
管理界面功能有:
1. Refresh Data
2. View Host Stats
3. System Cache Entries
4. User Cache Entries
5. Version Check
Xcache缓存:
和APC(Alternative PHP Cache),eAccelerator(eacc)类似,XCache 是一个开源的 opcode 缓存器/优化器, 这意味着他能够提高您服务器上的 PHP 性能. 他通过把编译 PHP 后的数据缓冲到共享内存从而避免重复的编译过程, 能够直接使用缓冲区已编译的代码从而提高速度. 通常能够提高您的页面生成速率2到5倍, 降低服务器负载. 需要特别说明的是,这个加速器是国人开发的方案。
与APC类似,由于是编译级别的缓存和优化。因此,除配置之外,没有太多可以说道的东西。这里就直接应用IBM开发社区中的一篇文章,完成对XCache的配置和安装说明。
Memcached缓存:
Memcached是什么?
memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。现在已成为 mixi、 hatena、 Facebook、 Vox、LiveJournal等众多服务中 提高Web应用扩展性的重要因素。
许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
PHP与Memcached的关系
php_memcached是php为Memcached提供的PECL扩展。由于Memcached简单的协议规范,因此,当Memcached推出后,就有了PHP的接口规范和相关扩展。
在阐述这个问题之前,我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用,虽然memcached 使用了同样的“Key=>Value”方式组织数据,但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是分布式的,也就是说 它不是本地的。它基于网络连接(当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程(Daemon方式)。
Memcached使用libevent库实现网络连接服务,理论上可以处理无限多的连接,但是它和Apache不同,它更多的时候是面向稳定的持续连接 的,所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200,这和Linux线程能力有关系,这个数值是可以调整的。 关于libevent可以参考相关文档。 Memcached内存使用方式也和APC不同。APC是基于共享内存和MMAP的,memcached有自己的内存分配算法和管理方式,它和共享内存没 有 关系,也没有共享内存的限制,通常情况下,每个memcached进程可以管理2GB的内存空间,如果需要更多的空间,可以增加进程数。
Memcached适合什么场合
在很多时候,memcached都被滥用了,这当然少不了对它的抱怨。经常在论坛上看见有人发贴,类似于“如何提高效率”,回复是“用memcached”,至于怎么用,用在哪里,用来干什么一句没有。memcached不是万能的,它也不是适用在所有场合。
Memcached是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应 用,memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX本地连接也一样。 在我之前的测试数据中显示,memcached本地读写速度要比直接PHP内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。可见,如果只是 本地级缓存,使用memcached是非常不划算的。
PHP调用Memcached的研发环境的搭建
该章节标题中,我增加了研发环境的搭建,主要原因是,这种搭建方案,在实际生产环境中是并不可取的。不能体现出Memcached的分布式,高扩展性等特 点。如果是单机版本想提升Web系统的效能的缓存方案,建议使用更适合的APC等加速器方法。效果要好于采用Memcached的方案。
CentOS 5.4 + Apache 2.2.3( Yum安装 ) + PHP 5.2.10( Yum 安装 ) + GCC 4.1.2 ( Yum 安装 )
注:这里要说明安装方式的原因是,很多朋友都采用的是源代码编译的方式安装的软件。因此,在程序路径和配置上可能会与Yum下载下来的rpm包安装的路径会有些出入。具体配置过程中一些路径问题,参考本文的朋友,请根据自己具体的情况进行设置修改。
1. 安装libevent和开发包
由于Memcached是基于libevent库实现的网络通讯部分的功能,因此,首先就要安装libevent库的相关包。
官网:http://www.monkey.org/~provos/libevent/
当前版本:1.4.10-stable 2.0.1-alpha
安装方法:
方法一: YUM安装( 个人推荐 )
CentOS 5.* 以后的操作系统最用户使用舒服的,莫过于YUM了。回想以前在RedHat9和后来的AS3,AS4做系统维护的时候,升级一下软件包,或在生产环境中编译程序缺少GCC,在源码光盘中,痛苦找包的经历。现在的YUM真是让人很幸福啊。
- #yum install libevent libevent-devel
说明:安装devel包的原因是,考虑到后期可能的一些应用会使用到libevent相关库进行编译活动,因此,顺带就将该软件包安装上。( 事实上,下一步Memcached的安装就将采用源代码编译,需要libevent-devel包的支持 )
方法二:源代码编译安装
- #wget http://www.monkey.org/~provos/libevent-1.4.14b-stable.tar.gz &
- #tar xzf libevent-1.4.14b-stable.tar.gz
- #cd libevent-1.4.14b-stable
对比rpm包安装方式的安装路径
- #rpm -ql libevent
- #/usr/lib/libevent-1.4.so.2
- #/usr/lib/libevent-1.4.so.2.1.3
- #/usr/lib/libevent_core-1.4.so.2
- #/usr/lib/libevent_core-1.4.so.2.1.3
- #/usr/lib/libevent_extra-1.4.so.2
- #/usr/lib/libevent_extra-1.4.so.2.1.3
- #/usr/share/doc/libevent-1.4.13
- #/usr/share/doc/libevent-1.4.13/README
便于维护,我建议采用源代码编译的程序同样使用相同的路径进行安装配置。
- # ./configure - -prefix=/usr/lib
- #make
- #make install
检测libevent安装情况:
- [root@localhost ~/libevent]# ls -al /usr/lib | grep libevent
- lrwxrwxrwx 1 root root 21 05-23 21:38 libevent-1.4.so.2 -> libevent-1.4.so.2.1.3
- -rwxr-xr-x 1 root root 104804 03-31 21:19 libevent-1.4.so.2.1.3
- -rw-r--r-- 1 root root 130350 03-31 21:18 libevent.a
- lrwxrwxrwx 1 root root 26 05-23 21:38 libevent_core-1.4.so.2 -> libevent_core-1.4.so.2.1.3
- -rwxr-xr-x 1 root root 34820 03-31 21:19 libevent_core-1.4.so.2.1.3
- -rw-r--r-- 1 root root 44868 03-31 21:18 libevent_core.a
- lrwxrwxrwx 1 root root 26 08-05 11:51 libevent_core.so -> libevent_core-1.4.so.2.1.3
- lrwxrwxrwx 1 root root 27 05-23 21:38 libevent_extra-1.4.so.2 -> libevent_extra-1.4.so.2.1.3
- -rwxr-xr-x 1 root root 84180 03-31 21:19 libevent_extra-1.4.so.2.1.3
- -rw-r--r-- 1 root root 102266 03-31 21:18 libevent_extra.a
- lrwxrwxrwx 1 root root 27 08-05 11:51 libevent_extra.so -> libevent_extra-1.4.so.2.1.3
- lrwxrwxrwx 1 root root 21 08-05 11:51 libevent.so -> libevent-1.4.so.2.1.3
2. 安装memcached
由于搜索了YUM软件库中,没有memcached包存在。
- [root@localhost ~/libevent]# yum search memcache
- Loaded plugins: fastestmirror
- Loading mirror speeds from cached hostfile
- * addons: centos.candishosting.com.cn
- * base: centos.mirror.cdnetworks.com
- * extras: centos.candishosting.com.cn
- * updates: centos.tt.co.kr
- Reducing CentOS-5 Testing to included packages only
- Finished
- ========================== Matched: memcache ==================================
- php-pecl-memcache.i386 : Extension to work with the Memcached caching daemon
- [root@localhost ~/libevent]#
因此,memcached我们采用源代码编译安装的方式进行。
官方网站:http://memcached.org/
当前版本:v1.4.5
- #wget http://memcached.googlecode.com/files/memcached-1.4.5.tar.gz &
- #tar xzf memcached-1.4.5.tar.gz
- #cd memcached-1.4.5
- #./configure
- #make
- #make install
检测memcached安装情况:
- #cd /usr/local/bin/
- # ll -al mem*
- -rwxr-xr-x 1 root root 202065 08-05 15:32 memcached
启动memcached服务
- #/usr/local/bin/memcached -d -m 10 -u root -l 127.0.0.1 -p 11211 -c 256 -P /tmp/memcached.pid
说明:
-d选项是启动一个守护进程,
-m是分配给Memcache使用的内存数量,单位是MB,这里是10MB,
-u是运行Memcache的用户,我这里是root,
-l是监听的服务器IP地址,如果有多个地址的话,由于是本地开发( 非实际运行环境 )这里指定了服务器的IP地址127.0.0.1,
-p是设置Memcache监听的端口,这里设置了11211,最好是1024以上的端口,
-c选项是最大运行的并发连接数,默认是1024,这里设置了256,按照服务器的负载量来设定,
-P是设置保存Memcache的pid文件,这里是保存在 /tmp/memcached.pid,
结束memcached进程
- #kill `cat /tmp/memcached.pid`
3. 安装memcached的PHP扩展( 提示,请看完本小节后再进行安装配置 )
虽然安装好了memcached,但是,php还没有办法直接使用memcached。当然,如果你想重复造轮子的话,可以自己写个类,完全实现memcached的通讯协议。
目前,PHP中使用最多的是PHP的PECL的Memcached的扩展。php-pecl-memcache
上面使用Yum搜索memcached的时候,我们已经看到Yum源中提供了软件包的下载,因此,很自然的我们就使用YUM安装memcached的PHP扩展。
- #yum install php-pecl-memcache
安装完成后查看PHP加载情况:
- #php –m
- bz2
- calendar
- ctype
- curl
- date
- dbase
- dom
- exif
- …
- PHP Warning: PHP Startup: memcache: Unable to initialize module
- Module compiled with module API=20050922, debug=0, thread-safety=0
- PHP compiled with module API=20060613, debug=0, thread-safety=0
- These options need to match
- in Unknown on line 0
发现出现了Module版本不匹配的情况。由于我们之前使用的最新的源代码包编译的memcached,因此,怀疑是Yum源中提供的php-pecl-memcache扩展版本较旧导致的。
因此,必须采用编译php动态扩展的方式来进行安装。
官方网址:http://pecl.php.net/package/memcache
当前版本:2.2.5-stable 3.0.4-bata
首先,先卸载YUM安装的软件包
- #yum erase php-pecl-memcache
下载源码并使用phpize进行安装
- #wget http://pecl.php.net/get/memcache-2.2.5.tgz &
- #tar xzf memcache-2.2.5.tgz
- #cd memcache-2.2.5
发现没有安装phpize
- #yum install php-devel
- # rpm -ql php-devel | grep phpize
- /usr/bin/phpize
- /usr/lib/php/build/phpize.m4
- /usr/share/man/man1/phpize.1.gz
使用phpize编译( rpm包安装,phpize默认配置到了系统环境变量中,因此后续操作没有加phpize的路径 )
当前目录是memcache-2.2.5的解压后的源代码目录
- #phpize
- #./configure --enable-memcache --with-php-config=/usr/bin/php-config --with-zlib-dir
- #make
- #make install
由于之前已经安装了其他的PHP模块。例如:php-mbstring php-mcrypt。所以,在/etc/php.ini中已经设置了PHP动态模块扩展的路径。
- ; Directory in which the loadable extensions (modules) reside.
- extension_dir = "/usr/lib/php/modules"
并且,默认安装好的memcache.so在make install的时候默认拷贝到了该目录下,那么,现在只需要在php.ini中增加module的扩展配置即可。
这里,参考了其他的rpm包安装的module的配置方式,在/etc/php.d/下增加自己的单独的module配置文件。PHP会自动加载该文件夹下的每个配置文件。
- [root@localhost /etc/php.d]# ll
- 总计 64
- -rw-r--r-- 1 root root 51 2009-11-14 dbase.ini
- -rw-r--r-- 1 root root 47 2009-11-14 dom.ini
- -rw-r--r-- 1 root root 49 2009-11-14 json.ini
- -rw-r--r-- 1 root root 49 2009-11-14 ldap.ini
- -rw-r--r-- 1 root root 57 2009-11-14 mbstring.ini
- -rw-r--r-- 1 root root 53 2009-09-10 mcrypt.ini
- -rw-r--r-- 1 root root 53 2009-11-14 mysqli.ini
- -rw-r--r-- 1 root root 51 2009-11-14 mysql.ini
- -rw-r--r-- 1 root root 47 2009-11-14 pdo.ini
- -rw-r--r-- 1 root root 59 2009-11-14 pdo_mysql.ini
- -rw-r--r-- 1 root root 61 2009-11-14 pdo_sqlite.ini
- -rw-r--r-- 1 root root 59 2009-11-14 xmlreader.ini
- -rw-r--r-- 1 root root 59 2009-11-14 xmlwriter.ini
- -rw-r--r-- 1 root root 47 2009-11-14 xsl.ini
- -rw-r--r-- 1 root root 47 2009-11-14 zip.ini
这里我们增加自己的一个memcache.ini
- [root@localhost /etc/php.d]# cat memcache.ini
- ; Enable memcache extension module
- extension=memcache.so
- [root@localhost /etc/php.d]#
检测php-pecl-memcache的安装情况
- #php -m
- ……
- mcrypt
- memcache
- mysql
- mysqli
- ……
这样就表示PHP正确识别了该对应版本的PECL的memcache的扩展模块。下面只要重启Apache服务,使得所有配置生效。我们可以建立一个测试文件在命令行或者一个测试站点上进行Memcache的调用测试了。
测试前提:
1. Memcached服务已经启动,监听的IP和端口是 127.0.0.1:11211
2. PHP加载php-pecl-memcache模块正常。
测试代码:
memcache_test.php
- < ?php
- $mem = new Memcache;
- $mem->connect("127.0.0.1", 11211);
- $mem->set('key', “This is a test!\n”, 0, 60);
- $val = $mem->get('key');
- echo $val;
- ?>
如果有虚拟主机环境,那么,可以在虚拟主机环境中的对应路径访问该php页面。
这里我们使用命令行测试。
- [root@localhost ~/lab/php]# php -e memcache_test.php
- PHP Notice: Memcache::connect(): Server 127.0.0.1 (tcp 11211) failed with: Connection refused (111) in /root/lab/php/mem_cache_test.php on line 3
- PHP Warning: Memcache::connect(): Can't connect to 127.0.0.1:11211, Connection refused (111) in /root/lab/php/mem_cache_test.php on line 3
- PHP Warning: Memcache::set(): No servers added to memcache connection in /root/lab/php/mem_cache_test.php on line 4
- PHP Warning: Memcache::get(): No servers added to memcache connection in /root/lab/php/mem_cache_test.php on line 5
- [root@localhost ~/lab/php]#
看到上述代码,就说明对应的Memcached服务没有启动。需要启动对应的服务。
- #/usr/local/bin/memcached -d -m 10 -u root -l 127.0.0.1 -p 11211 -c 256 -P /tmp/memcached.pid
再次访问php页面。出现如下结果,就说明Memcache的整套缓存解决方案初步配置完毕了。
- [root@localhost ~/lab/php]# php -e memcache_test.php
- This is a test!
- [root@localhost ~/lab/php]#
当然,在新版的PECL的Memcache扩展中,增加了类似APC的监控页面,可以很清晰的看到系统中当前若干个使用的Memcached的情况。
只需当前的主机上有对应的虚拟主机站点,并更改相应的配置即可。
假设,当前的主机上的虚拟站点为lab.lanbolee.com,对应的目录为:/www/vhost/lab.lanbolee.com/zflab/public/
1. 将PECL的memcache源代码路径下的memcache.php拷贝到目标目录下。(只要通过站点能够访问到即可)
2. 更改memcache.php的配置,将原有的如下数组,根据实际情况修改
- $MEMCACHE_SERVERS[] = 'mymemcache-server1:11211'; // add more as an array
修改为:
- $MEMCACHE_SERVERS[] = '127.0.0.1:11211'; // add more as an array
下面,访问memcache.php页面,就能看到当前Memcached的使用详情了。如下图:
下面是一个稍微复杂一点,cache一个对象的例子:
memcache_test_adv.php
- < ?php
- $memcache = new Memcache;
- $memcache->connect('localhost', 11211) or die ("Could not connect");
- $version = $memcache->getVersion();
- echo "Server's version: ".$version."\n";
- $tmp_object = new stdClass;
- $tmp_object->str_attr = 'test';
- $tmp_object->int_attr = 123;
- $memcache->set('key', $tmp_object, false, 10) or die ("Failed to save data at the server");
- echo "Store data in the cache (data will expire in 10 seconds)\n";
- $get_result = $memcache->get('key');
- echo "Data from the cache:\n";
- var_dump($get_result);
- ?>
测试结果:
- [root@localhost ~/lab/php]# php -e memcache_test.php
- Server's version: 1.4.5
- Store data in the cache (data will expire in 10 seconds)
- Data from the cache:
- object(stdClass)#3 (2) {
- ["str_attr"]=>
- string(4) "test"
- ["int_attr"]=>
- int(123)
- }
查看Memcached中实际的存储情况。可以直接telnet对应的端口,使用Memcache的标准协议中的get方法访问。
- [root@localhost ~/lab/php] # telnet 127.0.0.1 11211
- Trying 127.0.0.1...
- Connected to localhost.localdomain (127.0.0.1).
- Escape character is '^]'.
- get key
- VALUE key 1 66
- O:8:"stdClass":2:{s:8:"str_attr";s:4:"test";s:8:"int_attr";i:123;}
- END
- get key
- END
这里我们可以看到,PHP是采用将对象序列化后,再把对象存储在Memcache划分的内存块中的。同样可以看到,当过了缓存器,我再次尝试取出 key中对应的缓存对象时,Memcache给我返回的则是空值。(实际上,对应的数据并没有被从内存中清除掉,只是被Memcache将对应的内存块设 置为invisible,这样做无疑是一种高效的处理方式)
关于PHP序列化和Memcache的一些深入探讨
在查阅PHP调用Memcache的一些资料中,发现了关于调用优化的问题。关于PHP与Memcached之间的调用方式,简单总结了一下,可以从两方面入手:
第一,从代码设计的角度。优化的方法就如同仿照PHP针对MySQL的多次Query请求的优化。假设这样一个应用:要从缓存中取出三个不同的Value。那么,我们会很自然的写出如下的代码。
- $zhang = $memcache->get('key1');
- $li = $memcache->get('key2');
- $wang = $memcache->get('key3');
这种写法实际运行效果是
* get(key1) – 客户端发出请求 – 服务器端查询 – 客户端获取
* get(key2) – 客户端 – 服务器端 – 客户端
* get(key3) – 客户端 – 服务器端 – 客户端
* …
无疑,这就跟每次要从数据库取一下数据,都重新建立一个数据库连接一样,是个非常低效的方法。那么,类似数据库的访问优化,PHP针对这种取缓存的情况,提供了get( array() ) ; 这样的方法。
- $all = $memcache->get(array('key1', 'key2', 'key3'));
如此一来,会有三次客户端和服务器端交互的过程。但是如果用批量查询的方法,就只有一次交互的过程。这样性能会有一些提升。对于其它程序语言来说,也封装了类似get_multi这样的方法。
第二,从程序处理环节上下手。这里的核心是更换序列化函数和方法。从Facebook中的Memcached应用可以看出一些端倪。
php有两个memcache扩展,默认都是使用php自带的序列化函数serialize来存储数组或对象。但是serialize最为人诟病的 就是速度慢,序列化之后占用空间大。由于facebook已经在memcached里保存了200T字节的数据,因此序列化函数即便作出的百分之一的优化 对它来说都是个不小的收益。他们发粪涂墙在thrift的binary协议基础上搞出了一个fb_serialize,据称这个序列化方法能快上3倍,快 倒算了,还能节省30%空间, 200T字节的数据能节省出30%,简直就是传说中的银弹啊,这让php官方的开发人员们情何以堪?
facebook目前已经开源了thrift,其中自带了一个thrift协议的php扩展,但是这些代码里没有找到传说中的fb_serialize,倒是从最近他们放出来hiphop-php里找到了这部分代码,哪位大侠去扒拉扒拉弄出来做成php扩展造福广大群众?
作为备选方案,推荐igbinary,这也是一个binary的序列化方法。在上次的测试结果中,它甚至能节约50%的存储空间,速度也是稳超php原生的序列化方法,搞不好facebook换了这个序列化方法能省下更多的内存来?
节约每个item的存储空间有什么好处?我个人认为一个是省钱,另外一个就是能够带来速度上的提升。我们平常碰到稍大一点的item都得用gzip压的妥妥贴贴的才送到memcached里,网络传输的开销小了,这是实实在在的性能提升。何乐而不为?
Igbinary的相关信息,请参考它的官网:http://opensource.dynamoid.com/
Igbinary的安装就如同其他的源代码级的php动态扩展库一样。phpize → configuration → make → make install。这里我就不再详细描述了。具体可以参考源代码包中的README文档的Installing部分。
关于Igbinary的使用更为简单,当模块安装并正确被PHP加载识别后,将代码中原有的默认的PHP提供的serialize() 和 unserialize() 函数替换成igbinary_serialize() 和 igbinary_unseralize() 即可。
php-memcached扩展的进阶
libmemcached扩展
在看到改善序列化的问题上时,又发现了一篇关于Debug连接Memcached慢的文章。
文章原文:
http://www.ooso.net/archives/479
http://www.ooso.net/archives/524
连接慢的现象出现在两种情况下:
第一是作者采用了最新的3.0.2beta版本的extension。发现在完成4次addServer操作,需要耗时300ms以上,但一旦连接后,获取items就非常迅速。后来,作者换回最新的稳定版本2.2.6,这个版本中解决了这个问题。
第二是,在2.2.5版本的extension中addServer方法的问题。
函数原型:
- bool Memcache::addServer ( string $host [, int $port = 11211 [, bool $persistent [, int $weight [, int $timeout [, int $retry_interval [, bool $status [, callback $failure_callback [, int $timeoutms ]]]]]]]] )
比对结果表明,$weight参数对memcache的连接时间有显著的影响,$weight的默认值为1,一旦设置为别的数值,连接时间便会由毫秒级变成50ms左右,立竿见影。
综上两种情况,并且,还有默认序列化的问题。在PECL中就有了一个新的项目名为libmemcached的memcache扩展。
官方网址:http://pecl.php.net/package/memcached
软件版本:1.0.2 稳定版
需要说明的是libmemcached也使用里基于igbinary的序列化库的,因此,在安装配置libmemcached之前,要首先安装好igbinary扩展模块。
使用Memcache模拟namespace功能
实现namespace功能,就可以间接的实现清楚一部分键值对象的功能。默认的php-pecl-memcache扩展只能有限的使用 add、delete、replace、flush 等几个简单的方法。
memcache 官方是不建议我们手动去删除缓冲内容的, 因为官方的 api 中有关删除的方法只有 delete 和flush,前者是在知道 key 的情况下删除一个对象的值,后面是清除服务器上所有对象。
不过很多情况下,我们需要从服务器上删除一部分对象,又不希望把整个服务器上的对象都清空,官方的 FAQ 上提供了一个“伪命名空间(Simulating Namespaces)” 的作法。通过下面的方法,就可以实现一次只删除一部分对象数据的功能了。
- //生成一个用来保存 namespace 的 key
- $ns_key = $memcache->get("foo_namespace_key");
- //如果 key 不存在,则创建
- if($ns_key===false) $memcache->set("foo_namespace_key", rand(1, 10000));
- //根据 namespace_key 生成真正的 key
- $my_key = "foo_".$ns_key."_12345";
- //需要删除整个 namespace 里的对象的时候,这样处理即可:
- $memcache->increment("foo_namespace_key");
以上内容,基本上描述了PHP缓存技术中涉及到的基础的知识点。主要对Memcache扩展做了详尽的分析和解释。在整个学习中,主要参考了如下作者的文档,在这里表示感谢。
参考文章:
http://www.ooso.net/archives/306
http://www.ooso.net/archives/428
http://www.ooso.net/archives/475
http://www.ooso.net/archives/479
http://www.ooso.net/archives/524
http://www.ooso.net/archives/558
http://opensource.dynamoid.com/
http://blog.developers.api.sina.com.cn/?p=124
http://www.phpx.com/happy/viewthread.php?tid=138416
http://tech.idv2.com/2008/07/10/memcached-001/#content_2_0
http://framework.zend.com/manual/1.10/zh/zend.cache.backends.html
http://www.okajax.com/a/200904/php_cache.html
http://swingchen.javaeye.com/blog/152800
http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
浙公网安备 33010602011771号