代码之家  ›  专栏  ›  技术社区  ›  jkndrkn

如何在PHP/MySQL应用程序中充分利用多核CPU?

  •  30
  • jkndrkn  · 技术社区  · 14 年前

    我维护一个定制的类似CMS的应用程序。

    每当提交文档时,都会执行几个任务,这些任务可以大致分为以下类别:

    1. MySQL查询。
    2. HTML内容分析。
    3. 搜索索引更新。

    类别1包括与文档内容相关的各种mysql表的更新。

    类别2包括解析存储在mysql longtext字段中的HTML内容,以执行一些自动定位标记转换。我怀疑这项任务花费了大量的计算时间。

    类别3包括对基于MySQL的简单搜索索引的更新,只使用与文档相对应的少数字段。

    所有这些任务都需要完成,文档提交才能被视为完成。

    承载此应用程序的计算机具有双四核Xeon处理器(共8核)。但是,每当文档提交时,执行的所有PHP代码都被限制在一个核心上运行的单个进程中。

    我的问题:

    如果有的话,您使用了什么方案来将您的php/mysql web应用程序处理负载划分为多个CPU核心?我的理想解决方案基本上会产生几个进程,让它们在几个核心上并行执行,然后阻塞,直到所有进程都完成。

    相关问题:

    您最喜欢的PHP性能分析工具是什么?

    5 回复  |  直到 5 年前
        1
  •  34
  •   Pascal MARTIN    14 年前

    PHP并不是面向多线程的:正如您已经注意到的,每个页面都由一个PHP进程提供服务——一次只做一件事,包括在数据库服务器上执行SQL查询时“等待”。

    不幸的是,你对此无能为力:这是PHP的工作方式。


    不过,还是有一些想法:

    • 首先,您的服务器上一次可能会有更多的1个用户,这意味着您将同时提供多个页面,而这又意味着您将同时运行多个PHP进程和SQL查询…这意味着将使用服务器的多个核心。
      • 每个PHP进程将在一个核心上运行,以响应一个用户的请求,但是有几个Apache子进程并行运行 (每个请求一个,最多几十个或数百个,具体取决于您的配置)
      • mysql服务器是多线程的,这意味着它可以使用几个不同的核心来回答几个并发请求——即使每个请求不能由多个核心提供服务。

    因此,实际上,服务器的8核最终将被使用;-)


    而且,如果你认为你的页面需要花费太长时间来生成,一个可能的解决方案是将你的计算分成两组:

    • 一方面,要生成页面必须做的事情:对于那些人来说,你做不了太多
    • 另一方面,有时必须运行的东西,但不一定是立即运行的。
      • 例如,我在考虑一些统计计算:您希望它们是最新的,但是如果它们落后几分钟,那通常是相当好的。
      • 电子邮件发送也是如此:不管怎样,在用户接收/阅读邮件前几分钟就会过去,因此不需要立即发送邮件。

    对于我第二点中的情况,因为你不需要立即完成那些事情…好吧,只是不要马上做;-)
    我经常使用的解决方案是一些排队机制:

    • Web应用程序将内容存储在“待办事项列表”中
    • 这个“todo list”被一些经常通过cronjob运行的批处理取消排队。

    对于其他一些操作,您只希望它们每隔X分钟运行一次——在这里,cronjob也是完美的工具。

        2
  •  56
  •   Community miroxlav    7 年前

    介绍

    PHP已满 Multi-Threading 你可以在很多方面充分利用的支持。已经能够在不同的示例中演示这种多线程能力:

    A quick Search 会提供额外的资源。

    类别

    1:MySQL查询

    MySQL is fully multi-threaded 并且将利用多个CPU,前提是操作系统支持它们,如果正确配置性能,它还将最大化系统资源。

    中的典型设置 my.ini 影响线程性能的是:

    thread_cache_size = 8
    

    thread_cache_size 如果有很多新的连接,可以提高性能。通常,如果您有一个好的线程实现,那么这不会提供显著的性能改进。但是,如果您的服务器每秒看到数百个连接,您通常应该将线程缓存大小设置得足够高,以便大多数新连接使用缓存线程。

    如果您正在使用 Solaris 然后你可以用

    thread_concurrency = 8 
    

    thread_concurrency 使应用程序能够向线程系统提供有关应同时运行的所需线程数的提示。

    该变量自mysql 5.6.1起已被弃用,并在mysql 5.7中被删除。您应该在任何时候从MySQL配置文件中删除它,除非它们是针对Solaris 8或更早版本。

    InnoDB: :

    如果你在使用 Innodb 具有存储引擎,因为它完全支持线程并发性

    innodb_thread_concurrency //  Recommended 2 * CPUs + number of disks
    

    你也可以看看 innodb_read_io_threads innodb_write_io_threads 默认值为 4 它可以增加到 64 取决于硬件

    其他:

    其他配置也要查看包括 key_buffer_size , table_open_cache ,请 sort_buffer_size 等,这一切都会导致更好的性能

    PHP:

    pure PHP 可以在单独的PHP线程中执行每个查询的地方创建mysql worker。

    $sql = new SQLWorker($host, $user, $pass, $db);
    $sql->start();
    
    $sql->stack($q1 = new SQLQuery("One long Query")); 
    $sql->stack($q2 = new SQLQuery("Another long Query"));
    
    $q1->wait(); 
    $q2->wait(); 
    
    // Do Something Useful
    

    Here is a Full Working Example of SQLWorker

    2:HTML内容解析

    I suspect that a great deal of computation time is spent in this task.

    如果您已经知道这个问题,那么通过事件循环、作业队列或使用线程可以更容易地解决。

    一次处理一个文档可以是 very very slow painful process . @ka 一旦他用Ajax来调用多个请求,一些有创造力的人就会用 pcntl_fork 但是如果你用 windows 那你就不能利用 pcntl

    pThreads 支持Windows和Unix系统,您没有这样的限制。就像..一样简单如果你需要分析 100 document ? Spawn 100 Threads …简单的

    HTML扫描

    // Scan my System
    $dir = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
    $dir = new RecursiveIteratorIterator($dir);
    
    // Allowed Extension
    $ext = array(
            "html",
            "htm"
    );
    
    // Threads Array
    $ts = array();
    
    // Simple Storage
    $s = new Sink();
    
    // Start Timer
    $time = microtime(true);
    
    $count = 0;
    // Parse All HTML
    foreach($dir as $html) {
        if ($html->isFile() && in_array($html->getExtension(), $ext)) {
            $count ++;
            $ts[] = new LinkParser("$html", $s);
        }
    }
    
    // Wait for all Threads to finish
    foreach($ts as $t) {
        $t->join();
    }
    
    // Put The Output
    printf("Total Files:\t\t%s \n", number_format($count, 0));
    printf("Total Links:\t\t%s \n", number_format($t = count($s), 0));
    printf("Finished:\t\t%0.4f sec \n", $tm = microtime(true) - $time);
    printf("AvgSpeed:\t\t%0.4f sec per file\n", $tm / $t);
    printf("File P/S:\t\t%d file per sec\n", $count / $tm);
    printf("Link P/S:\t\t%d links per sec\n", $t / $tm);
    

    产量

    Total Files:            8,714
    Total Links:            105,109
    Finished:               108.3460 sec
    AvgSpeed:               0.0010 sec per file
    File P/S:               80 file per sec
    Link P/S:               907 links per sec
    

    使用的类

    Sink

    class Sink extends Stackable {
        public function run() {
        }
    }
    

    LinkParser

    class LinkParser extends Thread {
    
        public function __construct($file, $sink) {
            $this->file = $file;
            $this->sink = $sink;
            $this->start();
        }
    
        public function run() {
            $dom = new DOMDocument();
            @$dom->loadHTML(file_get_contents($this->file));
            foreach($dom->getElementsByTagName('a') as $links) {
                $this->sink[] = $links->getAttribute('href');
            }
        }
    }
    

    实验

    尝试句法分析 8,714 拥有的文件 105,109 链接没有线程,看看需要多长时间。

    更好的体系结构

    生成太多线程,这在生产中不是一件聪明的事情。更好的方法是使用 Pooling . 有一个定义池 Workers 然后 stack 用一个 Task

    绩效改进

    很好,上面的例子仍然是 improved .安装等待系统扫描 all files in a single thread 你也可以 use multiple threads scan my system 对于文件,然后将数据堆叠到工作人员进行处理。

    3:搜索索引更新

    第一个答案几乎可以回答这个问题,但是有很多方法可以改进性能。你有没有考虑过基于事件的方法?

    介绍活动

    @rdlowrey 报价1:

    好吧,这样想。假设您需要在Web应用程序中为10000个同时连接的客户机提供服务。传统 每个请求的线程数 按请求处理 服务器不是一个选项,因为无论您的线程有多轻,您仍然不能一次打开10000个线程。

    @罗瑞 报价2:

    另一方面,如果您将所有套接字保持在一个进程中,并且监听这些套接字以使其可读或可写,那么您可以将整个服务器放入单个事件循环中,并且仅当有要读/写的内容时才对每个套接字进行操作。

    你为什么不试试 event-driven , non-blocking I/O 解决你的问题。PHP有 libevent 为您的应用程序增压。

    我知道这个问题就是 Multi-Threading 但是如果你有时间,你可以看看这个 Nuclear Reactor written in PHP 通过 @igorw

    终于

    考虑

    我想你应该用冷凝器 Cache Job Queue 完成一些任务。你可以很容易地得到一个信息说

    Document uploaded for processing ..... 5% - Done   
    

    然后在后台完成所有浪费时间的任务。请看 Making a large processing job smaller 用于类似的案例研究。

    分装机

    分析工具??没有用于Web应用程序的单一配置文件工具 Xdebug Yslow 都很有用。xdebug在线程方面没有用处,因为它不受支持

    我没有最喜欢的

        3
  •  4
  •   Community miroxlav    7 年前

    扩展Web服务器并不会使MySQL在访问多核CPU时让步一英寸。为什么?首先考虑MySQL的两个主要存储引擎

    MyISAM

    此存储引擎无法访问多个核心。它从来没有也永远不会。它为每个插入、更新和删除操作执行完全表锁定。从多个Web服务器发送查询以使用myisam做任何事情都会遇到瓶颈。

    InnoDB

    在MySQL5.1.38之前,这个存储引擎只访问了一个CPU。你必须做一些奇怪的事情,比如 run MySQL multiple times on one machine to coerce the cores to handle different instances of MySQL . 然后,在多个实例之间平衡Web服务器的DB连接负载。这是老一套(尤其是如果您使用的是MySQL5.1.38之前的版本)。

    从mysql 5.1.38开始,安装新的innodb插件。它有一些特性,您必须对这些特性进行调优,才能让InnoDB访问多个CPU。我在DBA StackExchange中写过这个

    这些新功能在MySQL5.5/5.6和Percona服务器中也完全可用。

    告诫

    如果自定义CMS使用全文索引/搜索,则应升级到MySQL5.6,因为InnoDB现在支持全文索引/搜索。

    安装到MySQL5.6不会自动使CPU运行。您必须对其进行调优,因为在未配置的情况下,旧版本的MySQL可能会超过并超过新版本:

        4
  •  2
  •   Anthony Forloney    9 年前

    这可能不是您正在寻找的问题的答案,但您寻求的解决方案涉及线程。多核编程需要线程,线程是 在PHP中实现。

    但是,在某种意义上,您可以通过依赖操作系统的多任务能力来在PHP中伪造线程。我建议快速概述一下 Multi-threading strategies in PHP 制定一个策略来实现你所需要的。

    死链接: Multi-threading strategies in PHP

        5
  •  1
  •   simhumileco Janarthanan Ramu    5 年前

    让你们知道,当你们想: “较差的PHP没有多线程”

    好。。。 Python doesn't have real multithreading either . Nor does NodeJS have multi-threading support .Java有某种类型的多线程,但即使在那里, some code halts the whole machine afaik .

    但是:除非你对一件事进行大量的编程,否则这是无关紧要的。许多请求击中了您的页面,所有核心都将被使用,因为每个请求都用它自己的单个线程产生自己的进程。