【问题标题】:How do you make good use of multicore CPUs in your PHP/MySQL applications? [closed]如何在 PHP/MySQL 应用程序中充分利用多核 CPU?
【发布时间】:2011-01-17 01:31:07
【问题描述】:

我维护一个自定义构建的类似 CMS 的应用程序。

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

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

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

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

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

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

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

我的问题:

您使用过哪些方案(如果有)在多个 CPU 内核之间分配 PHP/MySQL Web 应用程序处理负载?我理想的解决方案基本上是产生几个进程,让它们在几个内核上并行执行,然后阻塞直到所有进程都完成。

相关问题:

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

【问题讨论】:

  • 您对多线程感兴趣吗?
  • 嗨,安东尼。在这种情况下,我愿意使用任何可以帮助我的技术。
  • 赏金编辑:我的意思是 PHP 5.5 ;)
  • 对于事后查看此内容的任何人,我发现这也很有帮助:stackoverflow.com/questions/70855/…

标签: php mysql multicore


【解决方案1】:

简介

PHP 具有完整的多线程支持,您可以通过多种方式充分利用它。已经能够在不同的示例中展示这种多线程能力:

quick Search 将提供额外资源。

类别

1:MySQL 查询

MySQL is fully multi-threaded 并且将使用多个 CPU,前提是操作系统支持它们,如果针对性能进行适当配置,它还将最大限度地利用系统资源。

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

thread_cache_size = 8

如果您有很多新连接,可以增加thread_cache_size 以提高性能。通常,如果您有良好的线程实现,这不会提供显着的性能改进。但是,如果您的服务器每秒看到数百个连接,您通常应该将 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_threadsinnodb_write_io_threads,其中默认为4,根据硬件可以增加到64

其他:

还需要查看的其他配置包括 key_buffer_sizetable_open_cachesort_buffer_size 等。这些都可以带来更好的性能

PHP:

在纯 PHP 中,您可以创建 MySQL Worker,其中每个查询都在单独的 PHP 线程中执行

$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内容解析

我怀疑这项任务花费了大量的计算时间。

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

一次处理一个文档可能是一个非常非常缓慢而痛苦的过程。 @ka 曾经使用 ajax 破解了他的出路来调用多个请求,一些有创意的人只会使用 pcntl_fork 分叉这个过程,但如果你使用的是 windows 那么你就不能利用 pcntl

pThreads 同时支持 windows 和 Unix 系统,你没有这样的限制。就这么简单.. 如果您需要解析 100 个文档?生成 100 个线程...简单

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');
        }
    }
}

实验

尝试解析具有105,109 链接但没有线程的8,714 文件,看看需要多长时间。

更好的架构

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

性能改进

很好,上面的例子还有待改进。无需等待系统在单个线程中扫描所有文件,您可以使用多个线程扫描我的系统中的文件,然后将数据堆叠到 Workers 进行处理

3:搜索索引更新

第一个答案几乎已经回答了这个问题,但是有很多方法可以提高性能。您是否考虑过基于事件的方法?

介绍活动

@rdlowrey引用1:

这样想吧。想象一下,您需要在 Web 应用程序中同时为 10,000 个连接的客户端提供服务。传统的 thread-per-requestprocess-per-request 服务器不是一种选择,因为无论您的线程多么轻量级,您仍然无法打开 10,000 个线程一次。

@rdlowrey引用2:

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

您为什么不尝试event-drivennon-blocking I/O 方法来解决您的问题。 PHP 有 libevent 来增强你​​的应用程序。

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

终于

考虑

我认为您应该考虑在某些任务中使用CacheJob Queue。你可以很容易地有一条消息说

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

然后在后台执行所有浪费时间的任务。请查看Making a large processing job smaller 了解类似案例研究。

分析

分析工具?从XdebugYslow 的Web 应用程序没有单一的配置文件工具都非常有用。例如。 Xdebug 在线程方面没有用,因为它不受支持

我没有最喜欢的

【讨论】:

  • CAVEAT:thread_concurrency 仅适用于 Solaris (dev.mysql.com/doc/refman/5.6/en/…)。您需要 innodb_thread_concurrency (仅当所有数据都是 InnoDB 时)。
  • pThreads 已被作者弃用。使用它的继任者 - Parallel
【解决方案2】:

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

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


不过,这里有几个想法:

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

所以,事实上,你的服务器的 8 核最终会被使用 ;-)


而且,如果您认为您的网页生成时间过长,一个可能的解决方案是将您的计算分成两组:

  • 一方面,生成页面必须做的事情:对于那些,您无能为力
  • 另一方面,有时必须运行但不一定立即运行的事情
    • 例如,我正在考虑一些统计计算:您希望它们是最新的,但如果它们落后几分钟,那通常是可以的。
    • 电子邮件发送也是如此:无论如何,在您的用户接收/阅读他们的邮件之前会过几分钟,因此无需立即发送。

对于我第二点中的那种情况,因为您不需要立即完成这些事情......好吧,只是不要立即执行它们;-)
我经常使用的一个解决方案是一些排队机制:

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

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

【讨论】:

  • 我喜欢排队机制的想法。你是如何在 PHP 中实现的?
  • 想到的最简单的想法是在数据库中使用一个表,从 Web 应用程序插入(带有某种“时间戳”列),然后从批处理中选择和删除最旧的行通过 cronjob 运行;;;其他解决方案将使用专门的机制(例如,参见framework.zend.com/manual/en/zend.queue.html,或gearman.org
  • 感谢您的想法和建议。
  • 我喜欢这个答案,只是想补充一点,大多数多任务是由操作系统本身处理的,所以我们不需要担心。
  • @jkndrkn:这么好的问题和答案。我是 php 新手,正在使用 Threading 将行插入表中。在 PHP 中搜索了很多线程 - 但发现 PHP 不是线程安全的, 向你们提出我的问题 - 使用 CRON 脚本实现这样的线程环境是否安全?
【解决方案3】:

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

MyISAM

此存储引擎不访问多个内核。它从来没有,也永远不会。它对每个 INSERT、UPDATE 和 DELETE 进行全表锁定。从多个 Web 服务器发送查询以使用 MyISAM 执行任何操作都会遇到瓶颈。

InnoDB

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

从 MySQL 5.1.38 开始,安装新的 InnoDB 插件。它具有您必须调整以使 InnoDB 访问多个 CPU 的功能。我在 DBA StackExchange 中写过这个

这些新功能在 MySQL 5.5/5.6 和 Percona Server 中也完全可用。

警告

如果您的自定义 CMS 使用 FULLTEXT 索引/搜索,您应该升级到 MySQL 5.6,因为 InnoDB 现在支持 FULLTEXT 索引/搜索。

安装到 MySQL 5.6 不会自动启动 CPU。您将不得不对其进行调整,因为在未配置的情况下,旧版本的 MySQL 可能会超越和超越新版本:

【讨论】:

    【解决方案4】:

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

    但是,从某种意义上说,您可以依靠操作系统的多任务处理能力来伪造 PHP 中的线程。我建议对Multi-threading strategies in PHP 进行快速概述,以制定实现您需要的策略。

    死链接: Multi-threading strategies in PHP

    【讨论】:

    • 这个如果还活着:-)
    • @mins 我已经更新了我的答案以包含一个有效的链接,我已经有一段时间没有检查现在已失效的链接了,所以我无法比较相同内容的新链接,但希望它有帮助给其他人。
    【解决方案5】:

    当你想到的时候让你们知道:“可怜的 PHP 没有多线程”

    嗯...Python doesn't have real multithreading eitherNor does NodeJS have multi-threading support。 Java 有某种多线程,但即便如此,some code halts the whole machine afaik

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-17
      • 1970-01-01
      • 2019-05-12
      • 1970-01-01
      • 1970-01-01
      • 2017-03-30
      相关资源
      最近更新 更多