2016-07-10 33 views
9

Ho implementato un servizio Web con Java Servlet.Come gestire le condizioni di gara nel servizio Web?

Ho ottenuto la seguente configurazione: C'è un database che gestisce le voci "lavoro". Ogni lavoro ha uno stato come "in esecuzione" o "in coda" o "finito". Se un utente inizia un nuovo lavoro, viene creata una voce nel database con un lavoro e lo stato 'in coda'.

Il lavoro deve essere eseguito solo se sono già stati eseguiti meno di altri cinque lavori. Se ci sono altri cinque già in esecuzione lo stato deve rimanere "in coda" e un Cronjob gestirà l'esecuzione di questo lavoro in seguito.

Ora mi chiedo solo che se ci sono meno di cinque lavori in esecuzione al momento, il mio script eseguirà questo lavoro. Ma cosa succede se, allo stesso tempo, tra il mio script che chiede al database quanti lavori vengono eseguiti e lo script che inizia a eseguire il lavoro, un'altra richiesta da un altro utente crea un lavoro e ottiene anche "quattro processi in esecuzione" come risultato Banca dati.

Quindi ci sarebbe una condizione di gara e verrebbero eseguiti 6 lavori.

Come posso evitare qualcosa del genere? Qualche consiglio? Grazie mille!

+1

Quindi non si sta utilizzando EJB (o forse Spring) per il livello di servizio? Quelli hanno strutture integrate per questo. Questo almeno non è responsabilità di un servlet (che sta semplicemente controllando le richieste/risposte HTTP). – BalusC

+0

@BalusC Purtroppo sono molto nuovo all'intera programmazione di Java Servlet. Ho solo un servlet che ottiene la richiesta di aggiungere un lavoro all'utente. Questo Sevlet esegue anche il lavoro se è disponibile uno slot libero. – progNewbie

+0

Il tuo processo non è chiaro. Parli di "script". Che cos'è? Il responsabile del lavoro e il creatore del lavoro si trovano nella stessa app? – davidxxx

risposta

7

se ho capito bene e si ha il controllo sul livello di applicazione che rende le richieste al DB è possibile utilizzare semafori per controllare chi accede al DB.

I semafori, in un certo senso, sono come semafori. Danno accesso al codice critico per solo N thread. Quindi, è possibile impostare N a 5 e consentire solo ai thread nel codice critico di modificare il loro stato su executing ecc.

Here è un bel tutorial sull'utilizzo di essi.

1

Modifica: ho capito la tua domanda ora. Faccio un'altra risposta :)

Sì, potresti avere condizioni di gara. È possibile utilizzare un blocco del database per gestirli. Se il record non è spesso accessibile in modo concorrente, guarda il blocco pessimistico. Se si accede spesso al record in modo concorrente, osservare il blocco ottimistico.

+0

Il mio problema è che il servlet che crea ed esegue il lavoro può essere chiamato più volte da più utenti. Forse allo stesso tempo. Quindi non so come sono programmati i processi sul sistema. Non potrebbe esserci se ci sono due richieste di esecuzione del lavoro differenti, che entrambi vedono che ci sono solo 4 lavori già in esecuzione e iniziano ad eseguirli, quindi dando un totale di 6 lavori in esecuzione? – progNewbie

3

È possibile utilizzare il blocco del record per controllare la concorrenza. Un modo per farlo è eseguendo la query "seleziona per aggiornamento".

L'applicazione deve disporre di un'altra tabella che memorizzi worker_count. E poi il tuo servlet deve fare come segue:

  1. Ottenere la connessione al database

  2. Spegnere commit automatico

  3. Inserire il lavoro con 'IN CODA' stato

  4. Execute " selezionare worker_cnt da ... per l'aggiornamento "query.

(a questo punto altri utenti che eseguono la stessa query dovranno aspettare fino a quando ci impegniamo)

  1. Leggi valore worker_cnt

  2. Se worker_cnt > = 5 commit e esci.

(a questo punto si ottiene il biglietto per eseguire il lavoro, ma altri utenti ancora aspettando)

  1. Aggiornamento del lavoro per 'ESECUZIONE'

  2. Incremento worker_cnt

  3. commit.

(a questo punto gli altri utenti possono continuare la loro query e avranno worker_cnt aggiornato)

  1. fare eseguire il lavoro

  2. Aggiornare il lavoro 'finito'

  3. Decremento worker_cnt

  4. commettere di nuovo

  5. chiudere la connessione al database

1

Guy Grin ha ragione, quello che stai chiamando è una situazione di mutua esclusione che può essere risolta con semaphores. Questo costrutto di Dijkstra dovrebbe risolvere il tuo problema.

Questo costrutto di solito è inteso per l'uso con codice, che può essere eseguito solo con il processo uno alla volta. Le situazioni di esempio sono esattamente ciò che sembri essere di fronte; per esempio. transazioni di database che devono assicurarsi di non incappare in aggiornamenti persi o letture sporche. Perché esattamente vuoi 5 esecuzioni simultanee? Sei sicuro di non incontrare esattamente questi problemi quando autorizzi l'esecuzione simultanea?

L'idea di base è quella di avere una cosiddetta sezione critica nel codice che deve essere protetta dalle condizioni di gara risp. ha bisogno di gestione dell'esclusione reciproca. Questa parte del tuo codice è contrassegnata come critica e prima della sua esecuzione dice ad altre parti che vogliono anche chiamarla a wait(). Appena terminato di fare la sua magia chiama notify() e un gestore interno ora consente il successivo processo in linea per eseguire la sezione critica.

Ma:

  • consiglio vivamente di non attuare QUALSIASI reciproca movimentazione approccio da soli esclusione.In una lezione di informatica teorica alcuni anni fa abbiamo analizzato queste costruzioni a livello di OS e abbiamo dimostrato cosa può andare storto. Anche se a prima vista sembra semplice, c'è di più rispetto all'occhio e, a seconda del linguaggio, è davvero difficile farlo bene se lo fai tu stesso. Soprattutto in Java e nelle lingue correlate in cui non si ha il controllo su ciò che sta facendo la VM sottostante. Invece ci sono soluzioni preimplementate out-of-the-box che sono già testate e dimostrate corrette.

  • Prima di gestire l'esclusione reciproca in un ambiente produttivo, leggetene un po 'e assicuratevi di capire cosa implica. Per esempio. c'è The Little Book of Semaphores che è un riferimento ben scritto e piacevole da leggere. Almeno guardiamolo.

io non sono del tutto sicuro di servlet Java, ma Java ha una soluzione out-of-the-box per le esclusioni reciproche in una parola chiave chiamato synchronized per contrassegnare le sezioni critiche nel codice che non sono consentiti da eseguire contemporaneamente da diversi processi. Non ci sarà bisogno di librerie esterne.

Un bel codice di esempio viene fornito nel post precedente su SO2 this. Anche se è già stato detto, lascia che ti ricordi di usare veramente lo notifyAll() se gestisci diversi produttori/consumatori, altrimenti accadranno cose strane e arriveranno dei processi selvaggi in preda alla fame che uccideranno il tuo gatto.

Un altro tutorial più grande sull'argomento può essere trovato here.

1

Come altre persone hanno risposto, questa situazione richiede un semaforo o un mutex. L'unica area in cui penso che tu voglia stare attento è, dove vive l'autorevole Mutex. A seconda della situazione, si potrebbero avere diverse soluzioni ottimali (sicurezza di compravendita rispetto a prestazioni/complessità):

a) Se si dispone di un solo server (non in cluster) e l'unico caso d'uso per la modifica del Il database si trova attraverso il servlet, quindi è possibile implementare un mutex statico in memoria (un oggetto comune a cui è possibile sincronizzare l'accesso). Questo avrà il minor impatto sulle prestazioni e sarebbe il più facile da mantenere (perché tutto il codice pertinente è nel tuo progetto). Inoltre, non dipende dalle idiosincrasie del database specifico che si sta utilizzando. Permette anche di bloccare l'accesso agli oggetti non di database.

b) Se si dispone di diversi server separati, ma sono tutte istanze del proprio codice, è possibile implementare un servizio di sincronizzazione, che consente all'istanza specifica di ottenere il blocco (probabilmente con un timeout), prima che sia permesso di aggiornare il database. Questo sarà un po 'più complesso, ma tutta la logica risiederà nel tuo codice e la soluzione sarà trasferibile tra i vari tipi di database.

c) Se il database può essere aggiornato dal server o da un processo back-end diverso (ad esempio un ETL), l'unico modo è implementare il blocco a livello di record nel DB. Se si esegue questa operazione, si dipenderà dal tipo specifico di supporto fornito dal database e sarà probabilmente necessario apportare modifiche se si verifica il porting su un DB diverso. A mio avviso, questa è l'opzione più complessa, la meno gestibile, e dovrebbe essere presa solo se le condizioni per c) sono vere inequivocabilmente vere.

1

La risposta è implicita nella tua domanda: le tue richieste devono essere accodate in modo da costruire una coda di fifo con produttori e consumatori.

Il servlet aggiunge sempre dei lavori in coda (facoltativamente controlla se è pieno) e altri 5 thread estraggono un lavoro alla volta o dormono se la coda è vuota.

Non è necessario utilizzare cron o mutex per questo, basta ricordare di sincronizzare la coda oppure i consumatori possono estrarre lo stesso lavoro due volte.

0

A mio parere, anche se non si utilizza ExecutorService, sarà più semplice ottenere la logica se si aggiorna sempre il database e si avvia il lavoro da un singolo thread. È possibile organizzare l'esecuzione dei lavori in una coda e disporre di un thread per eseguire e aggiornare lo stato del database sul modulo corretto.

Se si desidera controllare il numero di lavori in esecuzione. Un modo per farlo è utilizzare ExecutorsService con FixedThreadPool di 5. In questo modo si è sicuri che solo 5 lavori verranno eseguiti contemporaneamente e non più ... Tutti gli altri lavori verranno accodati all'interno di ExecutorService.

Alcuni dei miei colleghi vi indicheranno API di concorrenza di basso livello. Credo che questi non siano pensati per risolvere problemi di programmazione generale. Qualunque cosa tu decida di fare Prova a utilizzare un'API di livello superiore e non approfondire i dettagli. La maggior parte delle cose di basso livello è già implementata all'interno dei framework esistenti e dubito che lo farai meglio.

Problemi correlati