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

jkn*_*rkn 32 php mysql multicore

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

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

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

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

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

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

所有这些任务都需要完成才能使文档提交完整.

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

我的问题:

您有多少方案(如果有的话)将PHP/MySQL Web应用程序处理负载分散到多个CPU内核中?我理想的解决方案基本上会产生一些进程,让它们在几个内核上并行执行,然后阻塞直到所有进程完成.

相关问题:

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

Bab*_*aba 57

介绍

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

一个快速搜索将提供额外的资源.

分类

1:MySQL查询

MySQL是完全多线程的,并且将使用多个CPU,只要操作系统支持它们,如果正确配置性能,它还将最大化系统资源.

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

thread_cache_size = 8
Run Code Online (Sandbox Code Playgroud)

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

如果您使用的是Solaris,则可以使用

thread_concurrency = 8 
Run Code Online (Sandbox Code Playgroud)

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

从MySQL 5.6.1开始,该变量已被弃用,并在MySQL 5.7中被删除.除非它们适用于Solaris 8或更早版本,否则无论何时看到它都应从MySQL配置文件中删除它.

InnoDB ::

如果您使用Innodb具有存储引擎,则没有此类限制,因为它完全支持线程并发

innodb_thread_concurrency //  Recommended 2 * CPUs + number of disks
Run Code Online (Sandbox Code Playgroud)

您还可以看看innodb_read_io_threadsinnodb_write_io_threads其中默认的是4,它可以提高到高达64取决于硬件

其他:

其他配置也看包括key_buffer_size,table_open_cache,sort_buffer_size 等这一切CAL导致更好的性能

PHP:

windows您可以创建MySQL的工人,这时每个查询都在单独的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
Run Code Online (Sandbox Code Playgroud)

这是SQLWorker的完整工作示例

2:HTML内容解析

pcntl

如果您已经知道问题,那么通过事件循环,作业队列或使用线程更容易解​​决.

一次处理一个文档可以是一个pThreads.@ka曾经使用ajax调用多个请求来破解他的出路,有些创意人员只会使用pcntl_fork来分叉这个过程,但是如果你正在使用Sink那么你就无法利用LinkParser

通过8,714支持Windows和Unix系统,您没有这样的限制.就像..如果你需要解析一样容易105,109Task......简单

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);
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

使用的类

event-driven

class Sink extends Stackable {
    public function run() {
    }
}
Run Code Online (Sandbox Code Playgroud)

non-blocking I/O

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');
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

实验

尝试解析Multi-Threading具有无线程Cache链接的文件,并查看需要多长时间.

更好的架构

产生太多的线程,这在生产中不是一件聪明的事情.更好的方法是使用Pooling.有一个定义工作池然后与一个堆叠Job Queue

性能改进

上面的例子仍然很好my.ini.等待系统扫描innodb_read_io_threads您也可以 innodb_write_io_threads查找文件,然后将数据堆叠到Workers进行处理

3:搜索索引更新

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

介绍活动

@rdlowrey引用1:

好好想想这样.想象一下,您需要在Web应用程序中为10,000个同时连接的客户端提供服务.传统的线程每个请求过程每个请求的服务器是不是一种选择,因为无论你怎么线程轻巧,你仍然无法追究他们10,000同时打开.

@rdlowrey引用2:

另一方面,如果将所有套接字保留在一个进程中并监听这些套接字是否可读或可写,则可以将整个服务器放在单个事件循环中,并且只有在有/可读/写的情况下才对每个套接字进行操作.

你为什么不试验4,64解决你的问题.PHP可以解放您的应用程序.

我知道这个问题就是这样,key_buffer_size 但是如果你有一些时间你可以看看这个由@igorw 用PHP编写核反应堆

最后

考虑

我认为你应该冷凝使用table_open_cachesort_buffer_size你的一些任务.您可以轻松地传达消息

Document uploaded for processing ..... 5% - Done   
Run Code Online (Sandbox Code Playgroud)

然后一直在后台浪费任务.请查看为类似案例研究制作较小的大型处理工作.

Profilling

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

我没有最爱

  • 很好的答案! (3认同)

Pas*_*TIN 37

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

遗憾的是,你无能为力:这就是PHP的工作方式.


不过,这里有几个问题:

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

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


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

  • 一方面,生成页面必须要做的事情:对于那些,你可以做的事情并不多
  • 另一方面,有时需要运行的东西,但不一定是立即运行的
    • 例如,我考虑一些统计计算:你希望它们是最新的,但如果它们落后几分钟,这通常是相当不错的.
    • 电子邮件发送也是如此:无论如何,在用户收到/阅读邮件之前几分钟就会过去,因此无需立即发送邮件.

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

  • Web应用程序将事物存储在"待办事项列表"中
  • 并且"todo-list"由一些经常通过cronjob运行的批次排队

对于其他一些操作,你只需要每X分钟运行一次 - 而且,这里也是一个完美的工具.


Rol*_*DBA 5

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

MyISAM

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

数据库

在MySQL 5.1.38之前,该存储引擎仅访问一个CPU。您必须做一些奇怪的事情,例如在一台机器上多次运行 MySQL 来强制内核处理不同的 MySQL 实例。然后,在多个实例之间平衡 Web 服务器的数据库连接负载。这是老派的做法(特别是如果您使用的是 MySQL 5.1.38 之前的 MySQL 版本)。

从 MySQL 5.1.38 开始,安装新的 InnoDB 插件。为了让 InnoDB 访问多个 CPU,您必须调整它的一些功能。我已经在 DBA StackExchange 中写过相关内容

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

警告

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

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