2014-09-11 14 views
8

Ho bisogno di un server Python TCP in grado di gestire almeno decine di migliaia di connessioni socket simultanee. Stavo cercando di testare le funzionalità del pacchetto Python SocketServer in entrambe le modalità multiprocessore e multithread, ma entrambe erano lontane dalle prestazioni desiderate.Python socket stress simultity

In un primo momento, descriverò il client, perché è comune per entrambi i casi.

client.py server di

import socket 
import sys 
import threading 
import time 


SOCKET_AMOUNT = 10000 
HOST, PORT = "localhost", 9999 
data = " ".join(sys.argv[1:]) 


def client(ip, port, message): 
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    sock.connect((ip, port)) 
    while 1: 
     sock.sendall(message) 
     time.sleep(1) 
    sock.close() 


for i in range(SOCKET_AMOUNT): 
    msg = "test message" 
    client_thread = threading.Thread(target=client, args=(HOST, PORT, msg)) 
    client_thread.start() 

multiprocessore:

foked_server.py

import os 
import SocketServer 


class ForkedTCPRequestHandler(SocketServer.BaseRequestHandler): 

    def handle(self): 
     cur_process = os.getpid() 
     print "launching a new socket handler, pid = {}".format(cur_process) 
     while 1: 
      self.request.recv(4096) 


class ForkedTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer): 
    pass 


if __name__ == "__main__": 
    HOST, PORT = "localhost", 9999 

    server = ForkedTCPServer((HOST, PORT), ForkedTCPRequestHandler) 
    print "Starting Forked Server" 
    server.serve_forever() 

del server multithread:

threaded_server.py

import threading 
import SocketServer 


class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): 

    def handle(self): 
     cur_thread = threading.current_thread() 
     print "launching a new socket handler, thread = {}".format(cur_thread) 
     while 1: 
      self.request.recv(4096) 


class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 
    pass 


if __name__ == "__main__": 
    HOST, PORT = "localhost", 9999 

    server = ThreadedTCPServer((HOST, PORT), ForkedTCPRequestHandler) 
    print "Starting Threaded Server" 
    server.serve_forever() 

Nel primo caso, con forked_server.py, solo 40 processi vengono creati e circa il 20 di quelli iniziano rompere in tanto con il seguente errore:

error: [Errno 104] Connection reset by peer

su un lato client.

versione filettata è molto più resistente e ospita oltre 4000 connessioni, ma alla fine inizia a mostrare

gaierror: [Errno -5] No address associated with hostname

Le prove sono state effettuate sulla mia macchina locale, Kubuntu 14.04 x64 su kernel v3.13.0-32. Questi sono i passi che ho fatto per aumentare le prestazioni generali del sistema: limite del kernel

  1. Sollevare il handle di file: sysctl -w fs.file-max=10000000
  2. Aumentare l'arretrato di connessione, sysctl -w net.core.netdev_max_backlog = 2500
  3. Alzare le connessioni massime, sysctl -w net.core.somaxconn = 250000

Quindi, le domande sono:

  1. Wer e le prove corrette, posso contare su quei risultati? Sono nuovo di tutto questo materiale di Network/Socket, quindi per favore correggimi nelle mie conclusioni.
  2. È davvero l'approccio multiprocessore/multithreading non utilizzabile in sistemi con carichi elevati?
  3. Se sì, quali opzioni ci sono rimaste? Approccio asincrono? Quadri Tornado/Twisted/Gevent?
+0

Per lo sfondo, google "* C10K *" e "* C10K Python *". –

risposta

11

socketserver non ha intenzione di gestire da nessuna parte vicino alle connessioni 10k. Nessun server filettato o biforcato sarà sull'hardware e sui sistemi operativi attuali. Migliaia di thread ti consentono di dedicare più tempo al cambio di contesto e alla programmazione rispetto a quanto effettivamente funzioni. La moderna linux sta diventando molto brava a pianificare thread e processi, e Windows è abbastanza buono con i thread (ma orribile con i processi), ma c'è un limite a ciò che può fare.

E socketserver non è nemmeno provare essere ad alte prestazioni.

E, naturalmente, il GIL di CPython peggiora le cose. Se non stai usando 3.2+; qualsiasi thread che esegue anche una quantità insignificante di lavoro associato alla CPU sta per soffocare tutti gli altri thread e bloccare l'I/O. Con la nuova GIL, se si evita la CPU non banale non si aggiunge troppo al problema, ma rende comunque gli switch di contesto più costosi rispetto ai raw pthreads o ai thread di Windows.


Allora, che cosa fare vuoi?

Si desidera un "reattore" a thread singolo che servizi gli eventi in un ciclo e avvia i gestori. (Su Windows e Solaris, ci sono dei vantaggi invece di usare un "proactor", un pool di thread che servono tutti la stessa coda di eventi, ma visto che sei su Linux, non preoccupiamoci di questo.) I moderni OS hanno molto bene API multiplexing su ON-kqueue su BSD/Mac, epoll su Linux, /dev/poll su Solaris, IOCP su Windows, che può gestire facilmente connessioni 10K anche su hardware da anni.

socketserver non è un terribile reattore, è solo che non fornisce un buon modo per inviare lavori asincroni, solo thread o processi. In teoria, è possibile creare un (con il modulo di espansione greenlet) o un CoroutineMixIn (presumendo che si abbia o sappia scrivere un trampolino e uno schedulatore) senza troppo lavoro sopra socketserver e che potrebbe non essere troppo pesante- peso. Ma non sono sicuro di quanti benefici stai ottenendo da socketserver a quel punto.

Parallelismo può aiuto, ma solo per inviare processi lenti fuori dal thread di lavoro principale. Innanzitutto, fai partire le tue connessioni da 10K, facendo il minimo lavoro. Quindi, se il lavoro reale che si desidera aggiungere è legato all'I/O (ad esempio, la lettura di file o l'esecuzione di richieste ad altri servizi), aggiungere un pool di thread da inviare a; se hai bisogno di aggiungere un sacco di lavoro legato alla CPU, aggiungi invece un pool di processi (o, in alcuni casi, anche uno di ciascuno).

Se è possibile utilizzare Python 3.4, lo stdlib ha una risposta in asyncio (e c'è un backport su PyPI per 3.3, ma è intrinsecamente impossibile eseguire il backport alle versioni precedenti).

In caso contrario ... beh, si può costruire qualcosa di te stesso in cima selectors in 3.4+ se non si preoccupano di Windows, o select in 2.6+ se vi interessa soltanto Linux, * BSD e Mac e sono disposti a scrivere due versioni del tuo codice, ma ci sarà molto lavoro. Oppure puoi scrivere il tuo ciclo di eventi principale in C (o usarne uno esistente come libev o libuv o libevent) e avvolgerlo in un modulo di estensione.

Ma in realtà, probabilmente si desidera passare a librerie di terze parti. Ce ne sono molti, con API molto diverse, da gevent (che tenta di far apparire il codice come codice di thread preventivamente ma in realtà viene eseguito in greenlet su un ciclo di eventi a thread singolo) a Twisted (che si basa su callback e future espliciti, simile a molti moderni framework JavaScript).

StackOverflow non è un buon posto per ottenere consigli per librerie specifiche, ma posso darti un consiglio generale: guardali, scegli quello la cui API sembra la migliore per la tua applicazione, verifica se è abbastanza buona, e solo ricorrere ad un altro se quello che ti piace non lo può tagliare (o se ti sei sbagliato a piacergli l'API). I fan di alcune di queste librerie (soprattutto gevent e tornado vi diranno che il loro preferito è "più veloce", ma chi se ne frega di questo? Ciò che conta è se sono abbastanza veloce e utilizzabile per scrivere il vostro app.

fuori della parte superiore della mia testa, mi piacerebbe ricerca di gevent, eventlet, concurrence, cogen, twisted, tornado, monocle, diesel e circuits. che probabilmente non è una grande lista, ma se google tutti quei termini insieme, Scommetto che troverai un confronto aggiornato o un forum appropriato su cui chiedere.

+0

Ha, googling esattamente quel insieme di termini, ho trovato [questo articolo] (http://nichol.as/asynchronous-servers-in-python) dal 2009 confrontando tutti tranne uno di quei quadri e nessun altro ... che implica il mio elenco è 5 anni non aggiornato :) Penso anche che l'autore abbia ignorato il motivo per cui i 'yields 'espliciti possono essere un'API molto migliore di quelli impliciti di' gevent', almeno se si dispone di dati mutabili condivisi o altro non determinismo. – abarnert

1

This guy sembrava avere una soluzione piuttosto buona utilizzando threading e subprocess.

#!/usr/bin/env python 
# ssl_load.py - Corey Goldberg - 2008 

import httplib 
from threading import Thread 

threads = 250 
host = '192.168.1.14' 
file = '/foo.html' 

def main(): 
    for i in range(threads): 
     agent = Agent() 
     agent.start() 

class Agent(Thread): 
    def __init__(self): 
     Thread.__init__(self) 

    def run(self): 
     while True: 
      conn = httplib.HTTPSConnection(host) 
      conn.request('GET', file) 
      resp = conn.getresponse() 

if __name__ == '__main__': 
    main() 

Consentitegli di avere al massimo 250 thread per processo a causa di vincoli di Windows XP. Questo sta considerando che aveva un hardware piuttosto scadente rispetto agli standard odierni. Egli è stato in grado di raggiungere un 15k filo max eseguendo questo script come molteplici processi, come mostrato qui:

#!/usr/bin/env python 

import subprocess 
processes = 60 
for i in range(processes): 
    subprocess.Popen('python ssl_load.py') 

Spero che questo ti aiuta!