web-dev-qa-db-fra.com

Comment utilisez-vous correctement les processeurs multicœurs dans vos applications PHP/MySQL?

Je maintiens une application personnalisée de type CMS.

Chaque fois qu'un document est soumis, plusieurs tâches sont exécutées et peuvent être regroupées approximativement dans les catégories suivantes:

  1. Requêtes MySQL.
  2. Analyse du contenu HTML.
  3. Mise à jour de l'index de recherche.

La catégorie 1 inclut les mises à jour de diverses tables MySQL relatives au contenu d'un document.

La catégorie 2 inclut l'analyse du contenu HTML stocké dans les champs MySQL LONGTEXT afin d'effectuer certaines transformations automatiques des balises d'ancrage. Je suppose que cette tâche nécessite beaucoup de temps de calcul.

La catégorie 3 inclut les mises à jour d'un index de recherche simple basé sur MySQL en utilisant juste une poignée de champs correspondant au document.

Toutes ces tâches doivent être terminées pour que la soumission du document soit considérée comme terminée.

La machine qui héberge cette application dispose de deux processeurs Xeon à quatre cœurs (un total de 8 cœurs). Cependant, chaque fois qu'un document est soumis, tout le code PHP qui s'exécute est contraint à un seul processus s'exécutant sur l'un des cœurs.

Ma question: 

Quels schémas, le cas échéant, avez-vous utilisé pour répartir la charge de traitement de vos applications Web PHP/MySQL entre plusieurs cœurs de processeur? Ma solution idéale créerait essentiellement quelques processus, les laisserait s'exécuter en parallèle sur plusieurs cœurs, puis les bloquer jusqu'à ce que tous les processus soient terminés.

Question connexe:

Quel est votre outil de profilage de performances PHP préféré? 

29
jkndrkn

Introduction

PHP a un support complet Multi-Threading / dont vous pouvez tirer pleinement parti à bien des égards. Ont pu démontrer cette capacité multi-threading dans différents exemples:

A recherche rapide donnerait des ressources supplémentaires.

Les catégories

1: requêtes MySQL

MySQL est entièrement multithread et utilisera plusieurs processeurs, à condition que le système d’exploitation les prenne en charge. Il optimisera également les ressources du système s’il est correctement configuré pour les performances. 

Un paramètre typique du my.ini qui affecte les performances du thread est le suivant: 

thread_cache_size = 8

thread_cache_size peut être augmenté pour améliorer les performances si vous avez beaucoup de nouvelles connexions. Normalement, cela n'apporte pas une amélioration notable des performances si vous avez une bonne implémentation de thread. Cependant, si votre serveur détecte des centaines de connexions par seconde, vous devez normalement définir thread_cache_size suffisamment haut pour que la plupart des nouvelles connexions utilisent des threads mis en cache.

Si vous utilisez Solaris , vous pouvez utiliser 

thread_concurrency = 8 

thread_concurrency permet aux applications de donner au système de threads un indice sur le nombre de threads à exécuter simultanément. 

Cette variable est obsolète à partir de MySQL 5.6.1 et est supprimée dans MySQL 5.7. Vous devez le supprimer des fichiers de configuration MySQL chaque fois que vous le voyez, à moins qu'il ne s'agisse de Solaris 8 ou d'une version antérieure. 

InnoDB:

Vous n'avez pas de telles limitations si vous utilisez Innodb a le moteur de stockage, car il prend en charge la simultanéité des threads. 

innodb_thread_concurrency //  Recommended 2 * CPUs + number of disks

Vous pouvez également consulter innodb_read_io_threads et innodb_write_io_threads où la valeur par défaut est 4 et il peut être augmenté jusqu'à atteindre 64 en fonction du matériel.

Autres: 

Les autres configurations à prendre en compte incluent également key_buffer_size, table_open_cache, sort_buffer_size, etc., qui entraînent toutes de meilleures performances.

PHP: 

Dans pure PHP, vous pouvez créer MySQL Worker où chaque requête est exécutée dans des threads distincts 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

Voici un exemple de travail complet de SQLWorker

2: analyse du contenu HTML

I suspect that a great deal of computation time is spent in this task. 

Si vous connaissez déjà le problème, cela facilite la résolution via les boucles d'événements, la file d'attente des tâches ou l'utilisation de threads.

Travailler sur un seul document à la fois peut être un very very slow painful process. @ka une fois piraté en utilisant ajax pour appeler plusieurs requêtes, certains esprits créatifs lanceraient simplement le processus en utilisant pcntl_fork mais si vous utilisez windows, vous ne pourrez pas profiter de pcntl

Avec pThreads prenant en charge les systèmes Windows et Unix, vous n’avez pas cette limitation. Est aussi simple que .. Si vous devez analyser 100 document? Spawn 100 Threads ... Simple 

_ {{HTML Scanning)} _

// 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);

Sortie 

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

Classe utilisée 

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

_ {Expérience 

Essayez d’analyser les fichiers 8,714 qui ont des liens 105,109 sans thread et voyez combien de temps cela prendrait.

Meilleure architecture 

Générer trop de threads, ce qui n’est pas astucieux en production. Une meilleure approche serait d’utiliser Pooling . Avoir un pool de définir travailleurs alors pile avec un Task

Amélioration des performances 

Bien que l'exemple ci-dessus soit toujours improved. Institué d'attendre que le système analyse all files in a single thread, vous pouvez également use multiple threads scan my system pour les fichiers, puis empiler les données sur Workers pour traitement.

3: Mise à jour de l'index de recherche

La première réponse a bien répondu à cette question, mais il existe de nombreuses façons d'améliorer les performances. Avez-vous déjà envisagé une approche basée sur les événements? 

Présentation de l'événement

@rdlowrey Citation 1: 

Pensez-y comme ça. Imaginez que vous deviez servir 10 000 clients connectés simultanément dans votre application Web. Les serveurs traditionnels thread-per-request _ ou process-per-request ne sont pas une option, car, quel que soit le poids de vos threads, vous ne pouvez toujours pas en ouvrir 10 000 à la fois. temps.

@rdlowrey Citation 2:

D'un autre côté, si vous conservez toutes les sockets dans un seul processus et que vous souhaitez que ces sockets deviennent lisibles ou inscriptibles, vous pouvez placer votre serveur entier dans une boucle d'événement unique et opérer sur chaque socket uniquement lorsqu'il y a quelque chose à lire/écrire.

Pourquoi n'essayez-vous pas avec l'approche event-driven, non-blocking I/O à votre problème. PHP a libevent pour surcharger votre application. 

Je sais que cette question est tout Multi-Threading mais si vous avez un peu de temps, vous pouvez regarder ceci Réacteur nucléaire écrit en PHP par @igorw

Finalement

Considération

Je pense que vous devriez condenser en utilisant Cache et Job Queue pour une partie de votre tâche. Vous pouvez facilement avoir un message disant

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

Ensuite, faites tout votre travail en arrière-plan. Veuillez regarder Réduire un grand travail de traitement pour une étude de cas similaire.Profitant.

Outil de profilage ?? Il n’existe pas d’outil de profil unique pour une application Web de Xdebug à Yslow sont très utiles. Par exemple. Xdebug n'a pas d'utilité en matière de threads car il n'est pas supporté

_ {Je n'ai pas de favori} _

I don't have a favorite

54
Baba

La mise à l'échelle des serveurs Web ne va pas faire basculer MySQL pour accéder aux processeurs multicœurs. Pourquoi? Considérons d’abord les deux principaux moteurs de stockage de MySQL

MyISAM

Ce moteur de stockage n'accède pas à plusieurs cœurs. Cela n'a jamais et ne le sera jamais. Il effectue le verrouillage complet de la table pour chaque INSERT, UPDATE et DELETE. L'envoi de requêtes provenant de plusieurs serveurs Web pour faire quoi que ce soit avec un MyISAM est simplement goulot d'étranglement. 

InnoDB

Avant MySQL 5.1.38, ce moteur de stockage n'avait accès qu'à un seul processeur. Vous deviez faire des choses étranges telles que exécuter MySQL plusieurs fois sur une même machine pour contraindre les cœurs à gérer différentes instances de MySQL . Ensuite, chargez les connexions des bases de données des serveurs Web entre les différentes instances. C'est de la vieille école (surtout si vous utilisez des versions de MySQL antérieures à MySQL 5.1.38).

À partir de MySQL 5.1.38, vous installez le nouveau plugin InnoDB. Il possède des fonctionnalités que vous devez régler pour que InnoDB puisse accéder à plusieurs processeurs. J'ai écrit à ce sujet dans DBA StackExchange

Ces nouvelles fonctionnalités sont également entièrement disponibles dans MySQL 5.5/5.6 et Percona Server.

CAVEAT

Si votre CMS personnalisé utilise l'indexation/la recherche FULLTEXT, vous devez passer à MySQL 5.6 car InnoDB prend désormais en charge l'indexation/la recherche FULLTEXT.

L'installation sur MySQL 5.6 ne va pas automatiquement mettre les processeurs en marche. Vous devrez l’ajuster car, LEFT UNCONFIGURED, il est possible que les anciennes versions de MySQL dépassent et dépassent les nouvelles versions:

3
RolandoMySQLDBA

Ce n'est peut-être pas une réponse à la question que vous recherchez, mais la solution que vous recherchez concerne le threading. Le threading est nécessaire pour la programmation multicœur, et le threading est non implémenté en PHP.

Mais, dans un sens, vous pouvez simuler le threading dans PHP en vous fiant aux capacités multitâches du système d'exploitation. Je suggère de donner un aperçu rapide de Stratégies multi-threading dans PHP pour développer une stratégie vous permettant d'atteindre vos objectifs. 

Lien mort: Stratégies multi-threading dans PHP

2
Anthony Forloney

juste pour vous laisser savoir quand vous pensez: "un php pauvre n'a pas de multi-threading"

bien ... python n'a pas non plus de vrai multi threading . Les nœuds nodejs ne prennent pas non plus en charge le multi-threading . Java a une sorte de multi-threading, mais même là, du code bloque toute la machine .

mais: à moins que vous ne programmiez beaucoup d'une seule chose, cela n'a aucune importance. De nombreuses requêtes ont atteint votre page et tous vos cœurs seront néanmoins utilisés car chaque requête génère son propre processus avec son propre thread.

0
Toskan