2011-03-24 10 views
5

En mi solicitud rieles, tengo algo de código como este:¿Cómo puedo bloquear un conjunto de objetos para una operación?

def foo 
    if object_bar_exists 
    raise "can't create bar twice!" 
    end 

    Bar.create 
end 

que podría ser invocada por dos solicitudes diferentes que entran en el servidor de aplicaciones. Si este código es ejecutado por dos solicitudes al mismo tiempo, y ambas ejecutan la verificación if al mismo tiempo, ninguna de las dos encontrará bar, y se crearán 2 bar s.

¿Cuál es la mejor manera de crear un "mutex" para "la colección de barras"? ¿Una tabla mutex de propósito especial en DB?

actualización

Debo hacer hincapié en que no puedo utilizar un mutex de memoria aquí, debido a que la concurrencia es a través de las peticiones/procesos y no hilos.

Respuesta

4

Lo mejor que puede hacer es realizar sus operaciones en una transacción DB. Como es probable que tarde o temprano tenga varias aplicaciones en ejecución y posiblemente no compartan memoria, no podrá crear un bloqueo Mutex en el nivel de la aplicación, especialmente si esos dos servicios de aplicaciones se ejecutan en cajas físicas completamente diferentes. Aquí es cómo llevar a cabo la transacción DB:

ActiveRecord::Base.transaction do 
    # Transaction code goes here. 
end 

Si desea asegurarse de una reversión de la transacción DB entonces usted tiene que tener la validación habilitada en la clase de barra de modo que un inválido Guardar solicitud provocará un retroceso:

ActiveRecord::Base.transaction do 
    bar = Bar.new(params[:bar]) 
    bar.save! 
end 

Si ya tiene un objeto de bar en la base de datos, puede bloquear ese objeto pesimista como esto:

ActiveRecord::Base.transaction do 
    bar = Bar.find(1, :lock => true) 
    # perform operations on bar 
end 
+1

Pensé que las transacciones solo aseguran que todo lo que está dentro de ellas se realiza atómicamente. ¿No puede el mismo código de uso de transacciones ser ejecutado por dos procesos en paralelo y tener los mismos problemas? (Las verificaciones iniciales de 'si' se ejecutan en paralelo y se ejecutan correctamente, y luego las escrituras ocurren en paralelo, y en ambos casos, todos los códigos respectivos ocurren atómicamente) –

+0

No si bloquea la fila. Es el nivel de fila bloqueado en el DB, por lo que otra transacción no puede acceder a él.Además, si tiene errores de validación que garantizan una fila única, la primera escritura tendrá éxito y la siguiente fallará y revertirá la transacción completa. –

+2

Realmente desea la transacción ** y ** un índice único dentro de la base de datos para forzar el problema. La transacción mantendrá todo limpio cuando las cosas van mal, el índice único le dirá cuando las cosas van mal. –

1

Si todas las solicitudes provienen de la misma máquina y de la misma máquina virtual de ruby, puede usar la clase de Mutex incorporada de Ruby: Mutex Docs.

Si hay varias máquinas o rvms, tendrá que usar una transacción de base de datos para crear/obtener el objeto Bar, asumiendo que está almacenado en el db de alguna manera.

0

que probablemente create a Singleton objeto en el directorio lib, para que solo tenga una instancia del objeto y use Mutexes para bloquearlo.

De esta manera, puede garantizar un solo acceso a la cosa en cualquier momento dado. Por supuesto, cualquier otra solicitud se bloqueará, así que eso es algo a tener en cuenta.

Para máquinas múltiples, tendría que almacenar un token en una base de datos y sincronizar algún tipo de acceso al token. Al igual, se debe consultar y extraer el token y realizar un seguimiento de algún número o algo para garantizar que las personas no puedan eliminar el token al mismo tiempo. O use un servicio web de bloqueo centralizado para que su manejo de token esté solo en un lugar.

Cuestiones relacionadas