Devo attenermi o abbandonare Python per gestire la concorrenza?


31

Ho un progetto LOC da 10K scritto a Django con parecchi Celery ( RabbitMQ ) per lavori di asincronicità e background dove necessario, e sono giunto alla conclusione che parti del sistema trarrebbero beneficio dalla riscrittura in qualcosa di diverso da Django per una migliore concorrenza . I motivi includono:

  • Gestione dei segnali e oggetti mutabili. Soprattutto quando un segnale innesca un altro, gestirli in Django usando l' ORM può essere sorprendente quando le istanze cambiano o scompaiono. Voglio usare un approccio di messaggistica in cui i dati trasmessi non cambiano in un gestore ( l' approccio di copia e scrittura di Clojure sembra carino, se ho capito bene).
  • Parti del sistema non sono basate sul Web e necessitano di un supporto migliore per l'esecuzione simultanea di attività. Ad esempio, il sistema legge i tag NFC e quando uno viene letto un LED si accende per alcuni secondi (attività di sedano), viene riprodotto un suono (altra attività di sedano) e viene interrogato il database (altra attività). Questo è implementato come un comando di gestione di Django, ma Django e il suo ORM essendo sincroni per natura e condividendo la memoria sono limitanti (stiamo pensando di aggiungere più lettori NFC e non penso che l'approccio Django + Celery funzionerà più, Mi piacerebbe vedere migliori capacità di trasmissione dei messaggi).

Quali sono i pro e i contro dell'utilizzo di qualcosa come Twisted o Tornado rispetto all'utilizzo di una lingua come Erlang o Clojure ? Sono interessato a vantaggi e svantaggi pratici.

Come sei arrivato alla conclusione che alcune parti del sistema sarebbero andate meglio in un'altra lingua? Stai soffrendo problemi di prestazioni? Quanto sono gravi questi problemi? Se può essere più veloce, è essenziale che sia più veloce?

Esempio 1: Django al lavoro al di fuori di una richiesta HTTP:

  1. Viene letto un tag NFC.
  2. Il database (e possibilmente LDAP) viene interrogato e vogliamo fare qualcosa quando i dati diventano disponibili (luce rossa o verde, riprodurre un suono). Questo blocca l'utilizzo di Django ORM, ma fintanto che ci sono lavoratori del sedano disponibili non importa. Potrebbe essere un problema con più stazioni.

Esempio 2: "passaggio di messaggi" utilizzando segnali Django:

  1. Viene post_deletegestito un evento, altri oggetti possono essere modificati o eliminati per questo motivo.
  2. Alla fine, le notifiche dovrebbero essere inviate agli utenti. Qui sarebbe bello se gli argomenti passati al gestore delle notifiche fossero copie di oggetti eliminati o da cancellare e garantiti di non cambiare nel gestore. (Potrebbe essere fatto manualmente semplicemente non passando oggetti gestiti dall'ORM ai gestori, ovviamente.)

Penso che accadranno risposte migliori se spieghi di più sul perché sei giunto alla conclusione
Winston Ewert,

5
Prima che qualcuno dica che le domande sulla scelta della lingua sono fuori tema, mi terrò a dire che penso che questo vada bene poiché è un problema pratico con requisiti specifici. Spero che faccia alcuni confronti dettagliati.
Adam Lear

Twisted è l'opposto del concorrente! È un server a thread singolo guidato da eventi, non ti porterà da nessuna parte se hai bisogno di una vera concorrenza.

Risposte:


35

Pensieri di apertura

Come sei arrivato alla conclusione che alcune parti del sistema sarebbero andate meglio in un'altra lingua? Stai soffrendo problemi di prestazioni? Quanto sono gravi questi problemi? Se può essere più veloce, è essenziale che sia più veloce?

Asincronia a thread singolo

Esistono diverse domande e altre risorse Web che già trattano le differenze, i vantaggi e gli svantaggi dell'asincronia a thread singolo rispetto alla concorrenza multi-thread. È interessante leggere come si comporta il modello asincrono a thread singolo di Node.js quando l'I / O è il principale collo di bottiglia e ci sono molte richieste che vengono servite contemporaneamente.

Twisted, Tornado e altri modelli asincroni fanno un uso eccellente di un singolo thread. Poiché molta programmazione Web ha molti I / O (rete, database, ecc.), Il tempo trascorso in attesa di chiamate remote aumenta notevolmente. Questo è il tempo che potrebbe essere speso per fare altre cose, come dare il via ad altre chiamate al database, renderizzare pagine e generare dati. L'utilizzo di quel singolo thread è estremamente elevato.

Uno dei maggiori vantaggi dell'asincronia a thread singolo è che utilizza molta meno memoria. Nell'esecuzione multi-thread, ogni thread richiede una certa quantità di memoria riservata. All'aumentare del numero di thread, aumenta anche la quantità di memoria necessaria solo per l'esistenza dei thread. Poiché la memoria è limitata, significa che ci sono limiti al numero di thread che possono essere creati in qualsiasi momento.


Esempio

Nel caso di un server web, fai finta che a ogni richiesta venga assegnato il proprio thread. Supponiamo che siano necessari 1 MB di memoria per ogni thread e che il server Web abbia 2 GB di RAM. Questo server Web sarebbe in grado di elaborare (circa) 2000 richieste in qualsiasi momento prima che non ci sia più memoria sufficiente per elaborare più.

Se il tuo carico è significativamente superiore a questo, le richieste impiegheranno molto tempo (in attesa del completamento di richieste più vecchie) o dovrai lanciare più server nel cluster per espandere il numero di richieste simultanee possibili .


Concorrenza multi-thread

La concorrenza multi-thread si basa invece sull'esecuzione di più attività contemporaneamente. Ciò significa che se un thread viene bloccato in attesa di una chiamata al database per restituire, altre richieste possono essere elaborate contemporaneamente. L'utilizzo del thread è inferiore, ma il numero di thread in esecuzione è molto maggiore.

Il codice multi-thread è anche molto più difficile da ragionare. Ci sono problemi con il blocco, la sincronizzazione e altri divertenti problemi di concorrenza. L'asincronia a thread singolo non presenta gli stessi problemi.

Tuttavia, il codice multi-thread è molto più performante per le attività che richiedono molta CPU . Se non esiste alcuna possibilità per un thread di "cedere" - come una chiamata di rete che normalmente si bloccherebbe - un modello a thread singolo non avrà alcuna concorrenza.

Entrambi possono coesistere

Ovviamente c'è una sovrapposizione tra i due; Non si escludono a vicenda. Ad esempio, il codice multi-thread può essere scritto in modo non bloccante, per utilizzare meglio ogni thread.


La linea di fondo

Ci sono molti altri problemi da considerare, ma mi piace pensare ai due in questo modo:

  • Se il tuo programma è associato all'I / O , probabilmente l'asincronia a thread singolo funzionerà abbastanza bene.
  • Se il tuo programma è associato alla CPU , probabilmente sarà un sistema multi-thread.

Nel tuo caso particolare, devi determinare quale tipo di lavoro asincrono è stato completato e con quale frequenza si presentano tali compiti.

  • Si verificano ad ogni richiesta? In tal caso, probabilmente la memoria diventerà un problema con l'aumentare del numero di richieste.
  • Questi compiti sono ordinati? In tal caso, dovrai considerare la sincronizzazione se utilizzi più thread.
  • Queste attività sono impegnative per la CPU? In tal caso, un thread singolo è in grado di tenere il passo con il carico?

Non c'è una risposta semplice. È necessario considerare quali sono i casi d'uso e progettare di conseguenza. A volte è meglio un modello asincrono a thread singolo. Altre volte, è necessario utilizzare una serie di thread per ottenere un'elaborazione parallela massiccia.

altre considerazioni

Ci sono anche altri problemi che devi considerare, piuttosto che solo il modello di concorrenza che scegli. Conosci Erlang o Clojure? Pensi di essere in grado di scrivere codice multi-thread sicuro in una di queste lingue in modo da migliorare le prestazioni della tua applicazione? Ci vorrà molto tempo per mettersi al passo con una di queste lingue e la lingua che imparerai ti gioverà in futuro?

E le difficoltà associate alla comunicazione tra questi due sistemi? Sarà eccessivamente complesso mantenere in parallelo due sistemi separati? In che modo il sistema Erlang riceverà attività da Django? In che modo Erlang comunicherà questi risultati a Django? Le prestazioni sono abbastanza significative un problema che vale la complessità aggiunta?


Pensieri finali

Ho sempre trovato Django abbastanza veloce, ed è utilizzato da alcuni siti molto trafficati. Esistono diverse ottimizzazioni delle prestazioni che è possibile apportare per aumentare il numero di richieste e tempi di risposta simultanei. Certo, finora non ho fatto nulla con Celery, quindi le consuete ottimizzazioni delle prestazioni probabilmente non risolveranno alcun problema che potresti avere con questi compiti asincroni.

Naturalmente, c'è sempre il suggerimento di lanciare più hardware al problema. Il costo del provisioning di un nuovo server è più economico del costo di sviluppo e manutenzione di un sottosistema completamente nuovo?

Ho fatto troppe domande a questo punto, ma questa era la mia intenzione. La risposta non sarà facile senza analisi e ulteriori dettagli. Essere in grado di analizzare i problemi si riduce alla conoscenza delle domande da porre, però ... quindi spero di aver aiutato su questo fronte.

Il mio istinto dice che non è necessaria una riscrittura in un'altra lingua. La complessità e il costo saranno probabilmente troppo grandi.


modificare

Risposta al follow-up

Il tuo follow-up presenta alcuni casi d'uso molto interessanti.


1. Django lavora al di fuori delle richieste HTTP

Il tuo primo esempio riguardava la lettura di tag NFC, quindi l'interrogazione del database. Non penso che scrivere questa parte in un'altra lingua ti sarà così utile, semplicemente perché interrogare il database o un server LDAP sarà vincolato dall'I / O di rete (e potenzialmente dalle prestazioni del database). D'altra parte, il numero di richieste simultanee sarà vincolato dal server stesso, poiché ciascun comando di gestione verrà eseguito come processo proprio. Ci saranno tempi di installazione e smontaggio che influiscono sulle prestazioni, poiché non si inviano messaggi a un processo già in esecuzione. Sarai comunque in grado di inviare più richieste contemporaneamente, poiché ognuna sarà una procedura isolata.

Per questo caso, vedo due strade su cui puoi indagare:

  1. Assicurarsi che il database sia in grado di gestire più query contemporaneamente con il pool di connessioni. (Oracle, ad esempio, richiede di configurare Django di conseguenza 'OPTIONS': {'threaded':True}.) Potrebbero esserci opzioni di configurazione simili a livello di database o Django che è possibile modificare per il proprio database. Indipendentemente dalla lingua in cui scrivi le query del database, dovrai attendere la restituzione di questi dati prima di poter accendere i LED. Tuttavia, le prestazioni del codice di query possono fare la differenza e Django ORM non è velocissimo ( ma , di solito abbastanza veloce).
  2. Ridurre al minimo i tempi di installazione / smontaggio. Avere un processo costantemente in esecuzione e inviare messaggi ad esso. (Correggimi se sbaglio, ma questo è ciò su cui la tua domanda originale si sta effettivamente concentrando.) Se questo processo è scritto in Python / Django o in un'altra lingua / framework è trattato sopra. Non mi piace l'idea di utilizzare i comandi di gestione così frequentemente. È possibile avere un piccolo codice in esecuzione costantemente, che spinge i messaggi dei lettori NFC sulla coda dei messaggi, che Celery legge e inoltra a Django? L'installazione e lo smontaggio di un piccolo programma, anche se è scritto in Python (ma non in Django!), Dovrebbe essere migliore dell'avvio e dell'arresto di un programma Django (con tutti i suoi sottosistemi).

Non sono sicuro del server Web che stai utilizzando per Django. mod_wsgiper Apache consente di configurare il numero di processi e thread all'interno dei processi richiesti dal servizio. Assicurati di modificare la configurazione pertinente del tuo server web per ottimizzare il numero di richieste gestibili.


2. "Passaggio di messaggi" con segnali Django

Anche il tuo secondo caso d'uso è abbastanza interessante; Non sono sicuro di avere le risposte per questo. Se stai eliminando gli esempi di modelli, e desiderio di operare su di essi in seguito, potrebbe essere possibile per serializzare loro JSON.dumpse poi deserializzare JSON.loads. In seguito sarà impossibile ricreare completamente il grafico a oggetti (interrogare modelli correlati), poiché i campi correlati sono caricati in modo pigro dal database e quel collegamento non esisterà più.

L'altra opzione sarebbe in qualche modo contrassegnare un oggetto per l'eliminazione e cancellarlo solo alla fine del ciclo richiesta / risposta (dopo che tutti i segnali sono stati revisionati). Potrebbe richiedere un segnale personalizzato per implementarlo, piuttosto che fare affidamento post_delete.


1
un sacco di FUD e dubbi sul blocco e altre cose che non sono problemi con Erlang, nessuno dei tradizionali problemi di stato condiviso che elenchi sono considerazioni con una lingua e un runtime specificamente progettati per non condividere lo stato. Erlang è in grado di gestire decine di migliaia di processi discreti in pochissimo ariete, neanche la pressione della memoria è un problema.

@Jarrod, personalmente non conosco Erlang, quindi accetterò quello che dici a riguardo. Altrimenti, quasi tutto il resto che ho citato è rilevante. Costo, complessità e se gli strumenti attuali vengono utilizzati correttamente o meno.
Josh Smeaton,


Questo è il tipo di risposta epica che adoro davvero leggere ^^. +1, buon lavoro!
Laurent Bourgault-Roy,

Inoltre, se hai modelli DJango, possono essere utilizzati in Erlang con Erlydtl
Zachary K

8

Ho fatto uno sviluppo altamente sofisticato e altamente scalabile per un importante ISP statunitense . Abbiamo fatto alcuni seri numeri di tranasazione usando un server Twisted , ed è stato un incubo di complessità far scalare Python / Twisted su qualsiasi cosa fosse legata alla CPU . Il limite di I / O non è un problema, ma il limite della CPU era impossibile. Potremmo mettere insieme sistemi rapidamente, ma farli ridimensionare a milioni di utenti simultanei è stato un incubo di configurazione e complessità se fossero vincolati dalla CPU.

Ho scritto un post sul blog al riguardo, Python / Twisted VS Erlang / OTP .

TLDR; Ha vinto Erlang .


4

Problemi pratici con Twisted (che adoro e che uso da circa cinque anni):

  1. La documentazione lascia a desiderare, e il modello è abbastanza complesso da imparare comunque. Trovo difficile far funzionare altri programmatori Python su Twisted code.
  2. Ho finito per usare l'I / O dei file di blocco e l'accesso al database per mancanza di buone API di blocco. Questo può davvero danneggiare le prestazioni.
  3. Non sembra esserci una grande comunità e una sana comunità che usano Twisted; per esempio Node.js ha uno sviluppo molto più attivo, specialmente per la programmazione di back-end web.
  4. È ancora Python, e almeno CPython non è la cosa più veloce in circolazione.

Ho lavorato un po 'con Node.js con CoffeeScript e se le prestazioni simultanee sono la tua preoccupazione, potrebbe valere la pena.

Hai preso in considerazione l'esecuzione di più istanze di Django con un accordo per distribuire i client tra istanze?


1
La documentazione di Python in generale lascia a desiderare: / (Non dire che è così male, ma per un linguaggio così popolare ci si aspetterebbe che sia molto meglio).
Arriva il

3
Trovo che la documentazione di Python e in particolare la documentazione di Django siano tra i migliori documenti per qualsiasi lingua. Molte librerie di terze parti lasciano comunque a desiderare.
Josh Smeaton,

1

Ti suggerirò quanto segue prima di prendere in considerazione il passaggio a un'altra lingua.

  1. Utilizzare LTTng per registrare eventi di sistema come errori di pagina, cambi di contesto e attese di chiamata di sistema.
  2. Converti ovunque ci voglia troppo tempo per usare la libreria C e usa qualsiasi modello di progettazione che ti piace (multi-threading, basato su eventi di segnale, richiamata asincrona o Unix tradizionale select) che è buono per l'I / O.

Non userei il threading in Python una volta che l'applicazione ha la priorità nelle prestazioni. Vorrei prendere l'opzione sopra, che può risolvere molti problemi come il riutilizzo del software, la connettività con Django , le prestazioni, la facilità di sviluppo, ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.