关于性能:在高负载站点中使用PHP的策略

Tactics for using PHP in a high-load site

在回答这个问题之前,我从来没有开发过任何流行的东西来获得高服务器负载。把我当作(叹息)一个刚刚登陆地球的外星人,尽管他知道PHP和一些优化技术。

我正在开发一个PHP工具,如果它运行正常的话,可以获得相当多的用户。然而,尽管我完全有能力开发这个程序,但当涉及到可以处理巨大流量的事情时,我几乎一无所知。所以这里有几个问题(也可以把这个问题变成一个资源线程)。

数据库

目前,我计划使用php5中的mysqli特性。但是,我应该如何设置与用户和内容相关的数据库?我真的需要多个数据库吗?目前,所有东西都混在一个数据库中——尽管我一直在考虑将用户数据分散到一个数据库中,将实际内容分散到另一个数据库中,最后将核心网站内容(模板主控形状等)分散到另一个数据库中。我的推理是,将查询发送到不同的数据库将减轻它们的负载,因为一个数据库=3个负载源。如果它们都在同一个服务器上,这是否仍然有效?

缓存

我有一个模板系统,用于构建页面和交换变量。主模板存储在数据库中,每次调用模板时都会调用其缓存副本(HTML文档)。目前,我在这些模板中有两种类型的变量——静态变量和动态变量。静态变量通常是页面名称、站点名称之类的内容,这些内容不会经常更改;动态变量是在每次页面加载时都会更改的内容。

我的问题是:

假设我对不同的文章有评论。更好的解决方案是:每次加载页面时存储简单的注释模板并呈现注释(通过数据库调用),或者将注释页面的缓存副本存储为HTML页面-每次添加/编辑/删除注释时,都会重新获取页面。

最后

任何人都有在PHP上运行高负载站点的提示/指针吗?我很肯定这是一种可行的语言-Facebook和雅虎!给它很大的优先权-但是有什么经验我应该注意吗?


没有两个站点是相同的。您真的需要一个像JMeter和Benchmark这样的工具来查看您的问题点在哪里。你可以花很多时间去猜测和改进,但是在测量和比较你的变化之前,你不会看到真正的结果。

例如,多年来,MySQL查询缓存是解决所有性能问题的解决方案。如果您的站点运行缓慢,MySQL专家建议打开查询缓存。事实证明,如果您有很高的写负载,那么缓存实际上是残废的。如果你没有测试就打开了它,你永远不会知道。

别忘了,你永远不会完成缩放。一个处理10req/s的站点需要更改来支持1000req/s。如果您幸运地需要支持10000req/s,那么您的架构也可能完全不同。

数据库

  • 不要使用mysqli——pdo是"现代"OO数据库访问层。要使用的最重要的特性是查询中的占位符。它足够聪明,可以使用服务器端的准备和其他优化。
  • 此时您可能不想中断数据库。如果你确实发现一个数据库没有被剪切,那么根据你的应用程序,有几种技术可以扩展。如果读的比写的多,复制到其他服务器通常工作得很好。切分是一种在多台机器上分割数据的技术。

缓存

  • 您可能不想在数据库中缓存。数据库通常是您的瓶颈,因此向其添加更多IO通常是一件坏事。有几个PHP缓存可以完成类似的事情,比如APC和Zend。
  • 在打开和关闭缓存的情况下测量系统。我敢打赌你的缓存比直接提供页面要重。
  • 如果从数据库构建评论和文章数据需要很长时间,请将memcache集成到系统中。您可以缓存查询结果并将其存储在memcached实例中。重要的是要记住,从memcache中检索数据必须比从数据库中组装数据更快,才能看到任何好处。
  • 如果您的文章不是动态的,或者您在生成之后进行了简单的动态更改,那么可以考虑将HTML或PHP写到磁盘上。您可以有一个index.php页面在磁盘上查找该文章,如果它在磁盘上,它会将其流式传输到客户机。如果不是,它会生成文章,将其写入磁盘并发送给客户机。从磁盘中删除文件将导致重新写入页面。如果向文章中添加注释,请删除缓存副本——它将被重新生成。


我是一个拥有超过1500万用户的网站的主要开发人员。我们几乎没有缩放问题,因为我们提前计划了它,并进行了深思熟虑的缩放。以下是我从经验中可以提出的一些策略。

图式首先,取消模式的规范化。这意味着,与其有多个关系表,不如选择有一个大表。一般来说,连接是浪费宝贵的数据库资源,因为进行多次准备和整理会烧坏磁盘I/O。尽可能避免这些操作。

这里的权衡是您将要存储/提取冗余数据,但这是可以接受的,因为数据和笼内带宽非常便宜(较大的磁盘),而多准备I/O则是数量级更昂贵(更多的服务器)。

索引确保您的查询至少使用一个索引。但是要小心,如果您频繁地编写或更新索引,会花费您很多钱。有一些实验技巧可以避免这种情况。

您可以尝试添加其他未编入索引的列,这些列与编入索引的列并行运行。然后,您可以拥有一个脱机进程,该进程批量在索引列上写入非索引列。这样,您可以更好地控制MySQL何时需要重新计算索引。

避免像瘟疫一样的计算查询。如果必须计算查询,请尝试在写入时执行一次。

缓存我强烈推荐Memcached。它已经被php栈(facebook)上最大的玩家证明了,并且非常灵活。实现这一点有两种方法,一种是在数据库层中缓存,另一种是在业务逻辑层中缓存。

db layer选项需要缓存从db中检索到的查询结果。您可以使用md5()散列您的SQL查询,并在转到数据库之前将其用作查找键。这样做的好处是它很容易实现。缺点(取决于实现)是,您会失去灵活性,因为在缓存到期方面,您对所有缓存的处理都是相同的。

在我工作的车间中,我们使用业务层缓存,这意味着系统中的每个具体类都控制自己的缓存模式和缓存超时。这对我们来说非常有效,但是请注意,从数据库中检索到的项可能与从缓存中检索到的项不同,因此您必须一起更新缓存和数据库。

数据分片复制只会让你走到目前为止。比预期的快,您的写操作将成为瓶颈。要进行补偿,请确保尽早支持数据切分。如果你不这样做,你很可能会想稍后开枪自杀。

实现起来相当简单。基本上,您希望将密钥颁发机构与数据存储区分开。使用全局数据库存储主键和集群ID之间的映射。您查询这个映射以获取集群,然后查询集群以获取数据。您可以从这个查找操作中缓存地狱,这将使它成为一个微不足道的操作。

这样做的缺点是很难将来自多个碎片的数据拼凑在一起。但是,你也可以用自己的方式来解决这个问题。

脱机处理如果不需要,不要让用户等待您的后端。构建一个作业队列,并移动任何可以脱机的处理,使其与用户的请求分离。


我曾在一些网站上工作过,这些网站得到了php&mysql支持,每月数百万次点击量。以下是一些基础知识:

  • 缓存,缓存,缓存。缓存是减少Web服务器和数据库负载的最简单和最有效的方法之一。缓存页面内容、查询、昂贵的计算、任何I/O绑定的内容。memcache非常简单有效。
  • 一旦用完,就使用多个服务器。您可以有多个Web服务器和多个数据库服务器(使用复制)。
  • 减少对Web服务器的总体请求。这需要使用Expires头缓存JS、CSS和图像。您还可以将静态内容移动到cdn,这将加快您的用户体验。
  • 测量和基准。在生产机器上运行nagios,在dev/qa服务器上运行负载测试。你需要知道你的服务器什么时候会着火,这样你才能阻止它。
  • 我建议阅读构建可扩展网站,它是由Flickr的一个工程师编写的,是一个很好的参考。

    也可以查看我关于可伸缩性的博客文章,它有很多链接指向有关使用多种语言和平台进行伸缩的演示:http://www.ryandoherty.net/2008/07/13/unicorns-and-scalability/


    回复:PDO/mysqli/mysqlnd

    @加里

    你不能说"不要使用mysqli",因为他们有不同的目标。PDO几乎就像一个抽象层(尽管实际上不是),它的设计目的是使多个数据库产品的使用变得容易,而mysqli则是特定于mysql连接的。在将PDO与mysqli进行比较的情况下,将其称为现代访问层是错误的,因为您的语句暗示进程是mysql->mysqli->pdo,而事实并非如此。

    mysqli和pdo之间的选择很简单——如果需要支持多个数据库产品,那么就使用pdo。如果您只是使用MySQL,那么您可以在PDO和MySQLI之间进行选择。

    那你为什么选择mysqli而不是pdo呢?见下文…

    @罗斯

    您对mysqlnd的看法是正确的,它是最新的mysql核心语言级别库,但它不是mysqli的替代品。mysqli(与pdo一样)仍然是通过php代码与mysql交互的方式。这两种方法都使用libmysql作为PHP代码背后的C客户机。问题是libmysql不在核心php引擎的范围内,这就是mysqlnd的所在,也就是说,它是一个本地驱动程序,利用核心php内部来最大限度地提高效率,特别是在内存使用方面。

    mysqlnd是由mysql自己开发的,最近登陆了php 5.3分支,该分支正在进行rc测试,准备在今年晚些时候发布。然后您就可以将mysqlnd与mysqli一起使用……但不能与pdo一起使用。这将在许多领域(不是所有领域)提高mysqli的性能,并且如果您不需要PDO的抽象功能,它将成为mysql交互的最佳选择。

    也就是说,mysqlnd现在可以在php 5.3中用于pdo,因此您可以从nd到pdo中获得性能增强的优势,但是,pdo仍然是一个通用数据库层,因此不太可能从nd中的增强中获得尽可能多的好处。

    这里可以找到一些有用的基准,尽管它们来自2006年。您还需要了解类似于此选项的内容。

    在决定mysqli和pdo时,需要考虑很多因素。事实上,除非你得到了高得出奇的请求数,否则一切都无关紧要。在这种情况下,使用专门为MySQL设计的扩展比使用抽象事物并恰好提供MySQL驱动程序的扩展更有意义。

    这不是一个简单的问题,哪一个最好,因为每个都有优点和缺点。你需要阅读我提供的链接,并提出你自己的决定,然后测试并找出答案。我在过去的项目中使用过PDO,它是一个很好的扩展,但是我选择纯性能的是mysqli,编译了新的mysqlnd选项(当php 5.3发布时)。


    概述

    • 在开始看到现实世界的负载之前,不要尝试优化。你可能猜对了,但如果你不猜对了,你就浪费时间了。
    • 使用jmeter、xdebug或其他工具对站点进行基准测试。
    • 如果加载开始成为一个问题,那么可能会涉及对象或数据缓存,因此通常会读取缓存选项(memcached、mysql缓存选项)

    代码

    • 分析您的代码,以便您知道瓶颈在哪里,以及它是在代码中还是在数据库中。

    数据库

    • 如果到其他数据库的可移植性不重要,则使用mysqli,否则使用pdo
    • 如果基准测试显示数据库是问题所在,请在开始缓存之前检查查询。使用"解释"查看查询速度减慢的位置。
    • 在优化查询并以某种方式缓存数据库之后,您可能需要使用多个数据库。根据数据、查询和读/写行为的类型,复制到多个服务器或共享(在多个数据库/服务器上拆分数据)可能是合适的。

    缓存

    • 在缓存代码、对象和数据方面已经做了大量的工作。查找有关apc、zend优化器、memcached、quickcache、jpcache的文章。在你真正需要之前做一些这样的事情,你就不会那么担心开始时没有优化。
    • apc和zend优化器是操作码缓存,它们通过避免代码的重新编译来加速PHP代码。一般安装简单,值得早点做。
    • memcached是一个通用缓存,可以用来缓存查询、PHP函数或对象,或者整个页面。必须专门编写代码才能使用它,如果没有用于处理缓存对象的创建、更新和删除的中心点,这可能是一个涉及的过程。
    • quickcache和jpcache是文件缓存,否则类似于memcached。基本概念很简单,但也需要代码,并且更容易通过创建、更新和删除中心点来实现。

    其他

    • 为高负载考虑其他Web服务器。如果您牺牲了Apache的能力和灵活性(或者如果您只是不需要这些东西,而这通常是您不需要的),那么LightHTTP和nginx等服务器可以在比Apache更少的内存中处理大量流量。
    • 记住,现在的硬件非常便宜,所以一定要花掉优化一大块代码的成本,而不是"让我们买一台怪物服务器"。
    • 考虑在这个问题中添加"mysql"和"scaling"标签。


    首先,正如我认为克努斯所说,"过早的优化是万恶之源"。如果你现在不需要处理这些问题,那就不要去处理,先把精力放在交付一些正确的东西上。也就是说,如果优化不能等待。

    尝试分析您的数据库查询,找出什么是缓慢的和发生了什么,并从中提出一个优化策略。

    我会研究memcached,因为很多高负载的站点都使用它来有效地缓存所有类型的内容,并且与它的php对象接口非常好。

    在服务器之间拆分数据库,并使用某种负载平衡技术(例如,在1到冗余数据库之间生成一个随机数,其中包含必要的数据-并使用该数字确定要连接到哪个数据库服务器),这也是提高效率的一个很好的方法。

    在过去,对于一些相当高负载的站点来说,这些都运行得很好。希望这有助于您开始工作:-)


    APC是绝对必须的。它不仅为一个伟大的缓存系统做了贡献,而且从自动缓存的PHP文件中获得的好处是天赐良机。至于多数据库的想法,我认为在同一台服务器上使用不同的数据库不会有什么好处。在查询期间,它可能会提高您的速度,但我怀疑在确保这三个代码同步的同时部署和维护这三个代码所需的努力是否值得。

    我还强烈建议运行xdebug来查找程序中的瓶颈。这使优化对我来说轻而易举。


    我经营一个每月有700-800万页面浏览量的网站。不是很严重,但足以让我们的服务器感受到负载。我们选择的解决方案很简单:数据库级别的memcache。如果数据库负载是您的主要问题,那么这个解决方案可以很好地工作。

    我们开始使用memcache缓存整个对象和最常用的数据库结果。它确实有效,但它也引入了错误(如果我们更加小心的话,我们可能会避免其中的一些错误)。

    所以我们改变了方法。我们构建了一个数据库包装器(使用与旧数据库完全相同的方法,因此很容易切换),然后我们将其子类化,以提供memcached数据库访问方法。

    现在,您所要做的就是决定一个查询是否可以使用缓存的(可能是过期的)结果。用户运行的大多数查询现在直接从memcache中获取。例外情况是更新和插入,对于主网站,这只会由于日志记录而发生。这个相当简单的措施将我们的服务器负载减少了大约80%。


    用xdebug(如TJ9991推荐)来评测你的应用程序绝对是必须的。盲目地进行优化并没有什么意义。xdebug将帮助您在代码中找到真正的瓶颈,这样您就可以明智地花费优化时间,并修复导致速度减慢的代码块。

    如果您使用的是Apache,另一个可以帮助测试的实用程序是Siege。它将帮助您预测服务器和应用程序对高负载的反应,使其真正达到自己的速度。

    任何一种用于PHP的操作码缓存(比如APC或其他许多缓存中的一种)也会有很大帮助。


    尽管没有像memcached这样的扩展/帮助程序包,但是在PHP中缓存非常简单。

    您只需要使用ob_start()创建一个输出缓冲区。

    创建全局缓存函数。调用ob_start,将函数作为回调传递。在函数中,查找页面的缓存版本。如果存在,就服务它并结束。

    如果它不存在,脚本将继续处理。当它到达匹配的ob_end()时,它将调用您指定的函数。这时,您只需获取输出缓冲区的内容,将它们放到一个文件中,保存文件,然后结束。

    添加一些过期/垃圾收集。

    很多人不知道你可以嵌套ob_start()ob_end()电话。因此,如果您已经在使用输出缓冲区来解析广告或进行语法突出显示等操作,那么您可以嵌套另一个ob_start/ob_end调用。


    Thanks for the advice on PHP's caching extensions - could you explain reasons for using one over another? I've heard great things about memcached through IRC but have never heard of APC - what are your opinions on them? I assume using multiple caching systems is pretty counter-effective.

    事实上,很多人都同时使用apc和memcached…


    看来我错了。mysqli仍在开发中。但是根据文章,PDO-MySQL现在正由MySQL团队贡献。从文章中:

    The MySQL Improved Extension - mysqli
    - is the flagship. It supports all features of the MySQL Server including
    Charsets, Prepared Statements and
    Stored Procedures. The driver offers a
    hybrid API: you can use a procedural
    or object-oriented programming style
    based on your preference. mysqli comes
    with PHP 5 and up. Note that the End
    of life for PHP 4 is 2008-08-08.

    The PHP Data Objects (PDO) are a
    database access abstraction layer. PDO
    allows you to use the same API calls
    for various databases. PDO does not
    offer any degree of SQL abstraction.
    PDO_MYSQL is a MySQL driver for PDO.
    PDO_MYSQL comes with PHP 5. As of PHP
    5.3 MySQL developers actively contribute to it. The PDO benefit of a
    unified API comes at the price that
    MySQL specific features, for example
    multiple statements, are not fully
    supported through the unified API.

    Please stop using the first MySQL
    driver for PHP ever published:
    ext/mysql. Since the introduction of
    the MySQL Improved Extension - mysqli
    - in 2004 with PHP 5 there is no reason to still use the oldest driver
    around. ext/mysql does not support
    Charsets, Prepared Statements and
    Stored Procedures. It is limited to
    the feature set of MySQL 4.0. Note
    that the Extended Support for MySQL
    4.0 ends at 2008-12-31. Don't limit yourself to the feature set of such
    old software! Upgrade to mysqli, see
    also Converting_to_MySQLi. mysql is in
    maintenance only mode from our point
    of view.

    对我来说,这篇文章似乎偏向于mysqli。我想我对PDO有偏见。我真的喜欢PDO而不是mysqli。就在我面前。API与我用过的其他语言非常接近。OO数据库接口似乎工作得更好。

    我没有遇到任何特定的MySQL功能,这些功能在PDO中不可用。如果我做过,我会很惊讶的。


    PDO的速度也很慢,其API也相当复杂。如果便携性不是一个问题,在他们理智的头脑中没有人应该使用它。让我们面对现实吧,99%的网络应用程序都不是这样。你只需要坚持mysql或者postrgresql,或者你正在使用的任何东西。

    关于php的问题以及应该考虑什么。我认为过早的优化是万恶之源。;)首先完成应用程序,在编程时尽量保持干净,做一些文档和编写单元测试。有了以上所有这些,当时间到来时重构代码不会有任何问题。但首先你要做的是把它推出去看看人们对它的反应。


    我的第一条建议是在设计网站时考虑这个问题并牢记在心,但不要过火。通常很难预测一个新网站的成功,而我,你的时间将更好地花在早起完成和优化它。

    一般来说,简单就是快。模板会减慢你的速度。数据库会减慢您的速度。复杂的库会减慢速度。将模板相互分层从数据库中检索并在复杂库中分析模板——>时间延迟相乘。

    一旦你有了基本的站点并运行了,做一些测试来向你展示在哪里花费你的努力。很难找到目标。通常,为了加快速度,您必须解开代码的复杂性,这使得代码变得更大,更难维护,所以您只想在必要的时候进行维护。

    根据我的经验,建立数据库连接相对昂贵。如果你能摆脱它,不要连接到数据库的一般访问者在最流量的页面,如首页的网站。创建多个数据库连接是疯狂的,但没有什么好处。


    当然,PDO是不错的,但是它的性能与MySQL和MySQLI相比有一些争议,尽管它现在似乎已经修复了。

    如果您设想可移植性,那么应该使用PDO,但是如果不是,则应该使用MySQLI。它有一个OO接口,准备好的语句,以及PDO提供的大部分功能(除了可移植性)。

    另外,如果确实需要性能,请准备php 5.3中的(本机mysql)mysqlnd驱动程序,它将与php紧密集成,具有更好的性能和改进的内存使用率(以及性能调优的统计数据)。

    如果您有集群服务器(和类似YouTube的负载),memcache是不错的,但我也会先试用apc。


    已经给出了很多很好的答案,但我想向您指出一个名为xcache的备用操作码缓存。它是由一个轻量级的贡献者创建的。

    另外,如果将来可能需要平衡数据库服务器的负载,MySQL代理可以很好地帮助您实现这一点。

    这两种工具都应该很容易地插入到现有的应用程序中,这样,在需要时就可以进行优化,而不必太麻烦。


    第一个问题是你真的期望它有多大?你计划在你的基础设施上投资多少?既然你觉得有必要在这里问这个问题,我猜你会在有限的预算内开始小规模的工作。

    如果站点不可用,则性能无关紧要。对于可用性,您需要水平伸缩。您可以明智地避开的最小值是2台服务器,它们都运行Apache、PHP和MySQL。将一个DBMS设置为另一个DBMS的从属。在master上执行所有的写入操作,在本地数据库上执行所有的读取操作(无论是什么操作),除非出于某种原因需要读取刚刚读取的数据(使用master)。确保你的机器已经就位,可以自动提升奴隶并保护主人。使用Web服务器地址的循环DNS为从属节点提供更多的相关性。

    在这个阶段跨不同的数据库节点对数据进行分区是一个非常糟糕的主意,但是您可能需要考虑在同一台服务器上跨不同的数据库进行分区(当您超过Facebook时,这将有助于跨节点进行分区)。

    一定要确保你已经准备好了监控和数据分析工具来衡量你的站点性能和识别瓶颈。大多数性能问题都可以通过编写更好的SQL/修复数据库模式来解决。

    将模板缓存保存在数据库上是一个愚蠢的想法——数据库应该是结构化数据的中央公共存储库。将模板缓存保存在Web服务器的本地文件系统上-它将更快地可用,并且不会降低数据库访问速度。

    一定要使用操作码缓存。

    花大量的时间研究你的网站和它的日志,以理解为什么它会如此缓慢。

    将尽可能多的缓存推送到客户机上。

    使用mod_gzip压缩所有可以压缩的内容。

    C.


    查看mod_缓存,它是ApacheWeb服务器的输出缓存,与ASP.NET中的输出缓存类似。

    是的,我可以看到它仍然是实验性的,但总有一天它会是最终的。


    我不认为自己很快就会从MySQL转换过来——所以我想我不需要PDO的抽象功能。谢谢你的文章,大卫,他们帮助了我很多。


    我不敢相信没有人提到过这一点:模块化和抽象化。如果你认为你的网站将要成长为许多机器,你必须设计它,以便它可以!这意味着一些愚蠢的事情,比如不要假定数据库在本地主机上。它还意味着一开始会很麻烦的事情,比如编写一个数据库抽象层(比如PDO,但是要轻得多,因为它只做你需要它做的事情)。

    它意味着像使用框架这样的事情。您将需要代码的层,这样您以后就可以通过重构数据抽象层来获得性能,例如,通过告诉它一些对象在不同的数据库中——而代码不必知道或关心。

    最后,注意内存密集型操作,例如不必要的字符串复制。如果您可以降低PHP的内存使用率,那么您将从Web服务器获得更高的性能,当您使用负载平衡解决方案时,这是一个可以扩展的功能。


    @加里

    Don't use MySQLi -- PDO is the 'modern' OO database access layer. The most important feature to use is placeholders in your queries. It's smart enough to use server side prepares and other optimizations for you as well.

    我现在正在讨论PDO,看起来你是对的——不过我知道MySQL正在开发用于PHP的MySQLD扩展——我认为MySQL或MySQLI都会成功——你怎么看?

    @Ryan、Eric、TJ9991

    谢谢你对PHP缓存扩展的建议-你能解释一下为什么要一个接一个地使用吗?我通过IRC听说过关于memcached的很多事情,但从来没有听说过apc——你对它们有什么看法?我认为使用多个缓存系统是相当有效的。

    我肯定会挑选一些分析测试人员-非常感谢您对这些测试的建议。


    如果您正在处理大量的数据,而缓存并没有将其剪切,请查看sphinx。我们在使用sphinxsearch不仅可以更好地搜索文本,而且在处理较大的表时还可以作为mysql的数据检索替代品,取得了很好的效果。如果您使用的是sphinxse(mysql插件),它超过了我们从缓存中获得的性能提升,而且应用程序实现是一个sinch。


    关于缓存所做的工作都是现场进行的;它是构建高效应用程序的最不复杂和最重要的部分。我想补充一点,虽然memcached很好,但是如果您的应用程序在一台服务器上运行,APC的速度大约是它的五倍。

    MySQL性能博客上的"缓存性能比较"文章在这个主题上有一些有趣的基准——http://www.mysql performance blog.com/2006/08/09/cache性能比较。