Usamos colas junto con una base de datos: discusión de problemas, posibles soluciones

Las colas son una gran herramienta que se escala casi a la perfección. ¿El hierro no resiste? Acabamos de agregar nodos al clúster. Cuando una cola está presente en un proyecto, es tentador implementar cada vez más funcionalidades con su ayuda.





Hablaremos de las trampas de este camino en este artículo.





Tarde o temprano, al utilizar colas, el usuario se enfrenta a la cuestión de utilizarlas junto con algún servicio, base de datos, etc.





  • El pedido se completa, debe enviar una notificación por SMS al usuario.





  • Ha llegado una nueva orden, debe enviar una notificación automática a los ejecutores.





  • El trabajo está hecho, debe cancelar el dinero de la cuenta del cliente.





En todos los ejemplos anteriores, los cambios en una entidad comercial se registran en la base de datos (o un servicio con una base de datos) y existe una gran tentación de enviar notificaciones mediante colas.





¿Qué tenemos en esta situación? Estructura de código inicial y más simple:





  • El servicio (nuestro programa) registra cambios en los datos en la base de datos.





  • Luego, el servicio coloca el trabajo en la cola.





De hecho, en este caso, debe implementar un activador de eventos para cambiar el registro de datos.





Y en el caso general, resulta que aquí tenemos dos registros en dos bases de datos diferentes: servicios y colas.





Ahora saltemos al mundo real y consideremos qué situaciones pueden surgir:





  • Todo esta bien. DB está disponible, DB de cola está disponible;





  • , ;





  • , ;





  • , .





: , , .





, , ... . .





, .





, ( , , ), , :





  1. .





  2. .





  3. .





  4. .





, , .





:





  • , . 3 4 ( ).





  • . .





.





, . : , . , , .





, (, , http- /).





, .





/, ( queue



) .





, , ( ):





/*  */
UPDATE
   "orders"
SET
   "status" = 'complete'
WHERE
   "order_id" = $1
RETURNING
   *

      
      



/*  */
WITH "o" AS (
    UPDATE
       "orders"
    SET
       "status" = 'complete'
    WHERE
       "order_id" = $1
    RETURNING
       *
),
"q" AS (
   INSERT INTO
        "queue"
   (
      "key",
      "data"
   )

   SELECT
      "o.order_id",
      "o.status"
   FROM
      "o"
)
SELECT
   *
FROM
   "o"

      
      



: queue



, , orders



.





, queue



, , . , .





:





  • queue



    .





  • .





  • .





/

, , . , - ( , ..), :





  • .





, , , , O_APPEND



- .





  • ( ) ,





  • .





  • ( ) .





, , , , .





Como puede ver, hay pocas opciones para resolver el problema. Si queremos mantener el sistema simple (principio KISS), entonces la introducción de un demonio adicional y mensajes de caché / registro en la base de datos o archivo / base de datos local dará un ligero aumento en la complejidad. Al mismo tiempo, es muy importante mantener idempotente al manejador, ya que en caso de fallas a la hora de transferir tareas de la caché local a la cola general, pueden aparecer duplicados.





Una solución generalizada es utilizar un compromiso de dos fases.








All Articles