Согласованность данных (1) — согласованность вызовов интерфейса

задняя часть GitHub API RabbitMQ

github

Сцены

Клиент вызывает интерфейс службы A, а интерфейс службы A вызывает службу B. Если и служба A, и служба B выполняются успешно, это означает, что обе транзакции должны быть зафиксированы; В случае сбоя службы A или службы B происходит сбой, и ни одна транзакция не выполняется и не откатывается. Из-за ненадежности сетевых запросов, если A не может позвонить B, он может: 1. B не получает сетевой запрос; 2. B не может выполнить после его получения; 3. B выполняется успешно, и запрос возвращается с ошибкой. исключение. 4. B вызывает тайм-аут, B может преуспеть или потерпеть неудачу. Поэтому, когда А звонит Б, могут возникнуть несоответствия.

Несколько способов вызова интерфейса

Способ 1: симулировать звонки напрямую

Интерфейс услуг:

try {
    业务代码A
    feign调用B服务接口
    事务提交
} catch (Exception e) {
    事务回滚
}

Сервисный интерфейс Б:

try {
    业务代码B
    事务提交
    //此时接口状态码2XX
} catch (Exception e) {
    事务回滚
    //此时接口状态码非2XX
}

Анализ консистенции:

  1. feign успешно вызывает сервисный интерфейс B, и код состояния — 2XX. Затем транзакция службы B была зафиксирована, и транзакция была зафиксирована A. Если транзакция A успешно отправлена, она непротиворечива; если транзакция A не может быть отправлена, но транзакция в B была отправлена ​​в это время, она несогласованна.
  2. B не получает сетевой запрос. B не выполняется, вызов feign выдает исключение, а транзакция A не фиксируется, она откатывается, и данные непротиворечивы.
  3. B выполняется успешно, и при возврате запроса возникает исключение. Транзакция B зафиксирована, ложно вызывает интерфейс службы B ненормально, транзакция A откатывается, а данные несовместимы.
  4. Превышено время ожидания вызова B. Возможно, B не получил сетевой запрос, или возможно, что B успешно выполнился и запрос вернулся ненормально, или возможно, что B получил запрос и ответил медленно. Состояние согласованности неопределенно и возможно.

Второй способ: на основе надежного сообщения

После выполнения бизнес-кода службы сообщение отправляется в очередь сообщений, такую ​​как rabbitmq, и используется ack, чтобы убедиться, что отправка прошла успешно; После того, как служба B успешно получает и потребляет, она вручную подтверждает сообщение, и kafka вручную отправляет смещение.

Производитель услуг:

try {
    业务代码A
    ack = rabbitmqProducer.sendAndGetAck
    if !ack {
        //发送失败可以设置自动重试,不重试就抛出异常
        throws new RabbitmqSendException
    }
    事务提交
} catch (Exception e) {
    //事务回滚
}

Потребитель очереди сообщений службы B 1:

//
try {
    业务代码B
    channel.basicAck手动确认//kafka则手动提交位移
    事务提交
    //此时接口状态码2XX
} catch (Exception e) {
    事务回滚
    //此时接口状态码非2XX
}

Второй потребитель очереди сообщений службы B:

//
try {
    业务代码B
    事务提交
    //此时接口状态码2XX
} catch (Exception e) {
    事务回滚
    //此时接口状态码非2XX
}
channel.basicAck手动确认//kafka则手动提交位移

Анализ консистенции:

  1. Служба A успешно отправляет сообщение в очередь сообщений, но не может отправить транзакцию, что приводит к несогласованности данных.
  2. Сторона потребителя A B: после того, как сообщение с подтверждением вручную удалено из очереди сообщений, фиксация транзакции B завершается неудачно, несогласованность данных.
  3. Сторона потребителя B 2: транзакция B успешно отправлена, но ручное подтверждение не удается, и сообщение может быть получено повторно, что приводит к несогласованности. В это время к сообщению можно добавить uuid.После получения сообщения сервис B выполняет дедупликацию и повторную обработку в соответствии с uuid для достижения идемпотентности.

Способ 3: предварительное выполнение + подтверждение + возврат, аналогичный TCC

Службе A необходимо вставить таблицу transaction_record, чтобы записать статус вызова и предоставить его службе B в качестве обратного вызова.

Бизнес-интерфейс сервиса:

String uuid = generateUUID()//生成一个uuid
try{
    feign.preCreate(uuid,...)//feign调用B预执行,比如B服务为创建订单服务,预创建一个订单,但状态为待确认
    业务代码A
    将uuid插入transaction_record表中
    事务提交
}catch (Exception e) {
    事务回滚
    feign.cancel(uuid)//feign调用B取消,比如B服务为创建订单服务,设置该订单状态为取消
}

feign.confirm(uuid)//feign调用B确认,比如B服务为创建订单服务,设置该订单状态为确认,此时订单可用 

Интерфейс проверки службы службы предоставляется для состояния проверки службы B:

get /v1/transaction/{uuid}

从transaction_record表中查询,有则返回确认,没有则返回取消

Сервис B должен предоставлять интерфейсы предварительного выполнения, подтверждения и отмены. Если подтверждение или отмена задерживаются после предварительного выполнения, B свяжется с A и подтвердит или отменит в зависимости от результата.

Анализ консистенции:

  1. Выполнение preCreate в A ненормально. Исключение должно быть выброшено, больше нет бизнес-кода и uuid для вставки таблицы событий, чтобы выполнить отмену. Если выполняется B, статус также неподтвержденный, что не влияет на непротиворечивость; Если отменить также не удается, например, когда B в это время кладет трубку, B должен вызвать интерфейс проверки службы A после перезапуска, чтобы определить статус. Состояние такое же.
  2. Бизнес-код A не выполняется, транзакция откатывается, и симулируется вызов B для отмены. Если отмена прошла успешно, статус такой же, если отмена не удалась, должно быть выброшено исключение, и подтверждение выполнено не будет. Состояние такое же.
  3. Если бизнес-код A успешно выполнен, фиксация транзакции завершается ошибкой, и транзакция откатывается. Если откат не удался, выдается исключение, отмена и подтверждение больше не выполняются, а B выполнит проверку тайм-аута, чтобы определить статус; если откат успешен, отмена. Состояние такое же.
  4. Транзакция успешно отправлена, но подтверждение не выполнено (например, когда A выполняет подтверждение, служба A или B просто зависает). Затем служба B проверяет через некоторое время и обнаруживает, что uuid существует, и статус модификации подтверждается. Состояние такое же.
  5. После завершения предварительной обработки выполните бизнес-код. Если бизнес-код выполняется медленно, и служба B считает, что время ожидания истекло, служба B через некоторое время проверит обратно. Если транзакция A не была отправлена, интерфейс проверки Вернется отменить. Затем B отменяется, но A фиксирует транзакцию, и в это время состояние транзакции несовместимо. В это время его можно отрегулировать, установив чуть больший период ожидания для B, чтобы служба A могла пройти в ожидаемый период ожидания при вызове предварительно обработанной имитации.

Суммировать

Выше находится несколько способов позвонить между интерфейсами. Вот только вероятность идеи. Вы можете оптимизировать себя при применении, и вы можете изменить его в асинхронную + повторение. Если требования к согласованности могут быть использованы, таблица UUID + INSERT также может быть реализована другими способами. Чтобы улучшить консистенцию, позвоните по интерфейсу также должно быть как мощность и т. Д., И может пройти мощность бизнес-логики или UUID реализация.