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

Я поддерживаю специально созданное приложение, подобное CMS.

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

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

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

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

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

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

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

Мой вопрос:

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

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

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


person jkndrkn    schedule 15.02.2010    source источник
comment
Вы интересовались многопоточностью?   -  person Anthony Forloney    schedule 15.02.2010
comment
Привет, Энтони. Я готов использовать любую технику, которая могла бы мне помочь в этом случае.   -  person jkndrkn    schedule 15.02.2010
comment
BOUNTY EDIT: я имел в виду PHP 5.5;)   -  person Sliq    schedule 29.05.2013
comment
Для всех, кто посмотрел на это постфактум, я тоже нашел это полезным: stackoverflow.com/questions/70855/   -  person Pseudonym    schedule 05.02.2015


Ответы (5)


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

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


Тем не менее, вот пара мыслей:

  • First of all, you'll probably have more that 1 user at a time on your server, which means you'll serve several pages at the same time, which, in turn, means you'll have several PHP processes and SQL queries running at the same time... which means several cores of your server will be used.
    • Each PHP process will run on one core, in response to the request of one user, but there are several sub-processes of Apache running in parallel (one for each request, up to a couple of dozens or hundreds, depending on your configuration)
    • Сервер MySQL является многопоточным, что означает, что он может использовать несколько отдельных ядер для ответа на несколько одновременных запросов, даже если каждый запрос не может обслуживаться более чем одним ядром.

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


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

  • С одной стороны, то, что нужно сделать для создания страницы: для них мало что можно сделать.
  • On the other hand, the things that have to be run sometimes, but not necessarily immediately
    • For instance, I am think about some statistics calculations : you want them to be quite up to date, but if they lag a couple of minutes behind, that's generally quite OK.
    • То же самое и с отправкой электронной почты: в любом случае пройдет несколько минут, прежде чем ваши пользователи получат / прочитают свою почту, поэтому нет необходимости отправлять их немедленно.

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

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

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

person Pascal MARTIN    schedule 15.02.2010
comment
Мне нравится идея механизма очередей. Как вы реализовали это в PHP? - person jkndrkn; 15.02.2010
comment
Самая простая идея, которая приходит в голову, - использовать таблицу в вашей базе данных, вставлять ее из веб-приложения (с каким-то столбцом временных меток) и выбирать и удалять самые старые строки из пакетного запуска с помощью cronjob ;;; другие решения будут использовать специализированные механизмы (см. framework.zend.com/manual/en/ zend.queue.html, например, или gearman.org) - person Pascal MARTIN; 15.02.2010
comment
Спасибо за мысли и советы. - person jkndrkn; 16.02.2010
comment
Мне понравился ответ, и я просто хотел добавить, что большинство мультизадач обрабатываются самой операционной системой, поэтому нам не нужно об этом беспокоиться. - person Airy; 31.05.2014
comment
@jkndrkn: Отличный вопрос и ответ. Я новичок в php и работаю с Threading для вставки строк в таблицу. много искали потоки в PHP, но обнаружили, что PHP не является потокобезопасным, и у меня возник вопрос к вам обоим - Безопасно ли использовать CRON Script для реализации такой среды потоков? < / b> - person HP's 411; 23.09.2015

Введение

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

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

Категории

1: MySQL-запросы

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

Типичный параметр my.ini, влияющий на производительность потока:

thread_cache_size = 8

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

Если вы используете Solaris, тогда вы можно использовать

thread_concurrency = 8 

thread_concurrency позволяет приложениям предоставлять потоки system подсказка о желаемом количестве потоков, которые должны выполняться одновременно.

Эта переменная устарела в 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 документов? Создайте 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');
        }
    }
}

Эксперимент

Пробуем разобрать 8,714 файлы со ссылками 105,109 без потоков и посмотреть, сколько времени это займет.

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

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

Повышение производительности

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

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 вопрос, но если у вас есть время, вы можете посмотреть этот Nuclear Reactor, написанный на PHP, @igorw

Ну наконец то

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

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

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

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

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

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

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

person Baba    schedule 29.05.2013
comment
ПРЕДОСТЕРЕЖЕНИЕ: thread_concurrency применяется только к Solaris (dev. mysql.com/doc/refman/5.6/en/). Вам нужен innodb_thread_concurrency (только если все данные InnoDB). - person RolandoMySQLDBA; 31.05.2013
comment
pThreads устарел автором. Используйте его преемника - Parallel. - person T.Todua; 28.06.2021

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

MyISAM

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

InnoDB

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

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

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

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

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

Установка MySQL 5.6 не приведет к автоматическому запуску процессоров. Вам придется настроить его, потому что, СЛЕВА НЕ НАСТРОЙКА, старые версии MySQL могут опередить и превзойти более новые версии:

person RolandoMySQLDBA    schedule 30.05.2013

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

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

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

person Anthony Forloney    schedule 15.02.2010
comment
@mins - Как насчет saidov.net/3-multi- threading-strategy-in-php.html? - person Anthony Forloney; 25.03.2015
comment
Этот, если очень живой :-) - person mins; 26.03.2015
comment
@mins Я обновил свой ответ, включив ссылку, которая работает. Прошло некоторое время с тех пор, как я проверил теперь мертвую ссылку, поэтому мне не удалось сравнить новую ссылку на идентичный контент, но, надеюсь, она будет полезна другим. - person Anthony Forloney; 26.03.2015

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

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

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

person Toskan    schedule 30.01.2019