2016-04-14 16 views
12

Ho la seguente situazione: ci sono un paio di macchine che formano un cluster. I client possono caricare set di dati e dobbiamo selezionare il nodo su cui verrà caricato il set di dati e rifiutare di caricare/evitare un errore OOM se non esiste una macchina che possa adattarsi al set di dati.Come stimare se la JVM ha abbastanza memoria libera per una particolare struttura dati?

Cosa facciamo attualmente: ora il entry count nel set di dati e stimare lo memory to be used come entry count * empirical factor (determinato manualmente). Quindi controlla se è inferiore alla memoria libera (ottenuta da Runtime.freeMemory()) e, in caso affermativo, caricala (altrimenti ripristina il processo su altri nodi/segnala che non c'è capacità disponibile).

I problemi con questo approccio sono:

  • le empirical factor dev'essere rivisto e aggiornati manualmente
  • freeMemory volte possono underreport causa di alcuni rifiuti non ripulita (che potrebbe essere evitata eseguendo System.gc prima di ogni chiamata, tuttavia, ciò rallenterebbe il sever e potenzialmente potrebbe portare a una promozione prematura)
  • un'alternativa sarebbe "provare semplicemente a caricare il set di dati" (e tornare indietro se viene generata un'OOM) tuttavia una volta una OOM è lanciato, tu potentia ha corrotto altri thread in esecuzione nella stessa JVM e non esiste un modo elegante di ripristinarli.

Esistono soluzioni migliori a questo problema?

+0

Non dimenticare che potrebbe esserci una differenza tra una query per un numero elevato di oggetti più piccoli rispetto a una query per alcuni oggetti di grandi dimensioni (a causa della memoria intermedia potenzialmente necessaria per la conversione della rappresentazione del database in un oggetto Java) . – Robert

risposta

1

Il empirical factor può essere calcolato come passo di creazione e inserito in un file di proprietà.

Mentre freeMemory() è quasi sempre inferiore all'importo che sarebbe libero dopo un GC, è possibile controllare per vedere se è disponibile e chiama un System.gc() se il maxMemory() indica che ci potrebbe essere un sacco.

NOTA: l'utilizzo di System.gc() in produzione fa solo in very rare situations e in generale viene utilizzato spesso in modo errato con conseguente riduzione delle prestazioni e oscuramento del problema reale.

Vorrei evitare di attivare un OOME a meno che non si sta eseguendo è una JVM è possibile riavviare come richiesto.

+1

Di solito non è una [buona idea] (http://stackoverflow.com/questions/2414105/why-is-it-bad-practice-to-call-system-gc) per chiamare System.gc() in produzione codice. Dopo aver selezionato una giusta GC, questa è solitamente l'ultima cosa che vuoi fare; e per lo più è [esplicitamente disabilitato] (http://stackoverflow.com/questions/12847151/setting-xxdisableexplicitgc-in-production- che cosa può essere -uso-) nel codice di rilascio tramite l'impostazione JVM. –

+0

@AlexPunnen Ho aggiunto una nota che utilizza System.gc() in produzione è molto probabilmente un errore, anche se questo è qualcosa che suggerisco ma solo per un caso d'uso molto specifico (aggiunto un collegamento) –

1

La mia soluzione:

  1. Impostare la Xmx come 90%-95% di RAM della macchina fisica se nessun altro processo è in esecuzione tranne il vostro programma. Per la RAM da 32 GB, impostare Xmx come 27MB - 28MB.

  2. Utilizzare uno dei buoni algoritmi GC - CMS o G1GC e fine sintonizzare i parametri rilevanti. I prefer G1GC if you need more than 4 GB RAM for your application. Fare riferimento a questa domanda se si è scelto G1GC:

    Agressive garbage collector strategy

    Reducing JVM pause time > 1 second using UseConcMarkSweepGC

  3. Calcolare Cap su utilizzo della memoria da soli invece di controllare memoria libera. Aggiungi memoria utilizzata e memoria da allocare.Subtract it from your own cap like 90% of Xmx. Se la memoria disponibile è ancora disponibile, concedere la richiesta di allocazione di memoria.

+0

I primi due elementi sono irrilevanti – Basilevs

+0

Se avesse una RAM enorme e la memoria da allocare non è più , il punto 1 è valido. Se ha bisogno di tempi di risposta più rapidi, il punto 2 è valido. –

+1

I punti validi possono essere irrilevanti. La domanda non si interroga sulle prestazioni, il problema è la previsione dei guasti e l'errore può verificarsi su qualsiasi configurazione di Gc semplicemente perché la richiesta è troppo grande. – Basilevs

0

Come lei ha giustamente osservato, utilizzando freeMemory non vi dirà la quantità di memoria che può essere liberato da Java Garbage Collection. È possibile eseguire i test di carico e comprendere il modello di utilizzo dell'heap JVM e l'allocazione della memoria, il modello di de-allocazione utilizzando strumenti come JConsole, VisualVM, jstat e l'opzione printGCStats in JVM. Ciò darà un'idea sul calcolo dello empirical factor in modo più accurato, in pratica capisce qual è il modello di carico che può essere gestito dall'applicazione java. Il prossimo sarebbe scegliere il giusto GC e ottimizzare le impostazioni GC di base per una migliore efficienza. Questa non è una soluzione rapida, ma forse a lungo termine una soluzione migliore.

L'altro modo di essere uccidere il tuo JVM con -XX:OnOutOfMemoryError="kill -9 %p" impostazione JVM, una volta OOM accade, e poi scrivere, resue uno script di monitoraggio processo semplice per portare il vostro JVM se è basso.

+0

piuttosto sorpreso dai voti bassi per questa risposta; Potrebbe sembrare che chiamare System.gc e controllare freeMem sia una soluzione, ma non è una [buona idea] (http://stackoverflow.com/questions/2414105/why-is-it-bad-practice-to-call -system-gc) nel codice di produzione. Il codice Java commerciale, di solito deve lottare con perdite di memoria, frequenti GC di vecchia generazione che rallentano il sistema ecc. Non c'è un vero e proprio baco magico qui, piuttosto che fare un test di carico dedicato per capire l'utilizzo della memoria nell'applicazione come primo passo, arrivando a la causa principale insieme alla sintonizzazione GC –

1

Un approccio alternativo consiste nell'isolare ogni carico di dati nella propria JVM. È sufficiente predefinire ciascuna dimensione massima dell'heap di JVM e così via e impostare il numero di JVM per host in modo che ciascuna JVM possa occupare tutta la sua dimensione massima dell'heap. Ciò userà un po 'più risorse — significa che non è possibile utilizzare ogni ultimo byte di memoria inserendo più carichi di memoria a bassa memoria-carichi — ma questo semplifica enormemente il problema (e riduce il rischio di sbagliare), Rende possibile dire quando è necessario aggiungere nuovi host e, cosa più importante, riduce l'impatto che un singolo client può avere su tutti gli altri client.

Con questo approccio, una data JVM è "occupata" o "disponibile".

Al termine di un dato caricamento di dati, la JVM pertinente può dichiararsi disponibile per un nuovo carico di dati o semplicemente chiuderla. (In entrambi i casi, ti consigliamo di avere un processo separato di monitorare i JVM e fare in modo che il numero di diritti sono sempre in esecuzione.)

0

I clienti possono caricare i dati-set e abbiamo bisogno di selezionare il nodo sul quale il set di dati verrà caricato e si rifiuterà di caricare/evitare un errore OOM se non esiste una macchina che possa adattarsi al set di dati.

Questo è un problema di pianificazione del lavoro, ad esempio Ho risorse limitate come utilizzarle al meglio. Otterrò il problema OOM vicino alla fine.

Abbiamo uno dei fattori principali ie RAM, ma le soluzioni ai problemi di programmazione dipendono da molti fattori come per esempio ...

  1. sono i lavori ovvero piccole o grandi ci sono centinaia/migliaia di questi in esecuzione su un nodo o due o tre. Pensa allo scheduler di Linux.

  2. Devono essere completati in un determinato periodo di tempo? Programmatore in tempo reale.

Dato tutto ciò che conosciamo all'inizio di un lavoro possiamo prevedere quando un lavoro si concluderà con entro un certo periodo di tempo? Se possiamo prevedere che su Node X liberiamo 100 MB ogni 15 - 20 secondi, abbiamo un modo per pianificare un lavoro da 200 Mb su quel nodo, ovvero sono sicuro che in 40 secondi avrò completato 200Mb di spazio su quel nodo e il 40 secondi è un limite accettabile per la persona o la macchina che invia il lavoro.

Supponiamo di avere una funzione come segue.

predicted_time predict(long bytes[, factors]); 

Il factors sono le altre cose che ci sarebbe bisogno di prendere in considerazione che ho citato sopra e per ogni applicazione ci saranno cose che si possono aggiungere per soddisfare il vostro scenario.

Per calcolare i fattori nel calcolo predicted_time.

predicted_time è il numero di millisecondi (può essere qualsiasi TimeUnit) che questo nodo crede da ora che può servire questa attività, il nodo che ti dà il numero più piccolo è il nodo su cui il lavoro dovrebbe essere programmato. È quindi possibile utilizzare questa funzione come segue quando abbiamo una coda di attività, ad esempio, nel seguente codice this.nodes[i] rappresenta un'istanza JVM.

private void scheduleTask() { 
    while(WorkEvent()) { 
     while(!this.queue.isEmpty()) { 
      Task t = this.queue.poll(); 
      for (int i = 0; i < this.maxNodes; i++) { 
       long predicted_time = this.nodes[i].predict(t); 
       if (predicted_time < 0) { 
        boolean b = this.queue.offer(t); 
        assert(b); 
        break; 
       } 
       if (predicted_time <= USER_EXPERIENCE_DELAY) { 
        this.nodes[i].addTask(t); 
        break; 
       } 
       alert_user(boolean b = this.queue.offer(t); 
       assert(b); 
      } 
     } 
    } 
} 

Se predicted_time < 0 abbiamo un errore, abbiamo riprogrammare il lavoro, in realtà ci piacerebbe sapere perché ma questo non è difficile aggiungere. Se il predicted_time <= USER_EXPERIENCE_DELAY il lavoro può essere pianificato.

Come funziona questo evitare un OOM

Siamo in grado di raccogliere qualsiasi statistica che vogliamo dal nostro scheduler cioè quanti posti di lavoro di dimensioni X, dove pianificato in modo corretto, l'obiettivo sarebbe quello di ridurre gli errori e per renderlo più affidabile nel tempo, ovvero ridurre il numero di volte in cui diciamo a un cliente che il proprio lavoro non può essere riparato. Quello che abbiamo fatto è ridurre il problema a qualcosa che possiamo migliorare statisticamente verso una soluzione ottimale.

0

I clienti possono caricare i dati-set e abbiamo bisogno di selezionare il nodo su cui verrà caricato il set di dati e si rifiutano di caricare/evitare un errore di OOM se non c'è una macchina che potrebbe montare il set di dati.

Questo è un problema di pianificazione del lavoro, ad esempio Ho risorse limitate come utilizzarle al meglio. Otterrò il problema OOM vicino alla fine.

Abbiamo uno dei fattori principali ie RAM, ma le soluzioni ai problemi di programmazione dipendono da molti fattori come per esempio ...

  1. sono i lavori ovvero piccole o grandi ci sono centinaia/migliaia di questi in esecuzione su un nodo o due o tre. Pensa allo scheduler di Linux.

  2. Devono essere completati in un determinato periodo di tempo? Programmatore in tempo reale.

Dato tutto ciò che conosciamo all'inizio di un lavoro possiamo prevedere quando un lavoro si concluderà entro un certo periodo di tempo? Se possiamo prevedere che su Node X liberiamo 100 MB ogni 15 - 20 secondi, abbiamo un modo per pianificare un lavoro da 200 Mb su quel nodo, ovvero sono sicuro che in 40 secondi avrò completato 200Mb di spazio su quel nodo e il 40 secondi è un limite accettabile per la persona o la macchina che invia il lavoro.

Supponiamo di avere una funzione come segue.

predicted_time predict(long bytes[, factors]); 

Il factors sono le altre cose che ci sarebbe bisogno di prendere in considerazione che ho citato sopra e per ogni applicazione ci saranno cose che si possono aggiungere in base alle proprie scenario cioè quanti fattori sta a voi.

Per calcolare i fattori nel calcolo predicted_time.

predicted_time è il numero di millisecondi (può essere qualsiasi TimeUnit) che questo nodo crede da ora che può servire questa attività, il nodo che ti dà il numero più piccolo è il nodo su cui il lavoro dovrebbe essere programmato. È quindi possibile utilizzare questa funzione come segue quando abbiamo una coda di attività, ad esempio, nel seguente codice this.nodes[i] rappresenta un'istanza JVM.

private void scheduleTask() { 
    while(WorkEvent()) { 
     while(!this.queue.isEmpty()) { 
      Task t = this.queue.poll(); 
      for (int i = 0; i < this.maxNodes; i++) { 
       long predicted_time = this.nodes[i].predict(t); 
       if (predicted_time < 0) { 
        boolean b = this.queue.offer(t); 
        assert(b); 
        break; 
       } 
       if (predicted_time <= USER_EXPERIENCE_DELAY) { 
        this.nodes[i].addTask(t); 
        break; 
       } 
       alert_user(boolean b = this.queue.offer(t); 
       assert(b); 
      } 
     } 
    } 
} 

Se predicted_time < 0 abbiamo un errore, abbiamo riprogrammare il lavoro, in realtà ci piacerebbe sapere perché ma questo non è difficile aggiungere. Se il predicted_time <= USER_EXPERIENCE_DELAY il lavoro può essere pianificato.

Come funziona questo evitare un OOM

Siamo in grado di raccogliere qualsiasi statistica che vogliamo dal nostro scheduler cioè quanti posti di lavoro di dimensioni X, dove pianificato in modo corretto, l'obiettivo sarebbe quello di ridurre gli errori e per renderlo più affidabile nel tempo, ovvero ridurre il numero di volte in cui diciamo a un cliente che il suo lavoro non può essere assistito o che non ha funzionato. Quello che abbiamo fatto o almeno tentiamo di tentare è di ridurre il problema a qualcosa che possiamo migliorare statisticamente verso una soluzione ottimale.

1

alternativa potrebbe essere quella di "solo cercare di caricare il set di dati" (e tornare indietro se un OOM è gettato), tuttavia una volta che un OOM è gettato, è potenzialmente corrotto altri thread in esecuzione nella stessa JVM e non c'è modo aggraziato di riprendersi da esso.

Non c'è un buon sistema per gestire e recuperare da OOME in JVM, ma non c'è modo di reagire prima OOM accade. Java ha java.lang.ref.SoftReference che è garantito che è stato cancellato prima che la macchina virtuale lanci un OutOfMemoryError. Questo fatto può essere utilizzato per la predizione anticipata di OOM. Ad esempio, il caricamento dei dati può essere interrotto se viene attivata la previsione.

ReferenceQueue<Object> q = new ReferenceQueue<>(); 
    SoftReference<Object> reference = new SoftReference<>(new Object(), q); 
    q.remove(); 
    // reference removed - stop data load immediately 

La sensibilità può essere sintonizzato con -XX: bandierina SoftRefLRUPolicyMSPerMB (per Oracle JVM). Soluzione non ideale, l'efficacia dipende da vari fattori: altri riferimenti software utilizzati nel codice, come si sintonizza GC, versione JVM, meteo su Marte ... Ma può essere d'aiuto se si è fortunati.