1. Введение
Распределенная транзакция — это техническая трудность в интеграции предприятия, а также вещь, связанная с каждой распределенной системной архитектурой, особенно в архитектуре микросервисов, которая становится все более и более популярной в последние годы. фокусируется на одномашинных транзакциях, распределенных транзакциях и методах обработки распределенных транзакций.
2 транзакции
Транзакции обеспечивают механизм «ничего не делать или делать полный набор (все или ничего)», и у нее есть четыре свойства ACID.
- Атомарность: транзакция выполняется как единое целое, а содержащиеся в ней операции над базой данных выполняются либо все, либо ни одна из них.
- Непротиворечивость: транзакция должна обеспечивать изменение состояния базы данных из одного согласованного состояния в другое. Непротиворечивое состояние означает, что данные в базе данных должны удовлетворять ограничениям целостности. Кроме того, непротиворечивость имеет еще один слой семантики, то есть промежуточное состояние транзакции нельзя наблюдать (этот слой семантики также говорит о том, что он должен принадлежать к атомарности).
- Изоляция: при одновременном выполнении нескольких транзакций выполнение одной транзакции не должно влиять на выполнение других транзакций, как если бы база данных выполняла только эту операцию.
- Долговечность: изменения в базе данных, внесенные совершенными транзакциями, должны постоянно храниться в базе данных. В конце транзакции эта операция необратима.
2.1 Отдельные транзакции
Возьмите в качестве примера механизм хранения MySQL InnoDB, чтобы понять, как транзакции с одной машиной обеспечивают характеристики ACID.
Изоляция транзакций реализуется через механизм блокировок базы данных, сохраняемость реализуется через журнал повторов (redo log), а атомарность и непротиворечивость реализуются через журнал отмены.2.2 Распределенные транзакции
Одномашинная транзакция реализует ACID, ограничивая операции сеансом через блокировку и журнал самой базы данных Как обеспечить характеристики ACID в распределенной среде?
2.2.1 Протокол XA реализует распределенные транзакции
2.2.1.1 Описание ХА
X/Open DTP (Эталонная модель распределенной обработки транзакций X/Open) представляет собой набор стандартов распределенных транзакций, определенных организацией X/Open, то есть он определяет спецификации и API-интерфейсы, которые реализуются различными производителями.
X/Open DTP определяет три компонента: AP, TM, RM.
- AP (прикладная программа): то есть прикладная программа, которую можно понимать как программу, использующую DTP.
- RM (менеджер ресурсов): менеджер ресурсов, здесь можно понимать систему СУБД или систему управления сервером сообщений, приложение управляет ресурсами через менеджера ресурсов. Ресурс должен реализовывать интерфейс, определенный XA
- TM (менеджер транзакций): менеджер транзакций, отвечающий за координацию и управление транзакциями, предоставление интерфейсов прикладного программирования AP и управление менеджерами ресурсов.
Следующие понятия определены в DTP
- Транзакция: транзакция — это завершенная единица работы, состоящая из нескольких независимых вычислительных задач, логически атомарных.
- Глобальная транзакция: транзакция, которая управляет несколькими менеджерами ресурсов одновременно, является глобальной транзакцией.
- Транзакция ветвления: в глобальной транзакции у менеджера ресурсов есть свои независимые задачи, и набор этих задач используется как задача ветвления этого менеджера ресурсов.
- Поток управления: используется для представления рабочего потока, в основном потока, связанного с AP, TM и RM, то есть контекста транзакции. Проще говоря, необходимо выявить взаимосвязь между глобальной транзакцией и транзакцией филиала.
Если диспетчер транзакций управляет несколькими диспетчерами ресурсов, DTP управляет глобальными транзакциями и транзакциями ветвей посредством протокола двухэтапной фиксации.
- Фаза 1: Фаза подготовки Диспетчер транзакций информирует диспетчера ресурсов о подготовке транзакции перехода, а диспетчер ресурсов информирует диспетчера транзакций о результате подготовки.
- Фаза 2: Фаза фиксации Диспетчер транзакций уведомляет диспетчера ресурсов о необходимости фиксации транзакции ветвления, а диспетчер ресурсов информирует диспетчера транзакций о результате.
2.2.1.2 КИСЛОТНЫЕ характеристики ХА
- Атомарность: протокол XA использует протокол атомарной фиксации 2PC для обеспечения атомарности распределенных транзакций.
- Изоляция: XA требует, чтобы каждый RM обеспечивал локальную изоляцию транзакций и изоляцию подтранзакций для обеспечения изоляции всей транзакции.
- Непротиворечивость: Гарантируйте «переходы базы данных из одного согласованного состояния в другое согласованное состояние» за счет реализации атомарности, изоляции и самосогласованности; с помощью MVCC, чтобы гарантировать невозможность наблюдения промежуточных состояний.
2.2.1.3 Преимущества и недостатки XA
- преимущество:
Отсутствие вмешательства в бизнес, высокие требования к РМ - недостаток:
Синхронная блокировка: во время двухфазного процесса фиксации все узлы ожидают ответов от других узлов и не могут выполнять другие операции. Эта блокировка синхронизации сильно ограничивает производительность распределенных систем.
Единственная проблема: координатор очень важен во всем двухэтапном процессе фиксации, если у координатора возникла проблема на этапе фиксации, весь процесс не будет работать. Что еще более важно, другие участники будут в состоянии блокировки ресурсов транзакций и не смогут продолжать выполнять операции транзакций.
Несогласованность данных: предположим, что после того, как координатор отправляет запросы на фиксацию всем участникам, возникает локальное сетевое исключение или координатор дает сбой до отправки всех запросов на фиксацию, в результате чего только некоторые участники получают запрос на фиксацию. Это приведет к серьезным несоответствиям данных.
Плохая отказоустойчивость: если участник терпит неудачу на этапе отправки и запроса на втором этапе отправки, координатор не может получить информацию о подтверждении всех участников. нужно прервать. Очевидно, что эта стратегия слишком консервативна. Другими словами, протокол двухфазной фиксации не имеет хорошо продуманного механизма отказоустойчивости, и сбой любого узла приведет к сбою всей транзакции.
2.2.2 Протокол TCC реализует распределенные транзакции
2.2.2.1 Описание ТСС
По сравнению с традиционными моделями, такими как XA, модель распределенных транзакций TCC (Try-Confirm-Cancel) характеризуется тем, что она не полагается на диспетчер ресурсов (RM) для поддержки распределенных транзакций, а реализует распределенные транзакции путем декомпозиции бизнес-логики. .
- Фаза 1: можно зафиксировать
Фаза CanCommit в 3PC на самом деле очень похожа на фазу подготовки в 2PC. Координатор отправляет участнику запрос на фиксацию, и участник возвращает ответ «Да», если участник может отправить, в противном случае возвращает ответ «Нет».
Запрос транзакции: координатор отправляет участнику запрос CanCommit. Спросите, можно ли выполнить операцию фиксации транзакции. Затем начните ждать ответа участника
Обратная связь ответа: после того, как участник получит запрос CanCommit, при нормальных обстоятельствах, если участник считает, что транзакция может быть успешно выполнена, он вернет ответ «Да» и перейдет в состояние готовности; в противном случае он вернет «Нет».
- Этап 2: предварительная фиксация
После того, как координатор получит ответ от всех участников, он выполнит две операции в зависимости от результата: выполнит предварительную фиксацию транзакции или прервет транзакцию.
Выполнить предварительную фиксацию транзакции
Отправка запроса предварительной фиксации: Координатор отправляет запрос предварительной фиксации всем участвующим узлам и переходит в состояние готовности.
Предварительная фиксация транзакции: после того, как участник получит запрос на предварительную фиксацию, будет выполнена операция транзакции, соответствующая «выполнению транзакции» на этапе подготовки 2PC, а информация об отмене и повторении также будет записана в журнал транзакций.
Каждый участник отвечает на обратную связь: если участник успешно выполняет транзакцию, он возвращает ответ ACK, при этом ожидая инструкций: commit (фиксировать) или abort (абортировать)
прервать транзакцию
Отправить запрос на прерывание: координатор отправляет запрос на прерывание всем узлам-участникам.
Прерывание транзакции: если участник получает запрос на прерывание или истекает время ожидания, транзакция будет прервана.
- Этап 3: Совершение
На этом этапе выполняется реальная отправка транзакции, которую также можно разделить на следующие два случая.
выполнить фиксацию
Отправить запрос на фиксацию: координатор получает ответ ACK, отправленный каждым участником, затем он переходит в состояние фиксации из состояния предварительной фиксации. и отправить запрос doCommit всем участникам.
Фиксация транзакции: после того, как участник получает запрос на выполнение транзакции, выполняется формальная фиксация транзакции. И освободите все ресурсы транзакции после завершения транзакции.
Ответная обратная связь: после того, как транзакция будет зафиксирована, отправьте ответ ACK координатору.
Завершите транзакцию: после того, как координатор получит ответы ACK от всех участников, транзакция будет завершена.
прервать транзакцию
Если координатор не получает ACK-ответ, отправленный участником (возможно, получатель не отправляет ACK-ответ или время ответа истекло), будет выполнена транзакция прерывания.
Отправить запрос на прерывание: координатор отправляет всем участникам запрос на прерывание.
Откат транзакции: после того, как участник получает запрос на отмену, он использует информацию об отмене, записанную на этапе 2, для выполнения операции отката транзакции и освобождает все ресурсы транзакции после завершения отката.
Результат обратной связи: после того, как участник завершает откат транзакции, он отправляет сообщение ACK координатору.
Прерывание транзакции: после того, как координатор получит сообщение ACK, возвращенное участником, транзакция прерывается.
2.2.2.2 Характеристики ACID TCC
- Атомарность: модель TCC также использует протокол атомарной фиксации 2PC, чтобы гарантировать атомарность транзакций. Операция Try соответствует одноэтапной подготовке (Prepare) 2PC, Confirm соответствует двухэтапной фиксации (Commit) 2PC, а Cancel соответствует двухэтапному откату (Rollback) 2PC. TCC — это 2PC прикладного уровня.
- Изоляция: Суть изоляции заключается в управлении параллелизмом.Отказ от блокировки на уровне базы данных достигается за счет блокировки на бизнес-уровне. [Например, в дизайне модуля управления счетом увеличьте настройки доступного баланса и замороженной суммы]
- Непротиворечивость: атомарная фиксация транзакций гарантируется атомарностью, одновременный доступ к транзакциям контролируется бизнес-изоляцией, и реализуется согласованный переход состояния распределенных транзакций; тот факт, что промежуточное состояние транзакций невозможно наблюдать, не гарантирует [этого протокол основан на гибкой теории транзакций].
2.2.2.3 Преимущества и недостатки TCC
- преимущество:
По сравнению с двухэтапной фиксацией трехэтапная фиксация в основном решает проблему единой точки отказа и сокращает время блокировки. Поскольку, как только участник не сможет вовремя получить информацию от координатора, он по умолчанию выполнит коммит. Вместо того, чтобы постоянно удерживать ресурсы транзакций и блокировать их. - недостаток:
Трехэтапная фиксация также может вызвать проблемы с согласованностью данных. По сетевым причинам отправленный координатором ответ Cancel не поступает участнику вовремя, тогда участник выполняет операцию фиксации после ожидания таймаута. Это создает несогласованность данных с другими участниками, которые получают команду «Отмена» и выполняют откат.
2.2.3 Протокол SAGA реализует распределенные транзакции
2.2.3.1 Введение протокола SAGA
Состав саги:
- Каждая сага состоит из серии подтранзакций Ti
- Каждый Ti имеет соответствующее компенсационное действие Ci, которое используется для отмены результатов, вызванных Ti.
Есть два порядка исполнения саги:
- T1, T2, T3, ..., Tn
- T1, T2, ..., Tj, Cj,..., C2, C1, где 0
Saga определяет две стратегии восстановления:
- обратное восстановление, то есть второй порядок выполнения, упомянутый выше, где j — подтранзакция с ошибкой, результатом этого подхода является отмена всех предыдущих успешных подтранзакций, в результате чего результат выполнения всей Saga отменяется.
- прямое восстановление, подходящее для сценариев, которые должны завершиться успешно, порядок выполнения аналогичен этому: T1, T2, ..., Tj (сбой), Tj (повторная попытка), ..., Tn, где j — подтранзакция, где ошибка произошла. Ci в этом случае не требуется.
Заметки саги
- Ti и Ci идемпотентны. Например, если предположить, что время выполнения Ti истекло, мы не знаем результат выполнения в это время.Если будет принята стратегия прямого восстановления, Ti будет отправлено снова, тогда возможно, что Ti будет выполнено дважды, поэтому мощность Требуется Ti. Подождите. Если принята стратегия обратного восстановления, Ci будет отправлен, и если Ci также истечет, он попытается отправить Ci снова, поэтому возможно, что Ci будет выполнен дважды, поэтому Ci должен быть идемпотентным.
- Ci должен быть в состоянии добиться успеха, в противном случае требуется ручное вмешательство. Если Ci не может быть успешно выполнен, это означает, что вся Сага не может быть полностью отозвана, что не допускается. Но всегда будут какие-то особые случаи, например, код Ci имеет ошибки, сервис долго падает и т.д., в это время требуется ручное вмешательство.
- Результат выполнения Ti - Ci и Ci - Ti должен быть одинаковым: подтранзакция отменяется. Например, рассмотрим сценарий таймаута выполнения Ti, мы используем обратное восстановление и отправляем Ci, тогда возможны три ситуации:
1: запрос Ti потерян, и сервис не будет выполнять Ti до и после
2: Ti выполняется до Ci
3: Ci выполняется до Ti
В первом случае справиться несложно. Во втором и третьем случаях требуется, чтобы Ti и Ci были коммутативными, и в конечном итоге подтранзакция отменяется.
Сага Архитектура
- Saga Execution Component анализирует запрос JSON и строит график запроса
- TaskRunner использует очереди задач для обеспечения порядка выполнения запросов
- TaskConsumer обрабатывает задачи Saga, записывает события в журнал Saga и отправляет запросы удаленным службам.
2.2.3.2 КИСЛОТНЫЕ характеристики SAGA
- Атомарность: достигается через координатора SAGA
- Согласованность: локальная транзакция + журнал SAGA
- Постоянство: журнал SAGA
- Изоляция: не гарантируется (аналогично TCC)
3 Схема распределенной обработки транзакций
3.1 XA
Использование интерфейса X/Open XA необходимо только в том случае, когда несколько ресурсов (то есть баз данных и тем сообщений или очередей) необходимо координировать в рамках одного и того же контекста транзакции. Доступ базы данных к XA должен использовать версию драйвера базы данных XA.Для реализации XA очередь сообщений должна реализовать интерфейс javax.transaction.xa.XAResource.
3.1.1 распределенные транзакции jotm
код показывает, как показано ниже:
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private LogDao logDao;
@Transactional
public void save(User user){
userDao.save(user);
logDao.save(user);
throw new RuntimeException();
}
}
@Resource
public class UserDao {
@Resource(name="jdbcTemplateA")
private JdbcTemplate jdbcTemplate;
public void save(User user){
jdbcTemplate.update("insert into user(name,age) values(?,?)",user.getName(),user.getAge());
}
}
@Repository
public class LogDao {
@Resource(name="jdbcTemplateB")
private JdbcTemplate jdbcTemplate;
public void save(User user){
jdbcTemplate.update("insert into log(name,age) values(?,?)",user.getName(),user.getAge());
}
}
Конфигурация:
<bean id="jotm" class="org.objectweb.jotm.Current" />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="jotm" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置数据源 -->
<bean id="dataSourceA" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
<property name="dataSource">
<bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
<property name="transactionManager" ref="jotm" />
<property name="driverName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" />
</bean>
</property>
<property name="user" value="xxx" />
<property name="password" value="xxx" />
</bean>
<!-- 配置数据源 -->
<bean id="dataSourceB" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
<property name="dataSource">
<bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
<property name="transactionManager" ref="jotm" />
<property name="driverName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8" />
</bean>
</property>
<property name="user" value="xxx" />
<property name="password" value="xxx" />
</bean>
<bean id="jdbcTemplateA" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourceA" />
</bean>
<bean id="jdbcTemplateB" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourceB" />
</bean>
Используемый пакет JAR:
compile 'org.ow2.jotm:jotm-core:2.3.1-M1'
compile 'org.ow2.jotm:jotm-datasource:2.3.1-M1'
compile 'com.experlog:xapool:1.5.0'
Конфигурация транзакции: Мы знаем, что в распределенных транзакциях требуется диспетчер транзакций, а именно интерфейс javax.transaction.TransactionManager и ориентированный на разработчиков javax.transaction.UserTransaction. Для jotm их классы реализации Current
public class Current implements UserTransaction, TransactionManager
Если мы хотим использовать распределенные транзакции и в то же время использовать удобство @Transactional, которое предоставляет нам Spring, нам нужно настроить JtaTransactionManager, а JtaTransactionManager нуждается в экземпляре userTransaction, поэтому используется указанный выше Current следующим образом:
<bean id="jotm" class="org.objectweb.jotm.Current" />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="jotm" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
В то же время приведенному выше StandardXADataSource требуется экземпляр TransactionManager, поэтому приведенная выше конфигурация StandardXADataSource добавляет jotm.
Процесс реализации:
- Шаг 1: Перехватчик транзакции запускает транзакцию
Мы знаем, что добавление аннотации @Transactional и включение tx:annotation-driven будет проксировать этот объект и добавит перехватчик транзакций. В перехватчике транзакций получите javax.transaction.UserTransaction, который является org.objectweb.jotm.Current, а затем используйте его, чтобы запустить транзакцию и связать ее с текущим потоком.Данные об отношениях привязки хранятся в org.objectweb.jotm .Текущая середина. - Шаг 2. Используйте jdbcTemplate для бизнес-операций
dbcTemplateA необходимо получить Connection от dataSourceA, связать его с текущим потоком и использовать соответствующий dataSourceA в качестве ключа. В то же время оценивается, есть ли у текущего потока транзакция. подключение включено в текущую транзакцию.
jdbcTemplateB должен получить Connection из dataSourceB, привязать его к текущему потоку и использовать соответствующий dataSourceB в качестве ключа. В то же время оценивается, содержит ли текущий поток транзакцию.Если обнаруживается, что текущий поток имеет транзакцию через org.objectweb.jotm.Current в dataSourceB, автофиксация соединения устанавливается на false, а подключение включено в текущую транзакцию. - Шаг 3: Откат исключения После возникновения исключения транзакцию необходимо откатить. Откат — это откат текущей транзакции, а откат транзакции вызовет откат всех связанных с ней Connections.
3.1.2 Распределенные транзакции Atomikos
Код такой же, как и выше, сконфигурированный как:
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="atomikosUserTransaction" />
</bean>
<tx:annotation-driven transaction-manager="springTransactionManager"/>
<!-- 配置数据源 -->
<bean id="dataSourceC" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="XA1DBMS" />
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8</prop>
<prop key="user">xxx</prop>
<prop key="password">xxx</prop>
</props>
</property>
<property name="poolSize" value="3" />
<property name="minPoolSize" value="3" />
<property name="maxPoolSize" value="5" />
</bean>
<!-- 配置数据源 -->
<bean id="dataSourceD" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="XA2DBMS" />
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8</prop>
<prop key="user">xxx</prop>
<prop key="password">xxx</prop>
</props>
</property>
<property name="poolSize" value="3" />
<property name="minPoolSize" value="3" />
<property name="maxPoolSize" value="5" />
</bean>
<bean id="jdbcTemplateC" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourceC" />
</bean>
<bean id="jdbcTemplateD" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourceD" />
</bean>
Конфигурация транзакции:
Мы знаем, что в распределенных транзакциях требуется диспетчер транзакций, а именно интерфейс javax.transaction.TransactionManager и ориентированный на разработчиков javax.transaction.UserTransaction. Для Atomikos они следующие:
- com.atomikos.icatch.jta.UserTransactionImp
- com.atomikos.icatch.jta.UserTransactionManager Если мы хотим использовать распределенные транзакции, но также хотим использовать удобство @Transactional, предоставленное нам Spring, нам нужно настроить JtaTransactionManager, а JtaTransactionManager нужен экземпляр userTransaction.
<bean id="userTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="userTransaction" />
</bean>
<tx:annotation-driven transaction-manager="springTransactionManager"/>
Вы можете сравнить конфигурацию распределенной транзакции jotm в случае jotm. Видно, что для StandardXADataSource в xapool, используемом в jotm, требуется менеджер транзакций, а для AtomikosNonXADataSourceBean, используемого Atomikos, — нет. Мы знаем, что с помощью transactionManager в StandardXADataSource можно получить транзакцию текущего потока и добавить XAResource в текущую транзакцию, а AtomikosNonXADataSourceBean — нет.Как он добавляет XAResource в транзакцию, привязанную к текущему потоку? В настоящее время необходимо получить транзакции, привязанные к текущему потоку, в любое время с помощью статических методов. Используемый пакет JAR:
compile 'com.atomikos:transactions-jdbc:4.0.0M4'
3.2 Автономная транзакция + синхронный обратный вызов (асинхронный)
Возьмем в качестве примера подсистему заказа и подсистему оплаты, как показано на следующем рисунке:
Как показано на рисунке выше, платеж — это платежная система, торговля — это система заказов, а базы данных, соответствующие этим двум системам, являются отдельными. После завершения платежа платежная система должна уведомить систему заказов об изменении статуса.
Операция, которая должна быть выполнена для оплаты, может быть выражена в псевдокоде следующим образом:
begin tx;
count = update account set amount = amount - ${cash} where uid = ${uid} and amount >= amount
if (count <= 0) return false
update payment_record set status = paid where trade_id = ${tradeId}
commit;
Операции, которые должны выполняться торговлей, могут быть выражены в псевдокоде следующим образом:
begin tx;
count = update trade_record set status = paid where trade_id = ${trade_id} and status = unpaid
if (count <= 0) return false
do other things ...
commit;
Однако проблема заключается в том, как связать эти две части кода вместе.Мы добавляем таблицу транзакций, а именно tx_info на рисунке, для записи успешно завершенной платежной транзакции.В tx_info должно быть поле, которое может указывать статус обработки платежной системы, для того, чтобы платежная информация соответствовала Consistent, ее необходимо ввести в транзакцию, код выглядит следующим образом:
begin tx;
count = update account set amount = amount - ${cash} where uid = ${uid} and amount >= amount
if (count <= 0) return false
update payment_record set status = paid where trade_id = ${tradeId}
insert into tx_info values(${trade_id},${amount}...)
commit;
На этом границы платежной системы заканчиваются.Следующим шагом является опрос системы заказов для доступа к tx_info, извлечение информации об успешно оплаченных заказах и выполнение логики торговой системы для каждой части информации.Псевдокод следующее:
foreach trade_id in tx_info
do trade_tx
save tx_info.id to some store
Отсутствие задержки зависит от интервала опроса временной программы, поэтому мы добиваемся согласованности, и окончательный заказ завершит переход состояния в течение максимального интервала времени после оплаты.
Конечно, это также может быть достигнуто с помощью платежной системы, синхронно уведомляющей систему заказов через RPC, а статус обработки представлен полями в tx_info.
Кроме того, необходимо записывать начальную точку и запись потребления каждый раз, когда система транзакций извлекает данные, чтобы их можно было выполнить без пропусков и повторений, поэтому необходимо добавить таблицу для дедупликации, то есть tx_duplication в выше рисунок. Но каждая вставка в таблицу tx_duplication выполняется в транзакции trade_tx, псевдокод выглядит следующим образом:
begin tx;
c = insert ignore tx_duplication values($trade_id...)
if (c <= 0) return false
count = update trade_record set status = paid where trade_id = ${trade_id} and status = unpaid
if (count <= 0) return false
do other things ...
commit;
Кроме того, таблица trade_id в таблице tx_duplication должна иметь уникальный ключ Это в сочетании с предыдущими статьями об идемпотентах гарантирует, что операция trade_tx будет идемпотентной.
3.3 MQ как роль промежуточного стола
В приведенной выше схеме роль таблицы tx_info состоит в том, чтобы ставить в очередь, записывать обновление системной таблицы как событие для уведомления системы, о которой необходимо знать. Временная программа для извлечения — это только способ для системы получить интересные события, а локальная транзакция, соответствующая системе транзакций, — это только процесс, соответствующий событию потребления. Согласно этому описанию, эти функции являются промежуточным программным обеспечением сообщений MQ. Как показано ниже
Таким образом, функция таблицы tx_info передается MQ, и смещение потребления сообщений не должно беспокоить.MQ справится с этим, но tx_duplication все еще должен существовать, потому что MQ не может избежать повторной доставки сообщений. Причины этого следующие: Многие из них, в основном вызванные распределенным CAP, не будут подробно описываться снова.
Это требует, чтобы MQ поддерживал функцию транзакций, которая может обеспечить согласованность между локальными транзакциями и отправкой сообщений, но не обязательно строгую согласованность. Обычный способ использования псевдокода выглядит следующим образом:
sendPrepare();
isCommit = local_tx()
if (isCommit) sendCommit()
else sendRollback()
Прежде чем выполнять локальную транзакцию, сначала отправьте сообщение о подготовке в MQ, а затем выполните локальную транзакцию.Если локальная транзакция успешно отправлена, отправьте сообщение о фиксации в MQ, в противном случае отправьте сообщение о прекращении, чтобы отменить предыдущее сообщение. MQ доставляет сообщение только после получения подтверждения фиксации, поэтому эта форма может гарантировать, что локальная транзакция и MQ смогут достичь согласованности, когда все будет нормально.
Однако бывают нештатные ситуации при распределенном распространении, тайм-аут сети, простои машины и т. д. Например, после того, как система успешно выполнила local_tx(), она не успела отправить сообщение фиксации в MQ или отправила его, сеть истекло время ожидания и т. д. Если MQ не получает фиксацию, т. е. сообщение о фиксации потеряно, то MQ не доставляет сообщение о подготовке. Если это не может быть гарантировано, то это решение неосуществимо. В ответ на эту ситуацию необходим сторонний модуль проверки исключений для проверки сообщения в MQ, которое не было зафиксировано/прервано в течение определенного периода времени, и системы, отправляющей сообщение, для подтверждения того, должно ли сообщение быть доставлено или отбрасывается и получает подтверждение от системы. После этого MQ выполнит доставку или отмену, что полностью гарантирует согласованность MQ и системы, отправляющей сообщение, тем самым обеспечивая согласованность системы, получающей сообщение.
Это решение требует, чтобы доступность системы MQ была очень высокой, по крайней мере, выше, чем у системы, использующей MQ (рекомендуется Rocketmq и kafka для поддержки отправки предварительных сообщений и бизнес-обзора), чтобы обеспечить стабильную работу системы, которая зависит в теме.
3.4 Схема САГА
адрес проекта:GitHub.com/Apache/CailV…Сценарий обработки Saga требует, чтобы связанные подтранзакции предоставляли функции обработки транзакций и функции компенсации. Альфа-координатор Saga отправит соответствующие инструкции омеге в соответствии с выполнением транзакции, чтобы определить, следует ли повторить попытку вперед или восстановить назад.
сценарий успеха
В успешном сценарии каждая транзакция будет иметь начало и соответствующее конечное событие.
ненормальная сцена
В ненормальных сценариях omega сообщит о прерывании в alpha, а затем alpha отправит компенсационные команды другим завершенным подтранзакциям глобальной транзакции, чтобы убедиться, что все подтранзакции либо успешны, либо отменены.
Сценарий тайм-аута
В сценарии тайм-аута событие, которое истекло, будет обнаружено периодическим сканером альфы, и в то же время глобальная транзакция, соответствующая транзакции тайм-аута, также будет прервана.
пример
Предположим, вы хотите арендовать автомобиль и забронировать отель для выполнения распределенных транзакций.
прокат автомобилей
@Service
class CarBookingService {
private Map<Integer, CarBooking> bookings = new ConcurrentHashMap<>();
@Compensable(compensationMethod = "cancel")
void order(CarBooking booking) {
booking.confirm();
bookings.put(booking.getId(), booking);
}
void cancel(CarBooking booking) {
Integer id = booking.getId();
if (bookings.containsKey(id)) {
bookings.get(id).cancel();
}
}
Collection<CarBooking> getAllBookings() {
return bookings.values();
}
void clearAllBookings() {
bookings.clear();
}
}
Бронирование отелей
@Service
class HotelBookingService {
private Map<Integer, HotelBooking> bookings = new ConcurrentHashMap<>();
@Compensable(compensationMethod = "cancel")
void order(HotelBooking booking) {
if (booking.getAmount() > 2) {
throw new IllegalArgumentException("can not order the rooms large than two");
}
booking.confirm();
bookings.put(booking.getId(), booking);
}
void cancel(HotelBooking booking) {
Integer id = booking.getId();
if (bookings.containsKey(id)) {
bookings.get(id).cancel();
}
}
Collection<HotelBooking> getAllBookings() {
return bookings.values();
}
void clearAllBookings() {
bookings.clear();
}
}
основной сервис
@RestController
public class BookingController {
@Value("${car.service.address:http://car.servicecomb.io:8080}")
private String carServiceUrl;
@Value("${hotel.service.address:http://hotel.servicecomb.io:8080}")
private String hotelServiceUrl;
@Autowired
private RestTemplate template;
@SagaStart
@PostMapping("/booking/{name}/{rooms}/{cars}")
public String order(@PathVariable String name, @PathVariable Integer rooms, @PathVariable Integer cars) {
template.postForEntity(
carServiceUrl + "/order/{name}/{cars}",
null, String.class, name, cars);
postCarBooking();
template.postForEntity(
hotelServiceUrl + "/order/{name}/{rooms}",
null, String.class, name, rooms);
postBooking();
return name + " booking " + rooms + " rooms and " + cars + " cars OK";
}
// This method is used by the byteman to inject exception here
private void postCarBooking() {
}
// This method is used by the byteman to inject the faults such as the timeout or the crash
private void postBooking() {
}
}
Процесс реализации
- Выполните mvn clean package -DskipTests -Pdemo в каталоге Alpha.
- Выполнить java -Dspring.profiles.active=prd -D"spring.datasource.url=jdbc:postgresql://{saga_version}-exec.jar
- Выполните mvn clean package -DskipTests -Pdemo в демонстрационном каталоге saga spring.
- java -Dserver.port=8081 -Dalpha.cluster.address={saga_version}-exec.jar
- java -Dserver.port=8082 -Dalpha.cluster.address={saga_version}-exec.jar
- java -Dserver.port=8083 -Dalpha.cluster.address={host_address}:8082 -Dhotel.service.address={версия_saga}-exec.jar[альфа-адрес без http и другие адреса с http]
3.5 Схема ТСС
Адрес проекта https://github.com/QNJR-GROUP/EasyTransaction [По сравнению с tcc-транзакцией, Hmily, ByteTCC, EasyTransaction имеет наилучшую производительность, и в стресс-тесте ошибок не обнаружено], конечно, можно и использовать проект SAGA, упомянутый выше, также поддерживает протокол TCC. Давайте возьмем пример, чтобы увидеть, как TCC обрабатывает бизнес-логику.
например: оплата заказа
- 1: Заказ услуги -> Изменить статус заказа
- 2: Служба инвентаризации -> Инвентаризация вычетов
- 3: Сервис баллов -> Добавить баллы
- 4: Складская служба -> создать исходящий заказ
пробная фаза
- 1: Услуга заказа -> Статус изменен на «Обновление»
- 2: Служба инвентаризации -> Доступный инвентарь уменьшается на 1, замороженный инвентарь увеличивается на 1
- 3: Сервис баллов -> баллы остаются без изменений, увеличиваются резервные баллы
- 4: Складская служба -> Создать исходящий заказ, статус установлен на «Неизвестно».
подтвердить этап
- 1: Заказать услугу -> статус изменен на "оплачено"
- 2: Служба инвентаризации -> Очистка замороженных инвентарей
- 3: Сервис баллов -> баллы увеличиваются, резервные баллы обнуляются
- 4: Складская служба -> Статус установлен на «Создан исходящий заказ».
отменить фазу
- 1: Заказ услуги -> Статус меняется на «Отменено»
- 2: Служба инвентаризации -> доступный инвентарь увеличивается, замороженный инвентарь очищается
- 3: Сервис баллов -> Сброс резервных баллов
- 4: Складские услуги -> Статус установлен на «Отменено»
4 Резюме
Базовые концепты | преимущество | недостаток |
---|---|---|
местные дела. Транзакции управляются локально менеджером ресурсов, таким как СУБД. | Строгая КИСЛОТА | Не способен к распределенной обработке транзакций |
Глобальная транзакция (модель DTP) Протокол TX: интерфейс между приложением или сервером приложений и диспетчером транзакций. Протокол XA: интерфейс между Global Transaction Manager и Resource Manager |
Строгая КИСЛОТА | очень неэффективно |
JTA: высокоуровневый интерфейс транзакций для приложений, серверов приложений и менеджеров ресурсов. JTS: стандарт реализации диспетчера транзакций JTA, поддерживающий JTA вверх и реализующий взаимодействие между доменами транзакций через CORBA OTS вниз. EJB |
Простая и последовательная модель программирования ACID гарантирует распределенную обработку между доменами |
Ограничения самой модели DTP Отсутствие широко разрекламированных историй успеха для крупномасштабных приложений с высокой доступностью и интенсивными транзакциями. |
На основе МК | Независимое хранилище данных сообщений, независимое масштабирование Уменьшите связь между бизнес-системой и системой сообщений |
Для отправки одного сообщения требуется два запроса Службе бизнес-обработки необходимо реализовать интерфейс проверки статуса сообщения. |
двухэтапная фиксация | Простой принцип, легко реализуемый | Синхронная блокировка: во время двухфазного процесса фиксации все узлы ожидают ответов от других узлов и не могут выполнять другие операции. Эта блокировка синхронизации сильно ограничивает производительность распределенных систем. Единственная проблема: координатор очень важен во всем двухэтапном процессе фиксации, если у координатора возникла проблема на этапе фиксации, весь процесс не будет работать. Что еще более важно, другие участники будут в состоянии блокировки ресурсов транзакций и не смогут продолжать выполнять операции транзакций. Несогласованность данных: предположим, что после того, как координатор отправляет запросы на фиксацию всем участникам, возникает локальное сетевое исключение или координатор дает сбой до отправки всех запросов на фиксацию, в результате чего только некоторые участники получают запрос на фиксацию. Это приведет к серьезным несоответствиям данных. Плохая отказоустойчивость: если участник терпит неудачу на этапе отправки и запроса на втором этапе отправки, координатор не может получить информацию о подтверждении всех участников. нужно прервать. Очевидно, что эта стратегия слишком консервативна. Другими словами, протокол двухфазной фиксации не имеет хорошо продуманного механизма отказоустойчивости, и сбой любого узла приведет к сбою всей транзакции. |
TCC | По сравнению с двухэтапной фиксацией трехэтапная фиксация в основном решает проблему единой точки отказа и сокращает время блокировки. Поскольку, как только участник не сможет вовремя получить информацию от координатора, он по умолчанию выполнит коммит. Вместо того, чтобы постоянно удерживать ресурсы транзакций и блокировать их. | Трехэтапная фиксация также может вызвать проблемы с согласованностью данных. По сетевым причинам ответ на прерывание, отправленный координатором, не получен участником вовремя, тогда участник выполняет операцию фиксации после ожидания тайм-аута. Таким образом, возникает несогласованность данных между другими участниками, которые получают команду прерывания и выполняют откат. |
SAGA | Простое использование в бизнесе TCC необходимо изменить исходную бизнес-логику, саге нужно только добавить компенсационное действие. Поскольку действия резервирования нет, не нужно беспокоиться о проблеме высвобождения ресурсов, обработка исключений проста. |
Обработка компенсации затруднена из-за отсутствия зарезервированных действий |
Каждый бизнес отличается друг от друга. Некоторые предприятия могут терпеть краткосрочные несоответствия, а некоторые предприятия могут работать идемпотентно. Независимо от того, какое решение для распределенных транзакций имеет свои преимущества и недостатки, ни одна серебряная пуля не может удовлетворить всех. Следовательно, какое решение необходимо бизнесу, необходимо сочетать с его собственными бизнес-потребностями, бизнес-характеристиками, технической архитектурой и характеристиками каждого решения, и для поиска наиболее подходящего решения можно использовать всесторонний анализ.