2009-11-22 4 views
5

Abbiamo tre servizi web (/a, /b, /c) dove ogni mappe di servizio a un metodo (go()) in una classe Java separato (ClassA, ClassB, ClassC).Come impedire la concorrenza nell'API del servizio Web?

Solo un servizio deve essere eseguito allo stesso tempo (ad esempio: /b non può essere eseguito mentre è in esecuzione /a). Tuttavia, poiché questa è un'API REST, nulla impedisce ai client di richiedere l'esecuzione contemporanea dei servizi.

Qual è il metodo migliore e più semplice sul server per far rispettare che i servizi Non eseguiti contemporaneamente?


Aggiornamento: Si tratta di un'applicazione interna, non avremo un grande carico e sarà solo avere un unico application server.

Aggiornamento: Questa è una domanda soggettiva in quanto è possibile formulare argomenti diversi sulla progettazione generale dell'applicazione che influisce sulla risposta finale. Ho accettato la risposta di Overthink come ho trovato più interessante e utile.

+2

@Marcus: potrebbe essere un'app interna, ma progettare qualcosa con limiti incorporati in questa fase è una cattiva idea. Potresti * pensare * che non avrà mai bisogno di ridimensionare, ma puoi esserne sicuro? Salva te stesso il mal di testa più tardi lungo la linea e aderire ad alcune migliori pratiche! – jkp

+0

@jkp, punto preso. –

risposta

6

Supponendo che non sia giusto forzare semplicemente il server Web ad avere un solo thread di ascolto che serve le richieste ... Suppongo che dovrei semplicemente usare un blocco statico (ReentrantLock probabilmente, per chiarezza, sebbene tu possa sincronizzare su qualsiasi oggetto condiviso , davvero):

public class Global { 
    public static final Lock webLock = new ReentrantLock(); 
} 

public class ClassA { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do A stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 

public class ClassB { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do B stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 

public class ClassC { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do C stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 
+0

@ overthink: cosa succede quando vuole più livelli di servizio? Questa soluzione non verrà scalata se i blocchi sono implementati su quei livelli. – jkp

+1

@ overthink: Quindi stai dicendo che potresti usare anche i blocchi 'synchronized (sharedObject) {...}' invece delle istruzioni 'Global.webLock.lock()/unlock()'? –

+1

@Marcus: Sì, è possibile utilizzare la sincronizzazione anziché il blocco/sblocco. per esempio. nel mio esempio precedente potresti rendere 'webLock' un semplice' Object' e usare 'synchronized (webLock) {...}'. – overthink

3

In primo luogo, senza conoscere la propria architettura, è probabile che si verifichino problemi se è necessario applicare restrizioni di concorrenza sui livelli di WebService. Mentre è possibile utilizzare i blocchi tradizionali ecc per serializzare le richieste su entrambi i servizi, cosa succede quando si aggiunge un secondo livello Web per ridimensionare la propria soluzione? Se i blocchi sono locali per il livello Web, saranno quasi inutili.

Suppongo che ci sia probabilmente un livello di qualche tipo che si trova sotto i servizi Web ed è qui che è necessario applicare queste restrizioni. Se il client B entra dopo che il client A ha fatto una richiesta in conflitto, allora il backend dovrebbe respingere la richiesta quando scopre che lo stato è cambiato e si dovrebbe quindi restituire un 409 al secondo client. Alla fine, le condizioni di gara sono ancora possibili, ma devi avere il tuo livello comune più basso per proteggere dalle tue richieste in conflitto.

0

È possibile utilizzare un semaforo di qualche tipo per mantenere l'accesso tra i servizi di serie.

6

Il vostro disegno è difettoso. I servizi dovrebbero essere idempotenti. Se le classi che hai non lo supportano, ridisegnale finché non lo fanno. Sembra che ciascuno dei tre metodi dovrebbe essere la base per i servizi, non per le classi.

+0

Puoi elaborare? –

+0

L'unica ragione che posso vedere per impedire che il metodo go() in classe A, B e C venga eseguito contemporaneamente è che condividono qualcosa. Se si tratta di dati di classe o dati di database, dovresti provare a riprogettarli in modo che non condividano nulla. Ciò consentirebbe loro di funzionare contemporaneamente e di non preoccuparsi di interferire. Se ciò non è possibile, è necessario disporre di un servizio che li richiami nella sequenza corretta per garantire che non possano essere chiamati fuori servizio. In entrambi i casi, penso che il tuo design sia difettoso e che la cura sia peggiore della malattia. – duffymo

+0

+100 se potessi. C'è un grosso problema da qualche parte nel design e semafori, serrature, ecc. Non sono il modo per risolvere questo problema. –

0

Perché non utilizzare l'ipermediale per limitare l'accesso?

usare qualcosa di simile,

POST /A 

al initate il primo processo.Il quando è completa i risultati dovrebbero fornire un link da seguire per avviare il secondo processo,

<ResultsOfProcessA> 
    <Status>Complete</Status> 
    <ProcessB href="/B"/> 
</ResultsOfProcessA> 

seguire il link per initate il secondo processo,

POST /B 

e ripetere per parte C.

Probabilmente un client che si comporta male potrebbe memorizzare nella cache il collegamento al passaggio B e tentare di riutilizzarlo in alcune future richieste per eludere la sequenza. Tuttavia, non sarebbe troppo difficile assegnare un qualche tipo di token quando si esegue il passaggio A e richiedere che il token venga passato ai passaggi B e C per impedire al client di costruire manualmente l'URL.

Leggendo ulteriormente i tuoi commenti, sembra che tu abbia una situazione in cui A potrebbe essere eseguito prima o dopo B. In questo caso suggerirei di creare una risorsa D che rappresenti lo stato dell'intero insieme di processi (A, B e C). Quando un client recupera D viene presentato con gli URI che è consentito seguire. Una volta che un client ha avviato il processo A, la risorsa D dovrebbe rimuovere il collegamento B per la durata dell'elaborazione. L'opposto dovrebbe verificarsi quando viene iniziato prima B A.

L'altro vantaggio di questa tecnica è che è evidente se A o B è gestito per il giorno come lo stato può essere visualizzata in D. Quando A e B hanno stato eseguito D può contenere un collegamento per C.

L'ipermedia non è una soluzione infallibile al 100% in quanto si potrebbero avere due client con la stessa copia di D ed entrambi potrebbero pensare che il processo A non sia stato eseguito ed entrambi potrebbero tenta di eseguirlo contemporaneamente. Questo potrebbe essere risolto avendo una sorta di timestamp "Last Modified" su D e potresti aggiornare quel timestamp ogni volta che cambia lo stato di D. Ciò potrebbe consentire di negare la richiesta successiva. Sulla base della descrizione del tuo scenario sembrerebbe che si tratti più di un caso limite e l'ipermedia catturerebbe la maggior parte dei tentativi di eseguire processi in parallelo.