Maison  >  Article  >  Opération et maintenance  >  Comment analyser le code source de Gunicorn Arbiter

Comment analyser le code source de Gunicorn Arbiter

PHPz
PHPzavant
2023-05-12 16:28:181378parcourir

Comme mentionné précédemment, Arbiter est au cœur du processus gunicorn master. Arbiter est principalement responsable de la gestion des processus de travail, y compris le démarrage, la surveillance et la suppression des processus de travail. Dans le même temps, Arbiter peut également mettre à jour (recharger) à chaud les applications d'application ou mettre à niveau Gunicorn en ligne lorsque certains signaux se produisent. Le code de base d'Arbiter est dans un seul fichier et la quantité de code n'est pas importante. Le code source est ici : https://github.com/benoitc/gunicorn.

Arbiter dispose principalement des méthodes suivantes :

setup :

Traitement des éléments de configuration, le plus important est le nombre de travailleurs et le modèle de travail du travailleur

init_signal :

Enregistrer la fonction de traitement du signal

handle_xxx :

Les fonctions de traitement spécifiques de chaque signal

kill_worker, kill_workers:

Envoyer un signal au processus de travail

spawn_worker, spawn_workers:

Démarrez un nouveau processus de travail

murder_workers:

Tuer dans un certain laps de temps Processus de travail qui ne répond pas

manage_workers :

En fonction du nombre de travailleurs dans le fichier de configuration et du nombre de travailleurs actuellement actifs, décidez s'il faut bifurquer ou tuer le processus de travail

reexec :

Signal reçu appel SIGUSR2, mise à niveau en ligne gunicorn

reload:

Après avoir reçu le signal appel SIGHUP, le processus de travail sera démarré en fonction de la nouvelle configuration et le processus de travail précédent sera tué

sleep :

Lorsqu'il n'y a pas de traitement du signal, utilisez la sélection du délai d'attente pour dormir et peut être réveillé

wakeup :

En écrivant des messages dans le tuyau, réveillez le processus

run :

Boucle principale

La seule fonction Arbiter qui est réellement appelée par d'autres codes (Application) est __init_ _ et exécute les méthodes, en une phrase de code :

Arbiter(self).run()

  Le self dans le code ci-dessus est l'instance d'application. , dans lequel __init__ appelle setup pour définir les éléments de configuration. Voici le pseudo code de la méthode run


def run()
    self.init_signal()
    self.LISTENERS = create_sockets(self.cfg, self.log)
    self.manage_workers()    while True:        if no signal in SIG_QUEUE
            self.sleep()        else:
            handle_signal()


À propos du sous-processus fork

Le code du sous-processus fork est dans spawn_worker, le code source est le suivant :

如何进行gunicorn Arbiter 源码解析 Arbiter.spawn_worker

Processus principal :

(1) Charger work_class et instancier (la valeur par défaut est le modèle synchrone SyncWorker)

(2) Le processus parent (processus maître) revient après le fork et toute la logique ultérieure est exécutée dans l'enfant process

(3) Appelez worker.init_process pour entrer dans la boucle, nouveau canal Tout le travail de la formation IELTS est dans cette boucle

(4) Une fois la boucle terminée, appelez sys.exit(0)

(5) Enfin , enfin, enregistrez la sortie du processus de travail

Ce qui suit est moi-même. J'ai écrit un petit code pour simplifier le processus principal du fork


 1 # prefork.py 2 import sys 3 import socket 4 import select 5 import os 6 import time 7   8 def do_sub_process(): 9     pid = os.fork()10     if pid < 0:11         print &#39;fork error&#39;12         sys.exit(-1)13     elif pid > 0:14         print 'fork sub process %d'  % pid15         return16  17     # must be child process18     time.sleep(1)19     print 'sub process will exit', os.getpid(), os.getppid()20     sys.exit(0)21  22 def main():23     sub_num = 224     for i in range(sub_num):25         do_sub_process()26     time.sleep(10)27     print 'main process will exit', os.getpid()28  29 if __name__ == '__main__':30     main()


Sortie dans l'environnement de test :

 fork sub. processus 9601

 fork sous-processus 9602

 le sous-processus quittera 9601 9 600

le sous-processus quittera 9602 9600

Le processus principal quittera 9600

Il convient de noter que la ligne 20 appelle sys.exit assurer la fin du sous-processus, sinon la boucle for dans la fonction principale et la logique ultérieure continueront. Commentez la ligne 19 et réexécutez, et vous comprendrez en regardant le résultat.

À propos de la suppression des processus enfants

 Il est très simple pour le processus maître de tuer le processus de travail, il suffit d'envoyer un signal directement, le code source est le suivant :


 1     def kill_worker(self, pid, sig): 2         """\ 3         Kill a worker 4  5         :attr pid: int, worker pid 6         :attr sig: `signal.SIG*` value 7          """ 8         try: 9             os.kill(pid, sig)10         except OSError as e:11             if e.errno == errno.ESRCH:12                 try:13                     worker = self.WORKERS.pop(pid)14                     worker.tmp.close()15                     self.cfg.worker_exit(self, worker)16                     return17                 except (KeyError, OSError):18                     return19             raise


À propos du sommeil et wakeup

Jetons un coup d'œil au sommeil et au réveil d'Arbiter. L'arbitre "se mettra en veille" lorsqu'il n'y aura pas de signal à traiter. Bien sûr, il n'appellera pas réellement time.sleep, sinon le signal ne sera pas traité immédiatement lorsqu'il arrivera. L'implémentation ici est plus intelligente, utilisant des tuyaux et un délai d'attente sélectionné. Il suffit de regarder le code et vous saurez


        def sleep(self):        """\
        Sleep until PIPE is readable or we timeout.
        A readable PIPE means a signal occurred.        """
            ready = select.select([self.PIPE[0]], [], [], 1.0) # self.PIPE = os.pipe()
            if not ready[0]: 
                return
            while os.read(self.PIPE[0], 1):                pass

Les commentaires dans le code sont très clairs, soit PIPE est lisible et revient immédiatement, soit les délais d'attente expirent. Le tuyau est lisible car un signal se produit. Voici un aperçu de la fonction pipe

  •  os.pipe()  os.pipe()

  • Create a pipe. Return a pair of file descriptors (r,w)

Créez une paire de descripteurs de fichiers (r, w) utilisable respectivement en lecture et en écriture.

Ensuite, regardons quand le tube est lisible : il doit s'agir de quelque chose d'écrit dans le tube. C'est la fonction de la fonction de réveil

        def wakeup(self):            """
            Wake up the arbiter by writing to the PIPE            """
            os.write(self.PIPE[1], b'.')
.

Enfin, l'Arbiter est attaché Traitement du signal

 :

Sortie, INT : Arrêt rapide 🎜🎜TERM : Arrêt progressif. Attend que le travailleur termine sa demande en cours jusqu'à ce que son délai expire. 🎜

HUP : rechargez la configuration, démarrez de nouveaux processus de travail avec une nouvelle configuration et arrêtez progressivement les anciens processus de travail. Gunicorn chargera également la nouvelle version si l'application n'est pas préchargée (en utilisant l'option --preload).

TTIN : Augmenter le nombre de processus d'un

TTOU : Diminuer le nombre de processus d'un

USR1 : Rouvrir le fichier journal

USR2 : Mettre à niveau Gunicorn à la volée. Un signal de terme distinct doit être utilisé pour terminer l'ancien processus. Ce signal peut également être utilisé pour utiliser de nouvelles versions préchargées d'applications.

Winch : arrêtez gracieusement les processus de travail lorsque Gunicorn est démonisé.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer