Manejo de errores en el sistema distribuido

8

Esta es la secuencia común de dos componentes distribuidos en nuestra aplicación Java:

1  A sends request to B
2      B starts some job J in parallel thread
3      B returns response to A
4  A accepts response
5      Job finishes after some time
6      Job sends information to A
7  A receives response from a Job and updates

Este es el escenario ideal, asumiendo que todo funciona. Por supuesto, la vida real está llena de fracasos. Por ejemplo, uno de los peores casos puede ser si #6 falla simplemente debido a la red: el trabajo se ha ejecutado correctamente, pero A no sabe nada al respecto.

Estoy buscando un enfoque ligero sobre cómo gestionar los errores en este sistema. Tenga en cuenta que tenemos una gran cantidad de componentes, por lo que agruparlos a todos solo por el manejo de errores no tiene sentido. A continuación, abandoné el uso de cualquier memoria / repositorio distribuido que se instalaría de nuevo en cada componente por el mismo motivo.

Mis pensamientos van en la dirección de tener un estado absoluto en una B y nunca tener un estado persistente en un A . Esto significa lo siguiente:

  • antes de #1 marcamos en A que la unidad de trabajo, es decir, el cambio está a punto de comenzar
  • solo B puede desmarcar este estado.
  • A puede obtener información sobre el B en cualquier momento, para actualizar el estado.
  • no se puede invocar un nuevo cambio en la misma unidad en A .

¿qué te parece? ¿Hay alguna forma ligera de domar los errores en este tipo de sistema?

    
pregunta igor 11.02.2016 - 09:03

2 respuestas

2

Anexar a un inicio de sesión persistente en A debería ser suficiente. Esto hace frente a reinicios y particiones de red para lograr una consistencia eventual, o para señalar una rotura que impida dicha convergencia. Con el confirmación de grupo amortizado, puede tomar menos de un escritura única para persistir una entrada de registro.

Usted sugirió responsabilizar a B por desmarcar el estado. Estoy en desacuerdo. Solo A se da cuenta de un nuevo trabajo, y solo A debe ser responsable de rastrearlo e informar de errores como los tiempos de espera. B envía mensajes idempotentes a A, y A actualiza el estado, volviendo a consultar a intervalos según sea necesario.

En el paso 0, A se da cuenta de una nueva solicitud y la registra. Eso constituye una obligación que A debe cumplir posteriormente en algún plazo: A realizará y repetirá continuamente los pasos subsiguientes hasta que A sepa que el procesamiento de la solicitud se ha completado.

Algunas solicitudes serán más largas que otras. Las estimaciones del tiempo de procesamiento estarán disponibles en A y en B, quizás revisadas a medida que el procesamiento continúe. Tales estimaciones pueden devolverse a A, por lo que rara vez se producirán tiempos de espera falsos positivos. Piense en ello como un mensaje de "Manténgase vivo" que dice "sigue funcionando, sigue funcionando".

    
respondido por el J_H 23.11.2017 - 21:31
1

Adopta una estrategia pull en lugar de push. Haga que cada parte extraiga cambios de los demás y actualice sus propios registros.

  • A registra lo que B debería hacer en una cola
  • B saca de la cola de A y hace el trabajo
  • B registra las cosas que ha hecho en una cola
  • A extrae de la cola de B para saber cuál fue el resultado del trabajo

(Estoy usando la cola de palabras, pero puede sustituir el registro o el tema)

Puede hornear la cola en los servicios o puede tener un intermediario de mensajes por separado. Una implementación integrada en un servicio puede ser tan simple como GET /jobrequests?from=<timestamp> (con B haciendo un seguimiento de la última marca de tiempo de la solicitud de trabajo procesada).

Una parte difícil de esta arquitectura es decidir sobre la semántica al menos una vez frente a la más o menos una vez. Concretamente: si B saca un elemento de la cola y luego se bloquea mientras lo realiza, ¿qué debería suceder? Hay dos posibilidades, y la más adecuada depende de su caso de uso:

  • Al menos una vez: B solo confirma qué punto de la cola ha llegado después de completar una acción, existe el riesgo de que se realicen acciones dos veces. Si diseña acciones para que sean idempotentes, puede lograr exactamente una vez el comportamiento utilizando este enfoque. (Yo uso kafka para este escenario).
  • A lo sumo una vez: B solo consume cada elemento de la cola una vez. Si se bloquea al ejecutarlo, el elemento nunca se ejecutará.

Beneficios de este enfoque:

  • No es necesario que los servicios que consumen colas estén listos para que se produzca el envío de la cola. Esto significa que puede reiniciar B mientras A está trabajando o reiniciar A mientras B está trabajando. El alojamiento redundante de servicios en segundo plano solo es necesario para garantizar un tiempo de respuesta general, no un funcionamiento confiable.
  • El consumidor puede controlar el ritmo de extracción de elementos de la cola, lo que le permite amortiguar temporalmente los picos de carga en la cola.
respondido por el Joeri Sebrechts 23.01.2018 - 10:49

Lea otras preguntas en las etiquetas