Red de conocimiento informático - Material del sitio web - Cómo usar Redis para crear un bloqueo distribuido

Cómo usar Redis para crear un bloqueo distribuido

Redis tiene una serie de comandos, que terminan en NX. NX es la abreviatura de Not eXists. Por ejemplo, el comando SETNX debe entenderse como: SET if Not eXists. Esta serie de comandos es muy útil. Aquí hablamos sobre el uso de SETNX para implementar bloqueos distribuidos.

Utilice SETNX para implementar bloqueos distribuidos

Usar SETNX para implementar bloqueos distribuidos es muy sencillo. Por ejemplo: si un cliente desea obtener un bloqueo llamado foo, el cliente usa el siguiente comando para obtenerlo:

SETNX lock.foo

Si se devuelve 1, el cliente obtiene el bloqueo y establece el valor de la clave de lock.foo en el valor de tiempo para indicar que la clave ha sido bloqueada. El cliente finalmente puede liberar el bloqueo a través de DEL lock.foo.

Si se devuelve 0, significa que el bloqueo ha sido obtenido por otro cliente. En este momento, podemos regresar primero o volver a intentarlo y esperar a que la otra parte complete o esperar a que expire el tiempo de bloqueo. .

Resolución del punto muerto

Hay un problema con la lógica de bloqueo anterior: si un cliente que mantiene el bloqueo falla o falla y no puede liberar el bloqueo, ¿cómo solucionarlo? Podemos juzgar si esto ha ocurrido a través de la marca de tiempo correspondiente a la clave de bloqueo. Si la hora actual es mayor que el valor de lock.foo, significa que el bloqueo ha caducado y se puede reutilizar.

Cuando esto sucede, no puede simplemente eliminar el bloqueo a través de DEL y luego SETNX nuevamente. Cuando varios clientes detectan que el bloqueo ha expirado, intentarán liberarlo y puede ocurrir una carrera. condiciones, simulemos este escenario:

La operación C0 expiró, pero aún mantuvo el bloqueo C1 y C2 leyeron lock.foo y verificaron la marca de tiempo, y descubrieron que el tiempo de espera se produjo sucesivamente.

C1 envía DEL lock.foo

C1 envía SETNX lock.foo y tiene éxito.

C2 envía DEL lock.foo

C2 envía SETNX lock.foo y tiene éxito.

¡De esta manera, C1 y C2 obtienen el candado! ¡Gran problema!

Afortunadamente, este problema se puede evitar. Veamos cómo lo hace el cliente C3:

C3 envía SETNX lock.foo para obtener el bloqueo. Desde C0 El bloqueo es. aún se mantiene, por lo que Redis devuelve un 0 a C3

C3 envía GET lock.foo para comprobar si el bloqueo ha expirado. Si no, espera o vuelve a intentarlo.

Por el contrario, si se ha producido el tiempo de espera, C3 intenta obtener el bloqueo mediante las siguientes operaciones:

GETSET lock.foo

A través de GETSET, si la marca de tiempo obtenida por C3 aún está agotada, significa que C3 obtuvo el bloqueo como se esperaba.

Si antes de C3, un cliente llamado C4 realizó la operación anterior un paso más rápido que C3, entonces la marca de tiempo obtenida por C3 es un valor que no ha expirado. En este momento, C3 no obtuvo el bloqueo. Como era de esperar, es necesario esperar de nuevo o intentarlo de nuevo. Tenga en cuenta que, aunque C3 no obtuvo el bloqueo, reescribió el valor de tiempo de espera de bloqueo establecido por C4, pero el impacto de este pequeño error es insignificante.

Nota: Para que el algoritmo de bloqueo distribuido sea más estable, el cliente que mantiene el bloqueo debe verificar nuevamente si su bloqueo se agotó antes de desbloquearlo y luego realizar la operación DEL, porque el cliente puede El terminal Se bloquea debido a una operación que requiere mucho tiempo. Cuando se completa la operación, otra persona obtuvo el bloqueo debido al tiempo de espera y no es necesario desbloquearlo en este momento.

Pseudocódigo de muestra

Basado en el código anterior, escribí un pequeño fragmento de código falso para describir todo el proceso de uso de bloqueos distribuidos:

# get lock

bloqueo = 0

mientras bloqueo!= 1:

marca de tiempo = hora actual de Unix + tiempo de espera de bloqueo + 1

bloqueo = SETNX lock .foo timestamp

if lock == 1 o (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):

break

else:

dormir(10ms)

# haz tu trabajo

do_job()

# release

if now() < GET lock.foo:

DEL lock.foo

Sí, si quieres que esta lógica sea reutilizable, puedes usar Inmediatamente me vino a la mente Python Decorator, y si usas Java, ¿también pensaste en esa persona? ¿AOP + anotación? Bien, úsalo como te sientas cómodo, pero no repita el código.