Cosa devo fare quando il blocco ottimistico non funziona?


11

Ho questo scenario seguente:

  1. Un utente invia una richiesta GET a /projects/1e riceve un ETag .
  2. L'utente invia una richiesta PUT a /projects/1con ETag dal passaggio 1.
  3. L'utente invia un'altra richiesta PUT a /projects/1con ETag dal passaggio 1.

Normalmente, la seconda richiesta PUT riceverebbe una risposta 412, poiché l'ETag è ormai obsoleto: la prima richiesta PUT ha modificato la risorsa, quindi l'ETag non corrisponde più.

Ma cosa succede se le due richieste PUT vengono inviate contemporaneamente (o esattamente una dopo l'altra)? La prima richiesta PUT non ha il tempo di elaborare e aggiornare la risorsa prima dell'arrivo di PUT # 2, che fa sì che PUT # 2 sovrascriva PUT # 1. L'intero punto del blocco ottimistico è che ciò non accada ...


3
Atomizza le tue operazioni in transazioni a livello aziendale, come spiegato di seguito da Esben.
Robert Harvey,

Cosa succederebbe se atomizzassi le mie operazioni usando le transazioni? PUT # 2 non verrebbe elaborato fino a quando PUT # 1 non sarà completamente elaborato?
maximedupre

7
Diventa un pessimista?
jpmc26,

bene questo è a cosa serve il bloccaggio.
Fattie,

Corretto, ovviamente Put # 2 non dovrebbe essere elaborato - dovrebbero essere unici.
Fattie,

Risposte:


21

Il meccanismo ETag specifica solo il protocollo di comunicazione per il blocco ottimistico. È responsabilità del servizio applicativo implementare il meccanismo per rilevare gli aggiornamenti simultanei per imporre il blocco ottimistico.

In un'applicazione tipica che utilizza un database, di solito lo si fa aprendo una transazione durante l'elaborazione di una richiesta PUT. Normalmente leggeresti lo stato esistente del database all'interno di quella transazione (per ottenere un blocco di lettura), verificheresti la validità di Etag e sovrascriveresti i dati (in un modo che provocherebbe un conflitto di scrittura in caso di transazioni concorrenti incompatibili), quindi impegnarsi. Se imposti correttamente la transazione, uno dei commit dovrebbe fallire perché entrambi cercheranno di aggiornare gli stessi dati contemporaneamente. Sarai quindi in grado di utilizzare questo errore di transazione per restituire 412 o ritentare la richiesta, se ha senso per l'applicazione.


Il modo in cui il server implementa attualmente il meccanismo per rilevare gli aggiornamenti simultanei è confrontando gli hash della risorsa. Il server utilizza anche le transazioni per tutte le operazioni, ma non sto acquisendo alcun blocco, che potrebbe essere la causa del problema. Tuttavia, nel tuo esempio, come può esserci un errore in uno dei commit se le transazioni utilizzano i blocchi? La seconda transazione dovrebbe essere in sospeso durante la lettura dello stato, fino alla risoluzione della prima transazione.
maximedupre

1
@maximedupre: se si utilizza la transazione, si dispone di una sorta di blocchi, sebbene possano essere blocchi impliciti (i blocchi vengono acquisiti automaticamente quando si leggono / aggiornano i campi piuttosto che esplicitamente richiesti). Il meccanismo che ho descritto sopra può essere implementato usando solo i blocchi impliciti. Come altra domanda, dipende dal database che si sta utilizzando, ma molti database moderni utilizzano MVCC (controllo di concorrenza multi versione) per consentire a più lettori e scrittori di lavorare sugli stessi campi senza bloccarsi inutilmente a vicenda.
Lie Ryan,

1
Avvertenza: in molti DBMS (PostgreSQL, Oracle, SQL Server, ecc.), Il livello di isolamento della transazione predefinito è "read commit", in cui il vostro approccio non è sufficiente a prevenire le condizioni di competizione dell'OP. In tali DMBSes, è possibile risolvere il problema inserendo AND ETag = ...nel vostro UPDATEdi dichiarazione WHEREclausola, e controllando la fila-count aggiornato in seguito. (O usando un livello di isolamento delle transazioni più rigoroso, ma non lo consiglio davvero.)
ruakh

1
@ruakh: dipende da come scrivi la tua query, sì, il livello di isolamento predefinito non fornisce questo comportamento automaticamente per tutte le query, ma spesso è possibile strutturare la tua transazione in modo tale da implementare il blocco ottimistico. Nella maggior parte dei casi, se la coerenza delle transazioni è importante nell'applicazione, consiglio comunque di ripetere la lettura come livello di isolamento predefinito; nei database che utilizzano MVCC, l'overhead della lettura ripetibile è abbastanza minimo e semplifica notevolmente l'applicazione.
Lie Ryan,

1
@ruakh: il principale svantaggio della lettura ripetibile è che dovrai essere pronto a riprovare o fallire se ci sono transazioni simultanee. Questo di solito è un problema, ma le applicazioni che forniscono il blocco ottimistico come strategia di concorrenza richiedono già questa gestione, quindi gli errori di lettura ripetibili si associano naturalmente a errori di blocco ottimistici e questo non aggiungerà effettivamente nuovi svantaggi.
Lie Ryan,

13

Devi eseguire atomicamente la seguente coppia:

  • verifica della validità del tag (ovvero aggiornato)
  • aggiornamento della risorsa (che include l'aggiornamento del relativo tag)

Altri lo chiamano transazione - ma fondamentalmente, l'esecuzione atomica di queste due operazioni è ciò che impedisce a uno di sovrascrivere l'altro per errore di tempistica; senza questo hai una condizione di gara, come stai notando.

Questo è ancora considerato un blocco ottimistico, se si guarda al quadro generale: che la risorsa stessa non è bloccata dalla lettura iniziale (GET) da qualsiasi Utente o qualsiasi Utente che sta guardando i dati, sia con l'intenzione di aggiornare o meno.

È necessario un certo comportamento atomico, ma ciò accade all'interno di una singola richiesta (PUT) anziché tentare di mantenere un blocco su più interazioni di rete; questo è un blocco ottimistico: l'oggetto non è bloccato da GET ma può ancora essere aggiornato in modo sicuro da PUT.

Esistono anche molti modi per ottenere l'esecuzione atomica di queste due operazioni: bloccare la risorsa non è l'unica opzione; ad esempio, un thread leggero o un blocco oggetti possono essere sufficienti e dipendono dall'architettura dell'applicazione e dal contesto di esecuzione.


4
+1 per notare che è l'atomico che conta. A seconda della risorsa sottostante in fase di aggiornamento, ciò può essere realizzato senza transazioni o blocco. Ad esempio, il confronto e lo scambio atomici di una risorsa in memoria o l'approvvigionamento di eventi di dati persistenti.
Aaron M. Eshbach,

@ AaronM.Eshbach, d'accordo, e grazie per averlo chiamato.
Erik Eidt,

1

Sta allo sviluppatore dell'applicazione controllare effettivamente l'E-Tag e fornire quella logica. Non è magico che il web server fa per te perché sa solo come calcolare le E-Tagintestazioni per il contenuto statico. Quindi prendiamo il tuo scenario sopra e analizziamo come dovrebbe avvenire l'interazione.

GET /projects/1

Il server riceve la richiesta, determina l'E-Tag per questa versione del record, restituendola con il contenuto effettivo.

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

Poiché il client ha ora il valore E-Tag, può includere quello con la PUTrichiesta:

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

A questo punto l'applicazione deve effettuare le seguenti operazioni:

  • Verifica che l'E-Tag sia ancora corretto: "412" == "412"?
  • In tal caso, effettua l'aggiornamento e calcola un nuovo E-Tag

Invia la risposta di successo.

204 No Content
E-Tag: "543"

Se arriva un'altra richiesta e tenta di eseguire una PUTsimile alla richiesta precedente, la seconda volta che il codice del server lo valuta, l'utente è tenuto a fornire il messaggio di errore.

  • Verifica che l'E-Tag sia ancora corretto: "412"! = "543"

In caso di errore, inviare la risposta all'errore.

412 Precondition Failed

Questo è il codice che devi effettivamente scrivere. L'etichetta elettronica può infatti essere qualsiasi testo (entro i limiti definiti nelle specifiche HTTP). Non deve essere un numero. Può anche essere un valore di hash.


Questa non è una notazione HTTP standard che stai utilizzando qui. In HTTP conforme standard, si utilizza ETag solo in un'intestazione di risposta. Non si invia mai ETag in un'intestazione della richiesta, ma si utilizza invece il valore ETag precedentemente acquisito in un'intestazione If-Match o If-None-Match nelle intestazioni della richiesta.
Lie Ryan,

-2

A complemento delle altre risposte, posterò una delle migliori citazioni nella documentazione di ZeroMQ che descrive fedelmente il problema di fondo:

Per realizzare programmi MT assolutamente perfetti (e intendo letteralmente), non abbiamo bisogno di mutex, blocchi o qualsiasi altra forma di comunicazione inter-thread eccetto i messaggi inviati attraverso i socket ZeroMQ.

Per "programmi MT perfetti" intendo un codice facile da scrivere e da capire, che funziona con lo stesso approccio progettuale in qualsiasi linguaggio di programmazione e su qualsiasi sistema operativo e che si adatta a qualsiasi numero di CPU con zero stati di attesa e nessun punto di rendimenti decrescenti.

Se hai passato anni a imparare i trucchi per far funzionare il tuo codice MT, per non parlare rapidamente, con blocchi, semafori e sezioni critiche, rimarrai disgustato quando ti rendi conto che non è tutto per niente. Se c'è una lezione che abbiamo imparato da oltre 30 anni di programmazione concorrente, è: semplicemente non condividere lo stato. È come due ubriaconi che cercano di condividere una birra. Non importa se sono buoni amici. Prima o poi entreranno in una rissa. E più ubriachi aggiungi al tavolo, più si combattono a vicenda sulla birra. La tragica maggioranza delle applicazioni MT sembrano combattimenti da bar ubriachi.

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.