Как хорошо использовать многоядерные процессоры в ваших PHP / MySQL приложениях?

Я поддерживаю пользовательское CMS-подобное приложение.

Всякий раз, когда документ отправляется, выполняется несколько задач, которые можно грубо сгруппировать по следующим категориям:

  1. MySQL запросов.
  2. Разбор содержимого HTML.
  3. Обновление индекса поиска.

Категория 1 включает обновления различных таблиц MySQL, относящихся к содержимому документа.

Категория 2 включает в себя анализ содержимого HTML, хранящегося в полях MySQL LONGTEXT, для выполнения некоторых автоматических преобразований тегов привязки. Я подозреваю, что в этой задаче проводится большое количество вычислений.

Категория 3 включает обновления простого индекса поиска на основе MySQL, используя только несколько полей, соответствующих документу.

Все эти задачи должны быть завершены для того, чтобы представление документа считалось завершенным.

Аппарат, на котором размещено это приложение, имеет два четырехъядерных процессора Xeon (всего 8 ядер). Однако всякий раз, когда документ отправляется, весь выполняемый PHP-код ограничивается одним процессом, работающим на одном из ядер.

Мой вопрос:

Какие схемы, если таковые имеются, вы использовали для разделения нагрузки на обработку веб-приложений PHP / MySQL между несколькими ядрами ЦП? Мое идеальное решение в основном порождало несколько процессов, позволяло им выполнять параллельно на нескольких ядрах, а затем блокировать до тех пор, пока все процессы не будут выполнены.

Связанный вопрос:

Каков ваш любимый инструмент профилирования производительности PHP?

PHP не совсем ориентирован на многопоточность: как вы уже заметили, каждая страница обслуживается одним процессом PHP – это делает одно за раз, в том числе просто «ожидание», пока SQL-запрос выполняется на сервере базы данных.

К сожалению, вы не можете этого сделать, к сожалению: так работает PHP.

Тем не менее, вот пара из них:

  • Прежде всего, у вас, вероятно, будет больше одного пользователя за раз на вашем сервере, что означает, что вы будете обслуживать сразу несколько страниц, что, в свою очередь, означает, что у вас будет несколько процессов PHP и SQL-запросов в то же время …, что означает использование нескольких ядер вашего сервера.
    • Каждый PHP-процесс будет работать на одном ядре в ответ на запрос одного пользователя, но существует несколько подпроцессов Apache, работающих параллельно (по одному для каждого запроса, до нескольких десятков или сотен, в зависимости от вашей конфигурации)
    • Сервер MySQL является многопоточным, что означает, что он может использовать несколько отдельных ядер для ответа на несколько одновременных запросов – даже если каждый запрос не может обслуживаться более чем одним ядром.

Таким образом, на самом деле, ваше основное ядро ​​сервера в конечном итоге будет использоваться 😉

И, если вы считаете, что ваши страницы слишком много времени для генерации, возможным решением является разделение ваших вычислений на две группы:

  • Одна рука, то, что нужно сделать для создания страницы: для тех, что вы не можете сделать
  • С другой стороны, вещи, которые нужно запускать иногда, но не обязательно сразу
    • Например, я думаю о некоторых статистических расчетах: вы хотите, чтобы они были достаточно современными, но если они отстают на пару минут, это вообще нормально.
    • То же самое для отправки по электронной почте: в любом случае, несколько минут будут проходить до того, как ваши пользователи получат / прочитают их почту, поэтому нет необходимости отправлять их немедленно.

Для подобных ситуаций в моем втором пункте, поскольку вам не нужны эти вещи, сделанные немедленно … Ну, просто не делайте их сразу 😉
Решением, которое я часто использую, является некоторый механизм массового обслуживания:

  • Веб-приложение хранит вещи в «списке дел»,
  • И этот «todo-list» отключается несколькими партиями, которые часто запускаются через cronjob

И для некоторых других манипуляций вы просто хотите, чтобы они запускались каждые X минут – и, здесь тоже, cronjob – идеальный инструмент.

Введение

PHP имеет полную поддержку Multi-Threading, которую вы можете использовать в полной мере во многих отношениях. Могут продемонстрировать эту способность Multi-Threading в разных примерах:

  • Как можно использовать многопоточность в PHP-приложениях
  • pcntl выполняет один и тот же код несколько раз, требуется помощь
  • Улучшение эффективности скребка HTML с помощью pcntl_fork ()

Быстрый поиск даст дополнительные ресурсы.

категории

1: MySQL-запросы

MySQL полностью многопоточен и будет использовать несколько процессоров при условии, что операционная система их поддерживает, а также максимизирует системные ресурсы, если они правильно настроены для производительности.

Типичная настройка в 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_threads и innodb_write_io_threads где по умолчанию 4 и его можно увеличить до 64 зависимости от аппаратного обеспечения

Другие:

Другие конфигурации, которые также можно посмотреть, включают key_buffer_size , key_buffer_size , table_open_cache и т. key_buffer_size , table_open_cache приводят к лучшей производительности

PHP:

В pure 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 

Вот полный рабочий пример 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 поддерживающим как окна, так и 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 8,714 файлов, 105,109 ссылок без потоков и посмотреть, сколько времени потребуется.

Лучшая архитектура

Нерест слишком много нитей, которые не являются умными в производстве. Лучше было бы использовать Pooling . Создайте пул Определите Рабочих, затем сверните с Task

Улучшение производительности

Прекрасно приведенный выше пример еще improved . Если вы ожидаете, что система сканирует all files in a single thread вы также можете use multiple threads scan my system для файлов, а затем складывать данные в Workers for processing

3: Обновление индекса поиска

Ответ на этот вопрос был в значительной степени удовлетворен первым ответом, но есть так много возможностей для улучшения производительности. Вы когда-нибудь рассматривали подход, основанный на событиях?

Представляем событие

@rdlowrey Цитата 1:

Хорошо подумай об этом так. Представьте, что вам нужно обслуживать 10 000 одновременно подключенных клиентов в вашем веб-приложении. Традиционные серверы « нить за запрос» или « процесс за каждый запрос» не являются вариантом, так как независимо от того, насколько легки ваши потоки, вы по-прежнему не можете удерживать 10 000 из них одновременно.

@rdlowrey Цитата 2:

С другой стороны, если вы сохраняете все сокеты в одном процессе и слушаете, чтобы эти сокеты стали читабельными или записываемыми, вы можете поместить весь ваш сервер в один цикл событий и работать с каждым сокетом только тогда, когда есть что читать или писать.

Почему бы вам не поэкспериментировать с event-driven , non-blocking I/O подходом non-blocking I/O к вашей проблеме. PHP имеет libevent для надстройки вашего приложения.

Я знаю, что этот вопрос является Multi-Threading но если у вас есть время, вы можете посмотреть этот ядерный реактор, написанный на PHP by @igorw

в заключение

рассмотрение

Я думаю, вы должны использовать конденсатор, используя Cache и задание для некоторых из ваших задач. Вы можете легко получить сообщение о том, что

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

Затем выполняйте всю работу, тратя время в фоновом режиме. Посмотрите, как сделать большую работу по обработке меньше для аналогичного примера.

Profilling

Профилирующий инструмент? Не существует единого профиля для веб-приложения от Xdebug до Yslow , все это очень полезно. Например. Xdebug не имеет полезности, когда дело доходит до потоков, потому что его не поддерживают

У меня нет любимой

Масштабирование веб-серверов не заставит MySQL сдвинуться на один дюйм, когда дело доходит до получения доступа к многоядерным процессорам. Зачем? Сначала рассмотрим два основных модуля хранения MySQL

MyISAM

Этот механизм хранения не имеет доступа к нескольким ядрам. Это никогда не было и никогда не будет. Он выполняет полную блокировку таблицы для каждого INSERT, UPDATE и DELETE. Отправка запросов с нескольких веб-серверов для чего-либо с помощью MyISAM просто становится узким местом.

InnoDB

До MySQL 5.1.38 этот механизм хранения имел доступ только к одному процессору. Вам приходилось делать странные вещи, например, запускать MySQL несколько раз на одной машине, чтобы принуждать ядра обрабатывать разные экземпляры MySQL . Затем настройте балансировку DB-соединений веб-серверов между несколькими экземплярами. Это старая школа (особенно если вы используете версии MySQL до MySQl 5.1.38).

Начиная с MySQL 5.1.38, вы устанавливаете новый плагин InnoDB. У него есть функции, которые вы должны настроить для получения InnoDB для доступа к нескольким процессорам. Я написал об этом в DBA StackExchange

  • Sep 20, 2011 : многоядерные процессоры и производительность MySQL
  • Sep 12, 2011 : Возможно ли заставить MySQL использовать более одного ядра?
  • May 26, 2011 О производительности однопоточных и многопоточных баз данных

Эти новые функции полностью доступны в MySQL 5.5 / 5.6 и Percona Server.

ПРЕДОСТЕРЕЖЕНИЕ

Если ваша пользовательская CMS использует индексирование / поиск FULLTEXT, вы должны перейти на MySQL 5.6, потому что InnoDB теперь поддерживает индексирование / поиск FULLTEXT.

Установка в MySQL 5.6 не будет автоматически заставлять процессоры работать. Вам нужно будет настроить его, потому что, LEFT UNONFIGURED, возможно, что более старые версии MySQL будут превосходить и перенаправлять новые версии:

  • Nov 24, 2011 : Почему mysql 5.5 медленнее, чем 5.1 (linux, используя mysqlslap)
  • Oct 05, 2011 : Query длится много времени в некоторых новых версиях MySQL
  • Jun 19, 2011 : Как правильно выполнить выпечку MySQL?

Это может быть не ответ на вопрос, который вы ищете, но решение, которое вы ищете, связано с потоковой обработкой. Threading необходим для многоядерного программирования, и потоки не реализованы в PHP.

Но, в некотором смысле, вы можете подделывать потоки в PHP, полагаясь на возможности многозадачности операционной системы. Я предлагаю дать краткий обзор стратегий многопоточности в PHP, чтобы разработать стратегию для достижения того, что вам нужно.

Мертвая ссылка: многопоточные стратегии в PHP