Concorrência de estresse de soquete Python

Serge Mosin

Preciso de um servidor Python TCP que possa lidar com pelo menos dezenas de milhares de conexões de soquete simultâneas. Eu estava tentando testar os recursos do pacote Python SocketServer nos modos multiprocessador e multithread, mas ambos estavam longe do desempenho desejado.

A princípio descreverei cliente, porque é comum nos dois casos.

client.py

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()

Servidor multiprocessador:

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()

Servidor 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()

No primeiro caso, com forked_server.py , apenas 40 processos são criados e aproximadamente 20 deles começam a quebrar em um instante com o seguinte erro:

erro: [Errno 104] Conexão redefinida pelo par

do lado do cliente.

A versão encadeada é muito mais durável e mantém mais de 4000 conexões, mas eventualmente começa a mostrar

gaierror: [Errno -5] Nenhum endereço associado ao nome do host

Os testes foram feitos em minha máquina local, Kubuntu 14.04 x64 no kernel v3.13.0-32. Estas são as etapas que executei para aumentar o desempenho geral do sistema:

  1. Aumente o limite do kernel nos identificadores de arquivo: sysctl -w fs.file-max=10000000
  2. Aumente o backlog de conexão, sysctl -w net.core.netdev_max_backlog = 2500
  3. Aumente as conexões máximas, sysctl -w net.core.somaxconn = 250000

Então, as perguntas são:

  1. Os testes estavam corretos, posso confiar nesses resultados? Eu sou novo em todas essas coisas de rede / soquete, então, corrija-me nas minhas conclusões.
  2. É realmente a abordagem multiprocessador / multithread não viável em sistemas com carga pesada?
  3. Se sim, quais são as opções que ainda temos? Abordagem assíncrona? Estruturas Tornado / Twisted / Gevent?
abarnert

socketservernão vai lidar com nada perto de conexões de 10k. Nenhum servidor threaded ou bifurcado o fará no hardware e sistemas operacionais atuais. Milhares de threads significam que você gasta mais tempo trocando de contexto e agendando do que realmente trabalhando. O Linux moderno está ficando muito bom em agendar threads e processos, e o Windows é muito bom com threads (mas é horrível com processos), mas há um limite para o que ele pode fazer.

E socketservernem mesmo tenta ser de alto desempenho.

E, claro, o GIL de CPython torna as coisas piores. Se você não estiver usando o 3.2+; qualquer thread que faça até mesmo uma quantidade trivial de trabalho vinculado à CPU irá sufocar todas as outras threads e bloquear sua E / S. Com o novo GIL, se você evitar CPU não-trivial você não adicionar demasiado muito para o problema, mas ainda faz mudanças de contexto mais caro do que pthreads crus ou tópicos do Windows.


Então, o que você quer?

Você deseja um "reator" de thread único que atenda a eventos em um loop e inicie os manipuladores. (No Windows e Solaris, há vantagens em usar um "proator", um pool de threads que atendem à mesma fila de eventos, mas como você está no Linux, não vamos nos preocupar com isso.) OS modernos têm muito bons APIs de multiplexação para construir - kqueueno BSD / Mac, epollno Linux, /dev/pollno Solaris, IOCP no Windows - que podem lidar facilmente com conexões de 10K, mesmo em hardware de anos atrás.

socketservernão é um reator terrível, só que não fornece uma boa maneira de despachar trabalho assíncrono, apenas threads ou processos. Em teoria, você poderia construir um GreenletMixIn(com o greenletmódulo de extensão) ou um CoroutineMixIn(presumindo que você tenha ou saiba escrever um trampolim e agenda) sem muito trabalho em cima socketserver, e isso pode não ser muito pesado. Mas não tenho certeza de quanto benefício você está obtendo socketservernesse ponto.

O paralelismo pode ajudar, mas apenas para despachar quaisquer trabalhos lentos da linha de trabalho principal. Primeiro aumente suas conexões de 10K, fazendo um trabalho mínimo. Então, se o trabalho real que você deseja adicionar é vinculado a E / S (por exemplo, ler arquivos ou fazer solicitações a outros serviços), adicione um pool de threads para enviar; se você precisar adicionar muito trabalho vinculado à CPU, adicione um pool de processos (ou, em alguns casos, até mesmo um de cada).

Se você pode usar o Python 3.4, o stdlib tem uma resposta asyncio(e há um backport no PyPI para 3.3, mas é inerentemente impossível fazer o backport para versões anteriores).

Se não ... bem, você pode construir algo por conta própria selectorsno 3.4+ se não se preocupa com o Windows, ou selectno 2.6+ se você apenas se preocupa com o Linux, * BSD e Mac e está disposto a escrever duas versões do seu código, mas vai dar muito trabalho. Ou você pode escrever seu loop de evento principal em C (ou apenas usar um existente como libevou libuvou libevent) e envolvê-lo em um módulo de extensão.

Mas, realmente, você provavelmente deseja recorrer a bibliotecas de terceiros. Existem muitos deles, com APIs muito diferentes, de gevent(que tenta fazer seu código parecer um código encadeado preventivamente, mas na verdade é executado em greenlets em um loop de evento de encadeamento único) a Twisted(que se baseia em retornos de chamada explícitos e futuros, semelhante a muitos frameworks JavaScript modernos).

StackOverflow não é um bom lugar para obter recomendações para bibliotecas específicas, mas posso dar-lhe uma recomendação geral: examine-as, escolha aquela cuja API soe melhor para o seu aplicativo, teste se é boa o suficiente e apenas use outra um se o que você gosta não puder cortá-lo (ou se você errar ao gostar da API). Fãs de algumas dessas bibliotecas (especialmente gevente tornadodirão que sua favorita é "mais rápida", mas quem se importa com isso? O que importa é se elas são rápidas o suficiente e utilizáveis ​​para escrever seu aplicativo.

Em cima da minha cabeça, eu procurar gevent, eventlet, concurrence, cogen, twisted, tornado, monocle, diesel, e circuits. Provavelmente não é uma boa lista, mas se você pesquisar todos esses termos no Google, aposto que encontrará uma comparação atualizada ou um fórum apropriado para perguntar.

Este artigo é coletado da Internet.

Se houver alguma infração, entre em [email protected] Delete.

editar em
0

deixe-me dizer algumas palavras

0comentários
loginDepois de participar da revisão

Artigos relacionados

TOP lista

quentelabel

Arquivo