Гибкие транзакции, распределённые транзакции, TCC, SAGA, надёжные сообщения, максимальные усилия доставки сообщений, транзакционные сообщения, компенсация, глобальные транзакции, soft transaction, distributed transaction, compensation, автоматическая компенсация
Этот фреймворк предназначен для решения проблем распределённых транзакций в SOA (включая микросервисы) одним пакетом.
Этот фреймворк был создан с учётом распределённых транзакционных сценариев, с которыми компания столкнулась ранее, а также презентации Чжэнь Ли из Алибабы "Обработка распределённых транзакций в больших SOA системах".
Целью этого фреймворка является решение проблемы повторной разработки промежуточных состояний, реализации idempotence и логики повторных попыток для каждого распределённого транзакционного сценария внутри компании.
Принятие данного фреймворка позволяет решить все известные распределённые транзакционные сценарии, снижает объём работы по проектированию и разработке, повышает производительность разработки и обеспечивает единую гарантию надёжности выполнения транзакций.
Особенности:* Введение JAR-пакета для использования, без необходимости отдельной установки координатора или ZooKeeper (при использовании extension-suite-database-starter)
Для таких транзакций фреймворк вообще не вмешивается и не выполняет ни одной лишней строки кода.
Основной зависимостью фреймворка является Spring TransactionSynchronization. Любое используемое менеджер транзакций, который наследуется от AbstractPlatformTransactionManager, может использовать этот фреймворк (почти все менеджеры транзакций наследуются от этого базового класса). Кроме того, начиная с версии bk 1.0.0, фреймворк использует возможности конфигурации Spring Boot и новые возможности JDK 8, поэтому Spring Boot и JDK 8 также являются обязательными компонентами.Для распределённых транзакций фреймворк регистрирует соответствующие операции в TransactionSynchronization перед вызовом удалённого метода транзакции, например:
Фреймворк также полностью реализует механизм обеспечения идемпотентности (опционально). Он гарантирует, что бизнес-метод будет выполнен только один раз на уровне логики (хотя он может быть запущен несколько раз, повторные запуски будут откатываться). Поэтому при работе с неоткатываемыми внешними ресурсами бизнес-приложение должно самостоятельно контролировать идемпотентность.Фреймворк также правильно обрабатывает зависимости между методами, такими как традиционные транзакции с компенсацией:
Future
, что предоставляет пространство для оптимизации производительности фреймворка. До первого получения результата все логи не записываются, а все удалённые методы не вызываются. Как только бизнес-программа пытается получить результат выполнения, логи пишутся в блок и последующие параллельные вызовы удалённых методов выполняются.Если бизнес-программа не пытается получить результат выполнения до COMMIT фреймворка, она попробует GET один раз для всех удалённых методов. В случае, если любой из удалённых методов выбрасывает исключение, фреймворк откатит бизнес-операцию перед окончательным COMMIT, независимо от того, был ли исключение перехвачен ранее. Это гарантирует простоту программной модели и позволяет легко писать правильный и понятный код.Компонент уже загружен в центральный репозиторий, поэтому для бизнес-кода достаточно добавить следующее содержание в pom:
<dependency>
<groupId>com.yiqiniu.easytrans</groupId>
<artifactId>easytrans-starter</artifactId>
<version>1.4.3</version>
</dependency>
Starter включает несколько компонентов по умолчанию: распределённое управление транзакциями с использованием MySQL, RPC реализацию на основе Ribbon-Rest, сообщение очередей на основе Kafka. Если эти компоненты не нужны или требуется замена, их можно исключить.
Для инициатора бизнес-процесса фреймворк предлагает только один интерфейс:
public interface EasyTransFacade {
/**
* Начать распределённую транзакцию
*
* @param busCode appId + busCode + trxId — это глобально уникальный ID, независимо от того, будет эта транзакция завершена или отменена
* @param trxId см busCode
*/
void startEasyTrans(String busCode, String trxId);
}
``````markdown
/**
* Выполнить удалённую транзакцию
*
* @param params
* @return
*/
<P extends EasyTransRequest<R, E>, E extends EasyTransExecutor, R extends Serializable> Future<R> execute(P params);
}
Способ использования представлен ниже: используйте сторонний метод напрямую, не беспокоясь о конкретном типе распределённой транзакции или последующем процессе.
@Transactional
public void buySomething(int userId, long money) {
/**
* Локальный бизнес-метод, создание заказа и получение номера заказа
*/
JdbcTemplate jdbcTemplate = util.getJdbcTemplate();
Integer id = saveOrderRecord(jdbcTemplate, userId, money);
/**
* Объявление глобального ID транзакции, который состоит из appId, кода бизнес-процесса и внутреннего ID бизнес-процесса
* Этот метод также можно не вызывать, в этом случае фреймворк использует по умолчанию код бизнес-процесса и автоматически сгенерированный ID
* Однако недостатком такого подхода является то, что глобальный ID транзакции не может быть непосредственно связан с конкретной операцией бизнеса
* Тем не менее, это можно исправить с помощью пользовательских BusinessCodeGenerator и TrxIdGenerator
*/
transaction.startEasyTrans(BUSINESS_CODE, id);
}
``````java
/**
* Вызов удаленного сервиса для списания необходимой суммы денег, этот удаленный сервис реализует TCC интерфейс
* Фреймворк будет использовать результат транзакции метода buySomething для обеспечения конечной согласованности удаленного сервиса
*/
WalletPayTccMethodRequest deductRequest = new WalletPayTccMethodRequest();
deductRequest.setUserId(userId);
deductRequest.setPayAmount(money / 2);
// В бизнес-логике возможно многократное выполнение одного и того же метода, последний действительный вызов будет иметь эффект
Future<WalletPayTccMethodResult> deductFuture = transaction.execute(deductRequest);
Future<WalletPayTccMethodResult> deductFuture = transaction.execute(deductRequest);
}
#### Вызов удаленного сервиса для записи бухгалтерских данных, бухгалтерский сервис является компенсирующим сервисом
Когда метод `buySomething` откатывается в рамках транзакции, фреймворк автоматически вызывает соответствующий метод компенсации.
``````java
AccountingRequest accountingRequest = new AccountingRequest();
accountingRequest.setAmount(money);
accountingRequest.setUserId(userId);
Future<AccountingResponse> accountingFuture = transaction.execute(accountingRequest);
Это сообщение будет гарантировано отправлено после успешного завершения транзакции buySomething()
.
OrderMessage orderMessage = new OrderMessage();
orderMessage.setUserId(userId);
orderMessage.setAmount(money);
Future<PublishResult> reliableMessage = transaction.execute(orderMessage);
### Поставщик услуг
Для поставщика услуг следует реализовать соответствующие интерфейсы и зарегистрировать их в контейнере Bean Spring.
Например, для предоставления услуги TCC требуется реализация интерфейса `TccMethod`.
```java
public interface TccMethod<P extends TccTransRequest<R>, R extends Serializable> extends RpcBusinessProvider<P> {
R doTry(P param);
void doConfirm(P param);
void doCancel(P param);
}
Конкретная реализация:
@Component
public class WalletPayTccMethod implements TccMethod<WalletPayTccMethodRequest, WalletPayTccMethodResult> {
@Resource
private WalletService walletService;
@Override
public WalletPayTccMethodResult doTry(WalletPayTccMethodRequest param) {
return walletService.doTryPay(param);
}
@Override
public void doConfirm(WalletPayTccMethodRequest param) {
walletService.doConfirmPay(param);
}
@Override
public void doCancel(WalletPayTccMethodRequest param) {
walletService.doCancelPay(param);
}
}
Где WalletPayTccMethodRequest
— это запрос с параметрами, являющийся POJO, наследующимся от класса EasyTransRequest
, и требующим аннотации BusinessIdentifier
, чтобы фреймворк мог подтвердить ID бизнес-операции, связанной с данным запросом.```java
@BusinessIdentifier(appId = Constant.APPID, busCode = METHOD_NAME)
public class WalletPayTccMethodRequest implements TccTransRequest {
private static final long serialVersionUID = 1L;
private Integer userId;
private Long payAmount;
}
Пример выше представлен в традиционной форме вызова. Безопасное использование выглядит следующим образом:
```java
@Transactional
public String покупать_что_то(int userId, long деньги) {
int id = сохранить_запись_о_плате(jdbcTemplate, userId, деньги);
// WalletPayRequestVO - это объект-значение (value object), который не имеет никаких наследований или аннотаций, кроме связанных свойств и методов GETTER/SETTER
WalletPayRequestVO запрос = new WalletPayRequestVO();
запрос.setUserId(userId);
запрос.setPayAmount(деньги);
// payService - это экземпляр интерфейса PayService, созданный с помощью фреймворка. Вызов этого метода завершает распределенную транзакцию.
WalletPayResponseVO оплата = payService.оплатить(запрос);
return "id:" + id + " заморожено:" + оплата.getFreezeAmount();
}
```
#### Дополнительные примеры
Для получения более подробных примеров обратитесь к коду в easytrans-demos. Здесь представлено простое описание кода.
Более полная конфигурация и связанные примеры можно найти в UT-тестах easytrans-starter, где используется MockService со всеми возможными сценариями транзакций и тестами различных ситуаций ошибок.
### Настройка запуска
Каждый исполняющийся бизнес-контейнер требует создания двух таблиц. Типы полей были изменены, поэтому если таблицы уже существуют, их следует переопределить.
```sql
-- используется для записи информации о том, была ли выполнена конечная бизнес-операция
-- p_ с префиксом указывает на id родительского транзакта, связанного с данным транзактом
-- при выполнении запроса SELECT FOR UPDATE, если запись с указанным id транзакта отсутствует, значит транзакт завершился ошибкой
-- наличие записи, но статус равен 0 указывает на успешное завершение транзакта, статус 1 указывает на провал транзакта (включая родительский и текущий)
-- наличие записи, но статус равен 2 указывает на неопределенность окончательного состояния транзакта
CREATE TABLE `executed_trans` (
`app_id` smallint(5) unsigned NOT NULL,
`bus_code` smallint(5) unsigned NOT NULL,
`trx_id` bigint(20) unsigned NOT NULL,
`p_app_id` smallint(5) unsigned DEFAULT NULL,
`p_bus_code` smallint(5) unsigned DEFAULT NULL,
`p_trx_id` bigint(20) unsigned DEFAULT NULL,
`status` tinyint(1) NOT NULL,
PRIMARY KEY (`app_id`, `bus_code`, `trx_id`),
KEY `parent` (`p_app_id`, `p_bus_code`, `p_trx_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_bin;
``` CREATE TABLE `idempotent` (
`src_app_id` smallint(5) unsigned NOT NULL COMMENT 'Исходный AppID',
`src_bus_code` smallint(5) unsigned NOT NULL COMMENT 'Исходный тип бизнес-процесса',
`src_trx_id` bigint(20) unsigned NOT NULL COMMENT 'Исходный ID транзакции',
`app_id` smallint(5) NOT NULL COMMENT 'Вызов APPID',
`bus_code` smallint(5) NOT NULL COMMENT 'Вызов кода бизнес-процесса',
`call_seq` smallint(5) NOT NULL COMMENT 'Количество вызовов в рамках одного метода внутри одного транзакта',
`handler` smallint(5) NOT NULL COMMENT 'Обработчик appid',
`called_methods` varchar(64) NOT NULL COMMENT 'Названия вызываемых методов',
`md5` binary(16) NOT NULL COMMENT 'MD5 хэш параметров',
`sync_method_result` blob COMMENT 'Результат выполнения синхронного метода',
`create_time` datetime NOT NULL COMMENT 'Дата и время создания записи',
`update_time` datetime NOT NULL,
`lock_version` smallint(32) NOT NULL COMMENT 'Версия оптимистической блокировки',
PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; (Если используется REDIS для записи транзакционных логов, то следующие таблицы не требуются.) Для хранения транзакционных логов требуется база данных с двумя таблицами. Каждый бизнес-сервис должен иметь соответствующую базу данных для транзакционных логов. Несколько сервисов могут использовать одну общую базу данных, а один сервис может иметь отдельную базу данных для транзакционных логов.
```sql
CREATE TABLE `trans_log_detail` (
`log_detail_id` int(11) NOT NULL AUTO_INCREMENT,
`trans_log_id` binary(12) NOT NULL,
`log_detail` blob,
`create_time` datetime NOT NULL,
PRIMARY KEY (`log_detail_id`),
KEY `app_id` (`trans_log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
```CREATE TABLE `trans_log_unfinished` (
`trans_log_id` binary(12) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`trans_log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SELECT * FROM translog.trans_log_detail;
```
Для использования автоматической компенсации потребуется создание двух дополнительных таблиц в бизнес-базе данных:
```sql
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `fescar_lock` (
`t_name` varchar(64) NOT NULL,
`t_pk` varchar(128) NOT NULL,
`xid` varchar(256) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`t_name`, `t_pk`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
Подробные конфигурации будут представлены в будущих руководствах, но до этого вы можете обратиться за примерами к тестовым случаям в easytrans-starter или воспользоваться демонстрационной версией. ## 4. Расширяемость
Фреймворк использует интерфейсы для соединения различных модулей, обеспечивая высокую степень расширяемости. Рекомендуются следующие модули для расширения и замены:* Реализация RPC
* В настоящее время поддерживаются DUBBO, SPRING CLOUD RIBBON/EUREKA
* Приветствуем добавление дополнительной реализации
* Реализация очередей сообщений
* В настоящее время поддерживаются Alibaba ONS (при создании пожалуйста используйте незаконченные сообщения) и KAFKA
* Приветствуем добавление дополнительной реализации; требуется реализовать два ключевых метода
* Реализация сериализации
* В настоящее время используется сериализация Spring-core, которая менее эффективна, но производительность в данный момент не является бутылочным горлышком, поэтому нет необходимости в оптимизации
* Приветствуем добавление дополнительной реализации для повышения эффективности
* Конкретные реализации классов Filter для добавления, удаления и изменения
* В настоящее время доступны атомарные Filter, метаданные установочные FILTER, цепочка транзакций помощники
* При наличии дополнительных требований можно добавить новые Filter
* Реализация выборщика источников данных
* В настоящее время предоставляется одиночный выборщик источника данных
* При наличии нескольких источников данных для услуги необходимо самостоятельно реализовать бизнес-ассоциированный выборщик источника данных, выбирающий источник данных в зависимости от запроса
* Реализация журнала выполнения транзакций * В настоящее время предоставляются реализации на основе реляционных баз данных и Redis
* Для повышения эффективности рекомендуется самостоятельно реализовать журнал транзакций на основе других форматов, таких как файловая система, HBase, приветствуем PR
* Выборщик главного и вторичного сервера
* Выбор главного и вторичного сервера на основе ZooKeeper
* Выбор главного и вторичного сервера на основе MySQL
* Кодировщик строк
* Кодировка строк на основе ZooKeeper
* Кодировка строк на основе MySQL
* Генератор ID
* Генерация снежинкового ID на основе ZooKeeper
* Генерация снежинкового ID на основе MySQL
## 5. Наилучшие практики### Базирующиеся на журнале транзакций баз данных
* Размещайте журнал транзакций в отдельной базе данных от бизнес-базы данных
### Параметры и значения возврата
* Из-за затрат на сохранение данных, убедитесь, что параметры вызываемых методов и значения возврата минимальны
## 6. Часто задаваемые вопросы (FAQ)
1. Как определить после сбоя, был ли мягкий транзакт завершен?
* При вызове метода `startEasyTrans()`, фреймворк вставляет запись в `executed_trans`
* После вызова метода `startEasyTrans()` можно выполнять удалённые транзакционные методы
* Инициатор операции (основной транзакт) будет владеть замком записи `executed_trans` до момента откатывания или завершения основного транзакта
* Поэтому процесс восстановления после сбоя использует `select for update` для проверки записи `executed_trans`, всегда получая точный ответ о завершении транзакта (если основной транзакт всё ещё выполняется, `select for update` будет ждать)
* Использование `select for update` позволяет избежать ошибочного получения конечного результата транзакта при использовании MVCC## 7. Совместимость версий внешних компонентов
* Если вы не используете `extensionsuite-database-starter` и вам требуется ZK, используйте версию ZK 3.4 или выше
* Интеграция SpringBoot 2.0.x и SpringCloud F версии см. пример `tcc-and-fescar` в Demo
* Обратите внимание, что при создании документации последняя версия Spring Boot (2.0.8) включает багнутую версию `mysql-connector-java` (5.1.47), поэтому следует использовать 5.1.46
* Также из-за изменения больших версий Spring Cloud некоторые пакеты могут поменять названия, поэтому при использовании ET Ribbon в F версии необходимо добавить `spring-cloud-starter-netflix-ribbon` в проект
* В случае проблем совместимости пакетов ZK, проверьте версии связанных пакетов в вашем проекте
* С версии 1.2.0 `curator-recipes` и `curator-framework` должны быть версий 4.x.x, а клиентская версия ZooKeeper должна быть 3.4.x или 3.5.x (в зависимости от версии сервера ZK)
* До версии 1.2.0 `curator-recipes` и `curator-framework` должны быть версий 2.x.x, а клиентская версия ZooKeeper аналогична## 8. Другое
Приглашение к автору через личный WeChat и официальный аккаунт
Официальный аккаунт

WeChat (при добавлении в WeChat укажите источник ET)

Если вам понравился фреймворк, пожалуйста, сделайте star, спасибо
email: skyes.xu@qq.com Написал несколько дополнительных статей о ET, пожалуйста, поставьте лайк, чтобы помочь SEO:
* Как выбрать распределённые формы транзакций: [https://www.cnblogs.com/skyesx/p/9697817.html](https://www.cnblogs.com/skyesx/p/9697817.html)
* "Некоторые распределённые транзакционные фреймворки" и "Мои предпочтения": [https://www.cnblogs.com/skyesx/p/10041923.html](https://www.cnblogs.com/skyesx/p/10041923.html)
* Сравнение архитектур Seata и EasyTransaction: [https://www.cnblogs.com/skyesx/p/10674700.html](https://www.cnblogs.com/skyesx/p/10674700.html)
* Анализ основных исходных кодов EasyTransaction (пожалуйста, прочтите сначала статью "Сравнение архитектур Seata", затем изучите код и процесс отладки демонстрационной программы): [https://www.cnblogs.com/skyesx/p/11111726.html](https://www.cnblogs.com/skyesx/p/11111726.html)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )