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, suponiendo que todo funcione. 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 liviano sobre cómo administrar los errores en este sistema. Tenga en cuenta que tenemos muchos componentes, por lo que agruparlos todos solo por el manejo de errores no tiene sentido. Luego, abandoné el uso de cualquier memoria distribuida / repositorio que se instalaría nuevamente en cada componente por la misma razón.
Mis pensamientos van en la dirección de tener un estado absoluto en una B y nunca tener un estado persistente en una A
. Esto significa lo siguiente:
- antes de
#1
marcar enA
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 sobreB
cualquier momento para actualizar el estado.- no se puede invocar ningún cambio nuevo en la misma unidad
A
.
¿Qué piensas? ¿Hay alguna forma ligera de domar los errores en un sistema de este tipo?
Respuestas:
Agregar a un registro persistente en A debería ser suficiente. Esto hace frente a reinicios y particiones de red para lograr una consistencia eventual, o para romper la señal que impide dicha convergencia. Con el compromiso de grupo amortizado , puede tomar menos de una sola escritura para persistir una entrada de registro.
Usted sugirió que B sea responsable del estado de desmarcado. Estoy en desacuerdo. Solo A se da cuenta del nuevo trabajo, y solo A debe ser responsable de rastrearlo y reportar errores como 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 más adelante en un plazo determinado: A realizará continuamente y repetirá los pasos posteriores 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. Dichas estimaciones pueden retroalimentarse a A, por lo que rara vez producirá tiempos de espera falsos positivos. Piense en ello como un mensaje para mantener vivo que dice "sigue trabajando, sigue trabajando".
fuente
Adopte una estrategia de atracción en lugar de empujar. Haga que cada parte extraiga los cambios de las otras y actualice sus propios registros.
(Estoy usando la palabra cola, pero puede sustituir el registro o el tema).
Puede hornear la cola en los servicios o puede tener un agente de mensajes 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 complicada de dicha arquitectura es decidir sobre la semántica al menos una vez frente a la mayoría. Concretamente: si B saca un elemento de la cola y luego se bloquea mientras lo realiza, ¿qué debería pasar? Hay dos posibilidades, y la más adecuada depende de su caso de uso:
Beneficios de este enfoque:
fuente