Подтвердить что ты не робот

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

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

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

  • Запросы MySQL.
  • Анализ HTML-содержимого.
  • Обновление индекса поиска.

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

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

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

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

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

Мой вопрос:

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

Похожие вопросы:

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

4b9b3361

Ответ 1

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

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


Тем не менее, здесь пара мыслей:

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

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


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

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

Что касается ситуаций в моем втором пункте, так как вам не нужно, чтобы эти вещи выполнялись немедленно... Ну, просто не делайте их немедленно ;-)
Решение, которое я часто использую, - это механизм очередей:

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

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

Ответ 2

Введение

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

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

Категории

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, table_open_cache, sort_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

Вот полный рабочий пример SQLWorker

2. Разбор HTML-контента

Я подозреваю, что на выполнение этой задачи уходит много времени на вычисления.

Если вы уже знаете проблему, ее легче решить с помощью циклов событий, очереди заданий или потоков.

Работа над одним документом по одному может быть очень, очень медленным и болезненным процессом. @ka однажды взломав свой выход с помощью ajax для вызова нескольких запросов, некоторые творческие умы просто разветвляют процесс, используя pcntl_fork, но если вы используете windows, то вы не можете воспользоваться из pcntl

С pThreads, поддерживающим как Windows, так и Unix-системы, у вас нет таких ограничений. Это так же просто, как... Если вам нужно разобрать 100 документов? 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 без потоков, и посмотрите, сколько времени это займет.

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

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

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

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

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

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

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

@rdlowrey Цитата 1:

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

@rdlowrey Цитата 2:

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

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

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

. Наконец

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

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

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

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

Профилирование

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

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

Ответ 3

Масштабирование веб-серверов не заставит 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

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

CAVEAT

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

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

Ответ 4

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

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

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

Ответ 5

Просто сообщите вам, ребята, когда вы думаете: "плохой PHP не поддерживает многопоточность"

Ну... Python также не имеет реальной многопоточности. Также NodeJS не поддерживает многопоточность. Java имеет своего рода многопоточность, но даже там, какой-то код останавливает всю машину на этапе.

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