Stiamo accodando e serializzando correttamente?


13

Elaboriamo i messaggi attraverso una varietà di servizi (un messaggio toccherà probabilmente 9 servizi prima di essere eseguito, ciascuno con una specifica funzione relativa all'IO). In questo momento abbiamo una combinazione del caso peggiore (serializzazione del contratto dati XML) e del caso migliore (MSMQ in memoria) per le prestazioni.

La natura del messaggio significa che i nostri dati serializzati finiscono per circa 12-15 kilobyte e che elaboriamo circa 4 milioni di messaggi a settimana. I messaggi persistenti in MSMQ erano troppo lenti per noi e quando i dati crescono sentiamo la pressione dei file mappati in memoria di MSMQ. Il server ha 16 GB di memoria in uso e in crescita, solo per l'accodamento. Le prestazioni subiscono anche quando l'utilizzo della memoria è elevato, poiché la macchina inizia a scambiare. Stiamo già eseguendo il comportamento di autopulizia MSMQ.

Sento che c'è una parte che stiamo facendo di sbagliato qui. Ho provato a usare RavenDB per rendere persistenti i messaggi e ho semplicemente messo in coda un identificatore, ma le prestazioni erano molto lente (al massimo 1000 messaggi al minuto). Non sono sicuro che sia il risultato dell'utilizzo della versione di sviluppo o di cosa, ma abbiamo sicuramente bisogno di un throughput più elevato [1]. Il concetto ha funzionato molto bene in teoria, ma le prestazioni non erano all'altezza del compito.

Il modello di utilizzo ha un servizio che funge da router, che fa tutte le letture. Gli altri servizi collegheranno le informazioni in base al loro hook di terze parti e inoltreranno nuovamente al router. La maggior parte degli oggetti viene toccata 9-12 volte, anche se circa il 10% è costretto a circolare in questo sistema per un po 'fino a quando le terze parti rispondono in modo appropriato. I servizi ora tengono conto di questo e hanno comportamenti di sonno appropriati, poiché per questo motivo utilizziamo il campo prioritario del messaggio.

Quindi, la mia domanda, qual è lo stack ideale per il passaggio di messaggi tra macchine discrete ma LAN in un ambiente C # / Windows? Normalmente inizierei con BinaryFormatter invece della serializzazione XML, ma è un buco nel coniglio se un modo migliore è scaricare la serializzazione in un archivio documenti. Quindi, la mia domanda.

[1]: la natura della nostra attività significa che prima elaboriamo i messaggi, più soldi facciamo. Abbiamo empiricamente dimostrato che l'elaborazione di un messaggio più avanti nella settimana significa che abbiamo meno probabilità di fare quei soldi. Mentre le prestazioni di "1000 al minuto" sembrano molto veloci, abbiamo davvero bisogno di quel numero fino a 10k / minuto. Solo perché sto fornendo numeri nei messaggi a settimana non significa che abbiamo un'intera settimana per elaborare quei messaggi.

=============== modifica:

Informazioni aggiuntive

Sulla base dei commenti, aggiungerò alcuni chiarimenti:

  • Non sono sicuro che la serializzazione sia il nostro collo di bottiglia. Ho confrontato l'applicazione e, sebbene la serializzazione sia presente nel grafico termico, è responsabile solo del 2,5-3% dell'utilizzo della CPU del servizio.

  • Sono principalmente preoccupato per la permanenza dei nostri messaggi e il potenziale uso improprio di MSMQ. Stiamo usando messaggi non transazionali e non persistenti in modo da poter continuare ad accodare le prestazioni e mi piacerebbe davvero avere messaggi almeno persistenti in modo che sopravvivessero al riavvio.

  • L'aggiunta di più RAM è una misura di stopgap. La macchina è già passata da 4 GB -> 16 GB di RAM e diventa sempre più difficile smontarla per continuare ad aggiungere altro.

  • A causa del modello di instradamento a stella dell'applicazione, metà del tempo in cui un oggetto viene espulso, quindi spinto in una coda, non cambia affatto. Questo si presta di nuovo (IMO) a memorizzarlo in una sorta di archivio di valori-chiave altrove e semplicemente a passare identificatori di messaggi.

  • Il modello di instradamento a stella è parte integrante dell'applicazione e non cambierà. Non siamo in grado di forzare l'applicazione perché ogni pezzo lungo la strada funziona in modo asincrono (in modo polling) e vogliamo centralizzare il comportamento dei tentativi in ​​un unico posto.

  • La logica dell'applicazione è scritta in C #, gli oggetti sono POCO immutabili, l'ambiente di distribuzione di destinazione è Windows Server 2012 e siamo autorizzati a mettere in piedi macchine aggiuntive se un particolare software è supportato solo in Linux.

  • I miei obiettivi sono di mantenere l'attuale throughput riducendo al contempo l'ingombro della memoria e aumentando la tolleranza agli errori con un esborso minimo di capitale.


I commenti sono stati ripuliti dal momento che i punti pertinenti sono stati integrati nella domanda.
ChrisF

Avrebbe senso affrontare il problema più urgente prima di preoccuparsi di scambiare sottosistemi di accodamento (anche se alla fine potrebbe valere la pena farlo). Il fatto che la memoria stia crescendo senza controllo suggerisce che ci sono ancora perdite da qualche parte. Quale (se presente) profilo di memoria è stato fatto?
Dan Lyons,

@DanLyons: l'unica crescita di memoria è in MSMQ. Nessuno ne parla davvero, ma sembra essere a causa di messaggi non persistenti che sono tutti mappati in memoria. Poiché stiamo serializzando molti dati, mantiene allocata una notevole quantità di memoria. La memoria viene (eventualmente) recuperata quando i messaggi vengono consumati e viene eseguita la pulizia interna di MSMQ.
Bryan Boettcher,

Risposte:


1

Ecco alcuni benchmark delle code che potrebbero interessarti. MSMQ dovrebbe essere in grado di gestire 10K messaggi al secondo. Potrebbe essere un problema di configurazione o forse i client non riescono a tenere il passo con la lettura della coda? Inoltre, osserva quanto ZeroMQ sia straordinariamente veloce in quei benchmark (circa 100.000 messaggi al secondo), non offre un'opzione di persistenza ma dovrebbe portarti dove vuoi essere saggio delle prestazioni.


4

Abbiamo avuto una situazione un po 'simile diversi anni fa, con un sistema di messaggi in coda (impronte digitali audio nel nostro caso). Abbiamo valutato fortemente la persistenza dei pacchetti di dati accodati, ma abbiamo scoperto che accodare tutto su disco e consumare la coda da disco era molto costoso.

Se passavamo alle code basate sulla memoria, le prestazioni erano eccezionali, ma avevamo un grosso problema. Di tanto in tanto i consumatori delle code diventavano non disponibili per un considerevole lasso di tempo (gli elementi consumatore e produttore nel nostro caso sono collegati tramite WAN), quindi la coda del produttore cresceva fino a diventare ingestibile e come il tuo caso, una volta che il consumo di memoria è stato molto elevato, l'eccessivo blocco della memoria durante lo scambio ha portato il sistema a una scansione completa.

Abbiamo progettato una coda che abbiamo battezzato VMQueue (per Virtual Memory Queue, un nome molto brutto in retrospettiva). L'idea di questa coda è che se il processo del consumatore è in esecuzione alla pari, in altre parole, l'elaborazione abbastanza veloce da essere in grado di mantenere il numero di elementi accodati al di sotto di un certo livello, allora ha sostanzialmente le stesse prestazioni di una memoria- coda basata. Tuttavia, quando il consumatore rallenta o diventa non disponibile e la coda del produttore raggiunge una certa dimensione, la coda inizierà automaticamente il paging degli elementi da e verso il disco (usandoBinaryFormatterserializzazione a proposito). Questo processo mantiene l'utilizzo della memoria completamente controllato e il processo di paging è veloce, o almeno molto più veloce dello scambio di memoria virtuale durante il caricamento di memoria pesante. Una volta che il consumatore riesce a svuotare la coda al di sotto della soglia, riprende a funzionare come una pura coda basata sulla memoria

Se il sistema si arresta in modo anomalo o si riavvia, la coda è in grado di ripristinare tutti gli elementi di paging che sono stati memorizzati su disco, perderà solo gli elementi che erano ancora conservati in memoria prima del crash. Se puoi permetterti di perdere un numero limitato di pacchetti durante un arresto anomalo o un riavvio, questa coda potrebbe essere utile.

Se sei interessato, posso condividere il VMQueuecodice sorgente della classe in modo da poter giocare con esso. La coda accetterà qualsiasi classe contrassegnata come serializzabile. Alla creazione della coda si stabilisce la dimensione della pagina in numero di elementi. L'interfaccia di classe è praticamente la stessa di una classe di coda standard. Tuttavia, il codice è molto vecchio (.net 1.1), quindi purtroppo non esiste un'interfaccia generica.

So che passare dalla comprovata tecnologia MSMQ è una scommessa enorme, tuttavia questa coda ha funzionato in modo affidabile per quasi 6 anni e ci ha permesso di sopravvivere e recuperare da scenari in cui la macchina del produttore è stata offline per diverse settimane! Per favore fammi sapere se sei interessato. :)


1

Il sistema HP ProLiant ML350G5 ottiene 82k transazioni al minuto, ovvero ha oltre 8 volte il throughput "10k / minuto" menzionato.

Prestazioni: 82.774 tpmC

Inoltre, a dire il vero, sarei appena andato con 64 o anche 128 GB di RAM - La RAM è economica. Greenspun sottolinea la differenza tra "lanciare RAM" e "ottenere un ragazzo intelligente con il MIT per ottimizzarlo" e la RAM vince.

Ha finito con una macchina SQL Server dotata di 64 GB di RAM e una manciata di macchine front-end che eseguono pagine ASP.NET ... Il sito, swaptree.com, gestisce l'attuale affiliazione di oltre 400.000 utenti (in rapida crescita) Senza difficoltà...

Nota "la macchina ha già raggiunto i 16 GB di RAM" è tutt'altro che sufficiente, con un articolo che sottolinea un server che gestiva 400k utenti su 64 GB di RAM.

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.