Comment faire une bonne utilisation des processeurs multicore dans vos applications PHP/MySQL?

je maintiens une application de type CMS sur mesure.

chaque fois qu'un document est soumis, plusieurs tâches sont effectuées qui peuvent être grossièrement regroupées dans les catégories suivantes:

  1. des requêtes MySQL.
  2. contenu HTML de l'analyse.
  3. mise à jour de L'index de recherche.

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

Catégorie 2 comprend l'analyse de contenu HTML stocké dans des champs LONGTEXT MySQL pour effectuer certaines transformations automatiques d'étiquettes d'ancrage. Je soupçonne qu'une grande partie du temps de calcul est consacrée à cette tâche.

Catégorie 3 comprend les mises à jour d'un simple index de recherche basé sur MySQL en utilisant seulement une poignée de champs correspondant au document.

toutes ces tâches doivent être accomplies pour que la soumission du document soit considérée compléter.

la machine qui héberge cette application dispose de deux processeurs quad-core Xeon (un total de 8 cœurs). Cependant, chaque fois qu'un document est soumis, tout le code PHP qui s'exécute est limité à un seul processus tournant sur l'un des noyaux.

ma question:

quels schémas, le cas échéant, avez-vous utilisés pour partager votre charge de traitement D'application web PHP/MySQL entre plusieurs cœurs CPU? Ma solution idéale donnerait en gros quelques les processus, les laisser exécuter en parallèle sur plusieurs cœurs, puis bloquer jusqu'à ce que tous les processus sont fait.

question connexe:

Quel Est votre outil de profilage PHP préféré?

27
demandé sur jkndrkn 2010-02-15 19:30:44

4 réponses

PHP n'est pas tout à fait orienté vers le multi-threading : comme vous l'avez déjà remarqué, chaque page est servie par un processus PHP -- Cela fait une chose à la fois, y compris juste "attente" alors qu'une requête SQL est exécutée sur le serveur de base de données.

il n'y a pas grand chose que vous puissiez faire à ce sujet, malheureusement : C'est la façon dont PHP fonctionne.



Pourtant, voici quelques idées:

  • tout D'abord, vous aurez probablement plus d'un utilisateur à la fois sur votre serveur, ce qui signifie que vous servirez plusieurs pages à la fois, ce qui, à son tour, signifie que vous aurez plusieurs processus PHP et des requêtes SQL tournant en même temps... ce qui signifie que plusieurs noyaux de votre serveur seront utilisés.
    • chaque processus PHP s'exécute sur un noyau, en réponse à la demande d'un utilisateur, mais il existe plusieurs sous-processus D'Apache tournant en parallèle (un pour chaque requête, jusqu'à un quelques dizaines ou centaines, selon votre configuration)
    • le serveur MySQL est multi-threadé, ce qui signifie qu'il peut utiliser plusieurs noyaux distincts pour répondre à plusieurs requêtes simultanées -- même si chaque requête ne peut pas être servie par Plus d'un noyau.

donc, en fait, le 8 core de votre serveur va finir par être utilisé ; -)



Et, si vous pensez que votre les pages sont trop longues à générer, une solution possible est de séparer vos calculs en deux groupes:

  • D'une part, les choses qui doivent être faites pour générer la page : pour ceux-là, il n'y a pas beaucoup que vous pouvez faire
  • , d'autre part, les choses qui doivent être exécutées parfois, mais pas nécessairement tout de suite
    • par exemple, je pense à certains calculs statistiques : vous voulez qu'ils soient tout à fait à la hauteur de date, mais s'ils sont en retard de quelques minutes, c'est généralement assez bien.
    • même chose pour l'envoi d'e-mail : de toute façon, plusieurs minutes passeront avant que vos utilisateurs reçoivent/lisent leur courrier, il n'est donc pas nécessaire de les envoyer immédiatement.

pour le genre de situations dans mon deuxième point, car vous n'avez pas besoin de faire ces choses immédiatement... Eh bien, il suffit de ne pas les faire tout de suite ;-)

une solution que j'utilise souvent est un certain mecanisme de file d'attente :

  • L'application web de stocker des choses dans une "todo-list"
  • et que "todo-list" est désactivé par certains lots qui sont exécutés fréquemment via un cronjob

et pour d'autres manipulations, vous voulez juste qu'ils courent toutes les X minutes -- et, ici aussi, une cronjob est l'outil parfait.

30
répondu Pascal MARTIN 2010-02-15 17:15:30

Introduction

PHP a plein Multi-Threading support dont vous pouvez tirer pleinement avantage de tant de façons. Ont été en mesure de démontrer cette capacité multi-Threading dans différents exemples:

Un Recherche rapide serait de donner des ressources supplémentaires.

catégories

1: requêtes MySQL

MySQL est entièrement multi-threadé et fera usage de plusieurs CPU, à condition que le système d'exploitation les supporte, il serait également maximiser les ressources du système s'il est correctement configuré pour le rendement.

un réglage typique dans le my.ini qui affecte la performance du fil est:

thread_cache_size = 8

thread_cache_size peut être augmenté afin d'améliorer les performances si vous avez beaucoup de nouvelles connexions. Normalement, cela ne fournit pas une amélioration notable de la performance si vous avez une bonne implémentation du thread. Toutefois, si votre serveur voit des centaines de connexions par seconde vous devriez normalement définir thread_cache_size suffisamment haut de sorte que la plupart des nouvelles connexions utilisent des threads en cache

si vous utilisez Solaris alors vous pouvez utiliser

thread_concurrency = 8 

thread_concurrency permet aux applications de donner au système de threads un indice sur le nombre désiré de threads qui doivent être exécutés en même temps.

cette variable est obsolète à partir de MySQL 5.6.1 et est supprimé dans MySQL 5.7. Vous devriez supprimer cela des fichiers de configuration MySQL chaque fois que vous le voyez à moins qu'ils ne soient Pour Solaris 8 ou plus tôt.

InnoDB: :

vous n'avez pas de telles limitations si vous utilisez Innodb a le moteur de stockage parce qu'il prend en charge la pleine concurrence de thread

innodb_thread_concurrency //  Recommended 2 * CPUs + number of disks

, Vous pouvez aussi regarder innodb_read_io_threads et innodb_write_io_threads où la valeur par défaut est 4 et il peut être augmenté à 64 en fonction du matériel

autres:

les autres configurations à prendre en compte sont: key_buffer_size , table_open_cache , sort_buffer_size etc. qui se traduisent par de meilleures performances

PHP:

Dans pure PHP vous peut créer un Worker MySQL où chaque requête est exécutée dans des threads PHP séparés

$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 complet de travail de SQLWorker

2: Analyse de contenu HTML

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

si vous connaissez déjà le problème , alors il est plus facile de le résoudre via des boucles d'événements, des files d'attente de travail ou en utilisant des Threads.

Travailler sur un document, un à un le temps peut être un very very slow painful process . @ka une fois piraté son chemin en utilisant ajax à l'appel à la demande multiple, certains esprits créatifs serait juste bifurquer le processus en utilisant pcntl_fork mais si vous utilisez windows alors vous ne pouvez pas profiter de pcntl

avec pThreads supportant les systèmes windows et Unix, vous n'avez pas une telle limitation. Est aussi simple que .. Si vous devez analyser 100 document ? Spawn 100 Threads ... Simple

la Numérisation 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);

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

en Essayant d'analyse 8,714 les fichiers qui ont des liens 105,109 sans threads et voir combien de temps cela prendrait.

Meilleure Architecture

la fraie trop de fils qui n'est pas une chose intelligente à faire dans la production. Une meilleure approche serait d'utiliser Pooling . Avoir une réserve de définir travailleurs puis cheminée avec un Task

Amélioration De La Performance

amende l'exemple ci-dessus est toujours improved . Insted de l'attente pour le système de scanner all files in a single thread vous pouvez ainsi use multiple threads scan my system pour les fichiers puis empiler les données aux travailleurs pour le traitement

3: mise à jour de L'index de recherche

la première réponse a répondu à cette question, mais il y a tellement de façons d'améliorer le rendement. Avez-vous déjà envisagé Approche fondée sur les événements ?

Présentation De L'Événement

@rdlowrey citation 1:

pensez-y comme ça. Imaginez que vous devez servir 10.000 clients connectés simultanément dans votre application web. Traditionnel thread-per-request ou process-per-request les serveurs ne sont pas une option parce que peu importe la légèreté de vos threads tu ne peux toujours pas en garder 10 000 ouverts à la fois.

@rdlowrey citation 2:

d'un autre côté, si vous gardez toutes les sockets dans un processus unique et écoutez pour que ces sockets deviennent lisibles ou inscriptibles, vous pouvez mettre votre serveur entier dans une boucle d'événement unique et opérer sur chaque socket seulement quand il y a quelque chose à lire/écrire.

Pourquoi ne pas expérimenter avec event-driven , non-blocking I/O approche de votre problème. PHP a libevent pour suralimenter votre application.

je sais que cette question est tout Multi-Threading mais si vous avez du temps, vous pouvez regarder ce réacteur nucléaire écrit en PHP par @igorw

enfin

considération

I pensez 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   

alors faire tout le temps de perdre la tâche dans l'arrière-plan. Veuillez consulter pour une étude de cas similaire.

Profilling

Outil De Profilage ?? Il n'y a pas d'outil de profil unique pour une application web de Xdebug à Yslow sont tous très utiles. Par exemple. Xdebug n'a pas d'utilité quand il s'agit de threads parce que son pas pris en charge

Je n'ai pas de favori

53
répondu Baba 2017-05-23 11:47:00

la mise à L'échelle des serveurs Web ne va pas faire bouger MySQL d'un pouce quand il s'agit d'accéder aux CPU multicores. Pourquoi? Tout d'abord considérer les deux principaux moteurs de stockage de MySQL

MyISAM

ce moteur de stockage n'a pas accès à plusieurs noyaux. Il n'a jamais et ne sera jamais. Il assure le verrouillage complet de la table pour chaque insertion, mise à jour et suppression. Envoyer des requêtes à partir de plusieurs serveurs Web pour faire quoi que ce soit avec un MyISAM obtient juste un goulot d'étranglement.

InnoDB

avant MySQL 5.1.38, ce moteur de stockage n'a accédé qu'à un seul CPU. Vous avez dû faire des choses étranges comme exécuter MySQL plusieurs fois sur une machine pour forcer les cœurs pour gérer les différentes instances de MySQL . Ensuite, la charge de connexions DB des serveurs Web doit être équilibrée entre les multiples instances. C'est de la vieille école (surtout si vous utilisez des versions de MySQL avant MySQl 5.1.38).

à partir de MySQL 5.1.38, vous installez le nouveau Plugin InnoDB. Il a des fonctionnalités que vous devez régler pour obtenir InnoDB pour accéder à plusieurs CPU. J'ai écrit à ce sujet dans le DBA StackExchange

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

CAVEAT

si votre CMS personnalisé utilise l'indexation/la recherche en texte intégral, vous devriez passer à MySQL 5.6 parce que InnoDB prend maintenant en charge l'indexation/la recherche en texte intégral.

installation à MySQL 5.6 ne va pas automatiquement la Cpu. Vous devrez l'accorder car, si vous ne le faites pas, il est possible que les versions plus anciennes de MySQL soient plus rapides que les versions plus récentes:

2
répondu RolandoMySQLDBA 2017-04-13 12:42:38

ce n'est peut-être pas une réponse à la question que vous cherchez, mais la solution que vous cherchez traite du filetage. Le Threading est nécessaire pour la programmation multicore, et le threading est et non implémenté en PHP.

mais, dans un sens, vous pourriez simuler un threading en PHP en comptant sur les capacités multitâches du système d'exploitation. Je suggère donné un aperçu rapide de stratégies multi-threading en PHP à élaborer une stratégie pour atteindre ce dont vous avez besoin.

lien mort: Multi-threading strategies in PHP

2
répondu Anthony Forloney 2015-03-25 20:55:46