本系列文章的前两篇文章提供了为 PHP 应用程序提速的技术。第 1 部分 介绍了 XCache,它是把 PHP 操作码缓存到内存中的 PHP 扩展。XCache 将避免代价高昂且(严格来说)不必要的花费来重新编译相同的 PHP 代码去交付一个页面。XCache 是免费的开源软件,安装费时不多却可提供巨大回报。第 2 部分 介绍了 XDebug,它是配置 PHP 代码的 PHP 扩展。XDebug 是类似于软件 x 光照片的内容:它将深入应用程序,揭露内部工作原理,并且揭示代码如何度过其周期。拥有了 XDebug 度量之后 —— 而不是之前 —— 您可以优化代码来调整算法、减少瓶颈并减轻过多的内存使用。

注:XCache 适于生产使用。在开发过程中使用 XDebug 最佳,因为其计算负载在活动计算机中是十分浪费的。

这一次,让我们探究第三种尤为有效的性能增强因素。名为 memcached 的 Memcache 守护程序是一种高性能的分布式对象缓存。memcached 的安装位置介于应用程序与数据存储之间,它将把对象保存在 RAM 中。

Memcache PHP 扩展将为访问缓存提供一个简单的应用程序接口 (API)。要使用缓存,需要调用 API 来确定先前是否已缓存了对象。如果已经缓存,只需检索该对象并继续进行处理。否则,转至数据库、获取必需的数据、将其映射到对象中并把它添加到缓存中。在那里,memcached 将最小化或消除针对您先前处理过的信息的数据库查询。

如果 XCache 和 XDebug 是涡轮增压器,那么 memcached 就是喷气发动机。请准备好点燃加力燃烧室。

更高的速度需求

通常,PHP 应用程序中最耗费时间的任务是数据检索。实际上,用于从存储库(文件或数据库服务器)中获取信息的时间远高于编译并且甚至是执行 PHP 程序所需耗费的时间。连接至数据库服务器所需的时间是一次延迟,等待查询完成添加附加停顿,并且结果的传输甚至会导致更多延迟。此外,如果代码使用对象,则有指向对象的导致负担过重的映射平面行。

MySQL 可以使用其查询缓存为查询阶段提速。您还可以复制数据库(一个主数据库,多个副本),从而在多个 CPU 之间分担查询处理的负担。但是,一旦底层表改变,MySQL 查询缓存的内容就会过时。而且,仅当一个查询与先前查询相同时,查询缓存才会被命中。副本也有限制。例如,无法分布数据库写操作。

从根本上说,虽然查询缓存和数据库副本都是有目的的并且在整体工作负载管理策略中占有地位(查询缓存将消耗一些内存,但是在其他方面却很节省;副本将把灾难停机的风险降至最低),但是连接和传输时间不变。

Memcache PHP 扩展将把对象缓存到 RAM 中。每次缓存命中将替换到数据库服务器的一次往返,使应用程序运行得更快。您很可能会发现 memcached 也(间接地)提高了数据库服务器的性能;由于 memcached 将用作替代持久性存储,因此到达数据库服务器的请求更少,使数据库服务器可以更高效地响应已收到的查询。

您可以在一台或多台服务器上运行 memcached,并且将在所有节点之间复制缓存的内容。如果服务器出现故障,客户机 API 软件将把缓存读写操作重新路由到正常运行的备用服务器上。

不同于 XCache,您必须修改代码才能集成 memcached。不过,如果您已经小心地隔离了一些对象方法内的数据库访问代码,则修改将很可能十分轻微且集中。

Danga Interactive 编写的 Memcache 守护程序是由 Berkeley Software Distribution (BSD) License 的自由条款许可的免费开源软件。守护程序应当可以轻松地构建在 UNIX® 和 Linux® 系统上,也可以构建在 Mac OS X 和 Microsoft® Windows® 上。很多 Linux 发行版都提供了 memcached 包;请查看包存储库。如果使用 Mac OS X 或 Windows 并且更喜欢便捷的预构建二进制,则可以通过简单的 Google 搜索在 Web 上找到此类软件。

回页首

(重新)构建 PHP

让我们在 Debian Linux 上构建、安装和部署 memcached。要加速执行过程并允许您独立于现有 Web 服务器基础设施来测试 memcached,请使用 XAMPP Apache 发行版作为构建的基础。XAMPP 十分易于安装并且包含 Apache V2、MySQL、PHP V4 和 V5、Perl、许多库和许多 Web 应用程序(例如 phpMyAdmin)。如果从未从头开始构建 Linux、Apache、MySQL 和 PHP (LAMP) 堆栈,或者如果需要避免与此类努力相关联的争辩,则以 XAMPP 开始是十分理想的。

注:如果您以前通过源代码构建了 PHP 并保留了文件,则只需把 --enables-memcache 选项添加到配置开关列表中,并且跳过构建 memcached 和 PHP Memcache 扩展以前所示的步骤。

要构建和部署 memcached,您需要 XAMPP 发行版,包括 XAMPP 开发文件、XAMPP 附带的 PHP 版本的源代码以及 memcached 的源代码和 Memcache 的 PHP 扩展。您可以从 XAMPP 下载 XAMPP 二进制和 XAMPP 开发文件(构建附加组件的必备文件)。您还可以使用 wget 快速获取软件:

$ wget 'http://www.apachefriends.org/download.php?xampp-linux-1.6.tar.gz'      
$ wget 'http://www.apachefriends.org/download.php?xampp-linux-devel-1.6.tar.gz'

 

前一个 tarball(tarball 是压缩的 .tar 文件并且通常以后缀 .tar.gz 为结尾)包含二进制;后一个 tarball 包含针对 XAMPP 系统构建代码所需的题头文件。

虽然可以把 XAMPP 锚定在文件系统中的任意位置,但是请把压缩包安装到 /opt 中。同时把开发文件安装到 /opt 中。使用 /opt 将使剩下的构建过程变得更轻松。对 tar 使用 -C 选项把文件直接解压缩到 /opt 中,如下所示:
清单 1. 把文件直接解压缩到 /opt 中

                
$ sudo mkdir /opt
$ tar xzf xampp-linux-1.6.tar.gz -C /opt
$ tar xzf  xampp-linux-devel-1.6.tar.gz -C /opt
$ ls -CF /opt/lampp
RELEASENOTES	error/		info/		logs/		phpsqliteadmin/
backup/		etc/		lampp*		man/		sbin/
bin/		htdocs/		lib/		manual/		share/
build/		icons/		libexec/	modules/	tmp/
cgi-bin/	include/	licenses/	phpmyadmin/	var/

 

接下来,下载并解压缩 XAMPP 附带的 PHP 版本的源代码(XAMPP V1.6 绑定了 PHP V4.4.6);从 PHP.net 下载 PHP V4.4.6 的代码。wget 将再次使您可以轻而易举地完成任务:

$ wget http://us2.php.net/get/php-4.4.6.tar.bz2/from/www.php.net/mirror
$ tar xjf php-4.4.6.tar.bz2
$ cd php-4.4.6

 

接下来,修改 XAMPP 的 PHP 构建脚本重新构建 PHP 以启用 Memcache。您可以在 /opt/lampp/share/lampp/configures.tar.gz 中找到原始构建脚本(和其他构建脚本)。使用以下代码解压缩 PHP V4 构建脚本:

$ tar xzfv /opt/lampp/share/lampp/configures.tar.gz \
  php/configure-php4-oswald

 

打开 configure-php4-oswald 并添加 --enable-memcache(如果系统没有那些数据库,您还可能发现有必要删除选项,例如特定于 Oracle 和 PostgreSQL 的那些选项)。清单 2 展示了在测试系统上用于重新构建 PHP 的修改后的脚本。(PHP 构建过程依赖于许多实用程序和开发库,例如 Flex、Bison、libxml 和 PCRE。您可能需要安装附加软件包才能准备好进行这些构建,这取决于 Linux 发行版和 PHP 配置的内容)。
清单 2. 用于重新构建 PHP 的修改后的 XAMPP 脚本

                
(
    cd /opt/lampp/bin
    rm phpize phpextdist php-config php
    rm -rf /opt/lampp/include/php
)

make distclean

export PATH="/opt/lampp/bin:$PATH" 
export CFLAGS="-O6 -I/opt/lampp/include/libpng \
    -I/opt/lampp/include/ncurses \
    -I/opt/lampp/include -L/opt/lampp/lib" 

./configure \
    --prefix=/opt/lampp \
    --with-apxs2=/opt/lampp/bin/apxs \
    --with-config-file-path=/opt/lampp/etc \
    --with-mysql=/opt/lampp \
    --enable-inline-optimation \
    --disable-debug \
    --enable-memcache \
    --enable-bcmath \
    --enable-calendar \
    --enable-ctype \
    --enable-dbase \
    --enable-discard-path \
    --enable-exif \
    --enable-filepro \
    --enable-force-cgi-redirect \
    --enable-ftp \
    --enable-gd-imgstrttf \
    --enable-gd-native-ttf \
    --with-ttf \
    --enable-magic-quotes \
    --enable-memory-limit \
    --enable-shmop \
    --enable-sigchild \
    --enable-sysvsem \
    --enable-sysvshm \
    --enable-track-vars \
    --enable-trans-sid \
    --enable-wddx \
    --enable-yp \
    --with-ftp \
    --with-gdbm=/opt/lampp \
    --with-jpeg-dir=/opt/lampp \
    --with-png-dir=/opt/lampp \
    --with-tiff-dir=/opt/lampp \
    --with-freetype-dir=/opt/lampp \
    --without-xpm \
    --with-zlib=yes \
    --with-zlib-dir=/opt/lampp \
    --with-openssl=/opt/lampp \
    --with-expat-dir=/opt/lampp \
    --enable-xslt \
    --with-xslt-sablot=/opt/lampp \
    --with-dom=/opt/lampp \
    --with-ldap=/opt/lampp \
    --with-ncurses=/opt/lampp \
    --with-gd \
    --with-imap-dir=/opt/lampp \
    --with-imap-ssl \
    --with-imap=/opt/lampp \
    --with-gettext=/opt/lampp \
    --with-mssql=/opt/lampp \
    --with-mysql-sock=/opt/lampp/var/mysql/mysql.sock \
    --with-mcrypt=/opt/lampp \
    --with-mhash=/opt/lampp \
    --enable-sockets \
    --enable-mbstring=all \
    --with-curl=/opt/lampp \
    --enable-mbregex \
    --enable-zend-multibyte \
    --enable-exif \
    --enable-pcntl \
    --with-mime-magic \
    --with-iconv 

make 

sudo make install 

exit 1

 

在脚本的末尾,您的 XAMPP 安装将有一个新的独立运行的能够使用 memcache 的 PHP V4 副本。如果需要测试构建,请停止所有正在运行的 Apache 和 MySQL 副本,包括在 XAMPP 外部的那些副本,并且运行以下命令:

$ sudo /opt/lampp/lampp start

 

这将启动 XAMPP 版本的 Apache 和 MySQL,包括新的 PHP V4 模块。如果需要使 Apache 生产服务器的运行不受干扰,则可以编辑文件 /opt/lampp/etc/httpd.conf 并把 Listen 端口参数改为 8080(或其他可用端口)。然后您可以用以下命令单独启动 XAMPP 的 Apache 服务器:

sudo /opt/lampp/bin/apachectl start

 

把浏览器指向 http://localhost,然后您应当会看到类似于图 1 的内容。
图 1. XAMPP for Linux 初始页面:检验 XAMPP 和 PHP 是否已成功安装
XAMPP for Linux 初始页面:检验 XAMPP 和 PHP 是否已成功安装

要检验您的 PHP 构建是否已安装,请单击左侧的 phpinfo() 链接。您应当会看到类似图 2 中所示的统计信息。PHP 的版本应当是 4.4.6,构建选项应当包括 --enable-memcache,并且 php.ini 的工作版本应当驻留在 /opt/lampp/lib 中。
图 2. 检验 PHP 的自定义构建是否替换了 XAMPP 附带的 PHP 构建
检验 PHP 的自定义构建是否替换了 XAMPP 附带的 PHP 构建

回页首

构建 PHP Memcache 扩展

新的 PHP 就绪后,下一步是构建和安装 PHP Memcache 扩展。该扩展将提供访问 Memcache 守护程序的服务所需的 API。

首先将检索 Memcache 扩展的源代码。您可以再次使用 wget 来获得源代码,请使用以下命令:

$ wget http://pecl.php.net/get/memcache-2.1.0.tgz

 

构建 Memcache 扩展的过程与构建其他 PHP 扩展的过程完全相同:

  1. 切换到源代码的目录
  2. 运行 phpize,后接 ./configuremake 和 make install
  3. 确保使用新版本的 phpize
  4. 把 /opt/lampp/bin 放到 shell 的 PATH 中,位于包含其他版本的 phpize 的所有目录之前:
    $ cd memcache-2.1.0
    $ export PATH=/opt/lampp/bin:$PATH
    $ phpize
    $ ./configure
    $ make
    ...
    $ sudo make install
    Installing shared extensions:    
    /opt/lampp/lib/php/extensions/no-debug-non-zts-20020429/

构建将把一个名为 memcache.so 的新文件放入扩展目录。要加载并应用扩展,您必须编辑 php.ini。打开 XAMPP PHP 配置文件 /opt/lampp/etc/php.ini 并添加清单 3 中的代码行。
清单 3. 编辑 php.ini

                
extension=memcache.so
memcache.allow_failover = 1 
memcache.max_failover_attempts=20 
memcache.chunk_size =8192 
memcache.default_port = 11211

 

第 1 行将加载 Memcache 扩展。其他四行是用于控制扩展的参数。按顺序,从顶部到底部:

memcache.allow_failover
一个布尔值,用于控制当连接出错时 Memcache 扩展是否故障转移到其他服务器上。默认值为 1 (true)。
memcache.max_failover_attempts
一个整型值,用于限制连接到持久性数据或检索数据的服务器数目。如果 memcache.allow_failover 为 false,则将忽略此参数。默认值为 20
memcache.chunk_size
一个整型值,用于控制数据传输的大小。默认值为 8192 字节 (8 KB),但是如果设置为 32768 (32 KB),则可以获得更好的性能。
memcache.default_port
另一个整型值,用于设置连接到 Memcache 所使用的 TCP 端口。除非您修改它,否则默认值为无特权的高端口 11211

要确定构建现在是否能够完全运行,请使用以下命令重新启动 XAMPP Apache Web 服务器:

$ sudo /opt/lampp/bin/apachectl restart

 

如果重新访问 XAMPP phpinfo() 页面,您应当会看到类似图 3 中的 Memcache 部分。
图 3. 通过 phpinfo() 查看 Memcache 设置
通过 phpinfo() 查看 Memcache 设置

回页首

构建 Memcache 守护程序

此过程(表面上有些冗长)还有一个额外的步骤:构建和部署为数据管理 RAM 缓存的 Memcache 守护程序。守护程序依赖于 libevent,因此必须构建和部署该库,然后再编译 memcached:

$ wget http://www.monkey.org/~provos/libevent-1.3b.tar.gz
$ wget http://www.danga.com/memcached/dist/memcached-1.2.1.tar.gz

 

接下来,解压缩 tarball 来为每个包生成一个目录:

$ tar xzf memcached-1.2.1.tar.gz 
$ tar xzf libevent-1.3b.tar.gz

 

要继续执行操作,请依次构建每个包,从库开始。要使所有文件包含在 /opt 中,请在运行配置时使用 --prefix 选项。下面的指令将构建和安装 libevent。
清单 4. 编辑 php.ini

                
$ cd ../libevent-1.3b
$ ./configure --prefix=/opt/lampp
...
$ make
$ sudo make install
...
/usr/bin/install -c .libs/libevent.lai /opt/lampp/lib/libevent.la
/usr/bin/install -c .libs/libevent.a /opt/lampp/lib/libevent.a
chmod 644 /opt/lampp/lib/libevent.a
ranlib /opt/lampp/lib/libevent.a
PATH="$PATH:/sbin" ldconfig -n /opt/lampp/lib
----------------------------------------------------------------------
Libraries have been installed in:
   /opt/lampp/lib

 

接下来的命令将构建和安装 memcached 二进制。
清单 5. 编辑 php.ini

                
$ cd ../memcached-1.2.1
$ ./configure --prefix=/opt/lampp
...
$ make
$ sudo make install
...
/usr/bin/install -c memcached /opt/lampp/bin/memcached
/usr/bin/install -c memcached-debug /opt/lampp/bin/memcached-debug

 

启动 memcached 十分简单:

./memcached -d -m 2048 -l ip-address -p 11211

 

-d 选项将把 memcached 作为一个守护程序而不是在最显著的位置中运行。-m number 将把 number 兆字节分配给此过程实例。(在某些系统中,可能会要求您运行多个 memcached 实例以访问可用于进行缓存的所有内存。有关更多信息,请参阅 Memcache 文档)。-l ip-address -p 11211 将使守护程序分别侦听 IP 地址 ip-address 和端口 11211。替换您的 IP 地址。如果为 memcached 选择其他端口,请确保 php.ini 将反映该端口。

回页首

缓存数据

安装后,就可以让 Memcache 运行了。

Memcache 通常用于存储对象,但是它可以存储可序列化的任何 PHP 变量,例如字符串。Memcache 有一个面向过程和一个面向对象的 API。不管您使用哪一个变量,您必须提供四个实参才能把变量存储到缓存中:

惟一关键字
关键字用于从缓存中检索相关数据。如果每条记录都有一个惟一 ID,则可能足以作为缓存关键字,但是您可以策划其他模式来满足需求。
要缓存的变量
变量可以是任意类型,只要它可以被序列化为持久的变量并且可以取消序列化为检索的变量。
用于启用通过 zlib 进行动态压缩的布尔值
压缩将节省缓存中的内存 —— 虽然处理数据时都要以保存和恢复为代价。
以秒为单位指定的过期时间
当缓存的数据过期时,它将被自动删除。如果将此值设为 0,则该条目永远不会在缓存中过期。使用 Memcache APIdelete() 函数删除这样一个永久对象。

下面的代码将使用面向对象的 Memcache 扩展 API 来创建、存储和检索一个简单对象。
清单 6. 创建、缓存和检索 PHP 对象

                
<?php

class myCache extends Memcache  {   
	function getInstance() {
    static $instance;     

    if ( ! isset( $instance )) {
    $instance = new Memcache;
    $instance->connect(  '198.168.0.1'  );
    }       

    return $instance;      
    }  

    function close( ) {
        if ( isset( $instance ) ) {
            $instance->close();
            $instance = null; 
        }
    }
}

class myDatum {
    var $values = array(
        'description' => null,
        'price'       => null,
        'weight'      => null
    );

	function myDatum( $settings ) {
	    foreach ( $settings as $key => $value ) {
            if ( ! array_key_exists( $key, $this->values ) ) {
                die( "Unknown attribute: $key" );
            }

            $this->values{ $key } = $value;
        }
	}

	function set( $key=null, $value=null ) {
	    if ( is_null( $key ) ) {
            die( "Key cannot be null" );
        }

        if ( ! array_key_exists( $key, $this->values ) ) {
            die( "Unknown attribute: $key" );
        }

        if ( ! is_null( $value ) ) {
            $this->values{ $key } = $value;
        }

        return( $this->values{ $key } );
	}

	function get( $key=null ) {
	    return( $this->set( $key, null ) );
	}
}

$datum = new myDatum( 
    array(
        'description' => 'ball',
        'price'       => 1.50,
        'weight'      => 10
    ) 
);

print $datum->get( 'description' ) . "\n";

$cache = myCache::getInstance( );

if ( $cache->set( $datum->get( 'description' ), $datum, false, 600 ) ) {
    print( 'Object added to cache' . "\n"); 
}

$cache->close();

$new_cache = myCache::getInstance( );

$new_datum = $new_cache->get( $datum->get( 'description' ) );

if ( $new_datum !== false ) {
    print( 'Object retrieved from cache' . "\n"); 
    print $new_datum->get( 'description' ) . "\n";
}

print_r( $new_cache->getExtendedStats() );

$new_cache->close();
?>

 

myCache 类是单件并提供指向缓存的单独的开放连接。myDatum 类代表了所有对象:它有一系列属性(实现为散列),以及一个 getter 和一个 setter 方法。要创建 myDatum 对象,请把一个值散列传递给其构造函数。要设定属性,请调用带有属性名的 set() 作为字符串和值。要获得属性,请调用带有属性名的 get()

以上代码中的最后几行将创建对象、把对象存储到缓存中并检索对象。Memcache API 的倒数第二行、部分将显示缓存的统计信息。如果使用新的 PHP 命令行解释程序运行清单 6,则应当会看到类似清单 7 所示的内容。
清单 7. 创建、缓存和检索 PHP 对象

                
ball
Object added to cache
Object retrieved from cache
ball
Array
(
    [72.51.41.164:11211] => Array
        (
            [pid] => 865
            [uptime] => 3812845
            [time] => 1173817644
            [version] => 1.1.12
            [rusage_user] => 0.043993
            [rusage_system] => 0.038994
            [curr_items] => 1
            [total_items] => 5
            [bytes] => 145
            [curr_connections] => 1
            [total_connections] => 8
            [connection_structures] => 3
            [cmd_get] => 5
            [cmd_set] => 5
            [get_hits] => 5
            [get_misses] => 0
            [bytes_read] => 683
            [bytes_written] => 1098
            [limit_maxbytes] => 67108864
        )
)

 

十分简单,不是?如果 myDatum 的构造函数是典型的,则它将很可能被给定一个 ID 并将查询数据库以生成特定行(例如,查找社会安全号码为 123-45-6789 的学生)。您可以扩展构造函数以便在缓存中首先查找 ID。如果找到,则只需返回该对象。否则,构造对象,缓存并返回该对象。

如果有一组 Debian Linux 系统,则可以复制或导出(通过 NFS)/opt/lampp 并在多个系统中运行 memcached。在两台或多台计算机中同时运行 memcached 将删除单点故障并扩展缓存的容量。使用 addServer() API 函数来构建一列可用的 memcached 服务器。

回页首

我希望速度更快!

使用 Memcache PHP API 十分简单,并且 memcached 十分易于部署。可能大部分工作就是(重新)构建 PHP 本身以包括适当的扩展。的确,如果用于构造对象的方法已被良好隔离,则修改代码以利用 Memcache 应当是小事一桩。

正如您已经看到的,一些简单的技术和一点点时间和努力就可以提高 PHP 应用程序性能。在购买更多 RAM 或另一台服务器之前,请先尝试调整现有服务器。那样做更便宜!

 

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
  • 阅读本系列的 第 1 部分 和 第 2 部分
  • PHP.net 是面向 PHP 开发人员的资源网站。
  • 查阅 “PHP 推荐读物列表”。
  • 浏览 developerWorks 上的所有 PHP 文章 和 PHP 教程
  • 查阅 IBM developerWorks 的 PHP 项目资源 提高 PHP 技巧。
  • 要收听针对软件开发人员的有趣访谈和讨论,访问 developerWorks 的 podcast
  • 随时关注 developerWorks 的 技术事件和网络广播
  • 要将数据库与 PHP 结合使用?查看 Zend Core for IBM,它是支持 IBM DB2 9 的无缝、即用且易于安装的 PHP 开发和生产环境。
  • 查阅最近将在全球举办的面向 IBM 开放源码开发人员的研讨会、展览、网络广播和其他 活动
  • 访问 developerWorks 开源软件技术专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。

获得产品和技术

讨论

关于作者

Martin Streicher 是 Linux Magazine 的主编。他从普度大学获得了计算机科学硕士学位,从 1982 年开始用 Pascal、C、Perl、Java 和(最近)Ruby 编程语言编写类 UNIX 的系统。

用 Memcache 守护程序把数据缓存到内存中
标签: