Maison >Opération et maintenance >Sécurité >Comment analyser le code source de Gunicorn Arbiter
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 :
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 'fork error'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)
(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!