Эта статья немного устарела, были добавлены некоторые новые функции. Вы можете обратиться к файлу readme.md
на китайском языке за новостями, либо вы можете отправить запрос, чтобы сообщить мне о необходимости обновления этой статьи.
(Русский не является моим родным языком, может кто-то поможет проверить текст ниже?)
Этот фреймворк вдохновлён презентацией "<Обработка распределённых транзакций в масштабах крупной системы SOA>" авторства Чэнь Ляо, работающего в Alibaba.
Целью этого фреймворка является решение проблемы повторного проектирования промежуточных состояний, бесперебойных реализаций и логики повторной попытки для каждого распределённого сценария транзакции в нашей компании.
С помощью этого фреймворка мы можем решить все найденные распределённые сценарии транзакций, снизить нагрузку дизайна и разработки, повысить эффективность разработки и обеспечить единую гарантию надёжности реализации транзакций.
Характеристики:* Фреймворк объединяет все виды транзакционных шаблонов.
по аналогии с традиционной локальной транзакцией, EasyTransaction полностью не вмешивается
Основная зависимость фреймворка — это класс TransactionSynchronization
библиотеки Spring. EasyTransaction можно использовать, если менеджер транзакций наследуется от AbstractPlatformTransactionManager
(в основном все менеджеры транзакций наследуются от этого исполнения). Кроме того, начиная с версии 1.0.0, фреймворк использует возможности конфигурации Spring Boot и функции JDK8, поэтому Spring Boot и JDK8 также являются обязательными компонентами.
Для распределённых транзакций фреймворк внедряет соответствующие операции фреймворка в TransactionSynchronization
перед вызовом метода удалённого транзакта, например:
при использовании надёжных сообщений, фреймворк отправляет сообщения после подтверждения состояния глобальной транзакции, обеспечивая, что сообщения могут быть видны внешним системам после подтверждения состояния глобальной транзакции, снижая нагрузку на программирование паттерна отправки-подтверждения-детектирования;
при использовании TCC, фреймворк вызывает методы Confirm или Cancel в зависимости от состояния глобальной транзакции.Фреймворк имеет фоновые потоки, ответственные за восстановление после аварийного завершения (например, для выполнения подтверждения или отката в TCC) на основе принципов «запись логов до выполнения распределённого вызова службы» и «запись данных транзакции для определения состояния глобальной транзакции».Фреймворк также имеет (опциональную) реализацию идемпотентности, которая гарантирует, что бизнес-методы логически выполняются только один раз (возможно, несколько раз, но повторно выполненные методы будут откатываться, так что бизнес-программы должны контролировать свою идемпотентность при работе с нероллабельными внешними ресурсами). Фреймворк также управляет порядком вызова методов, например, в шаблоне компенсации:
Если первоначальная операция не была вызвана (или задержана сетью), соответствующий метод компенсации, написанный пользователем, не будет выполнен (но фреймворк отметит, что компенсация уже выполнена).
Если методы компенсации отмечены как выполненные, то (задержанная) первоначальная операция, независимо от того, была ли она выполнена или нет, не будет вызвана.
Результаты всех удалённых вызовов возвращаются в виде объекта Future
, что даёт фреймворку пространство для оптимизации производительности, а все логи не будут записываться до попытки получения первого результата. Как только бизнес-программа пытается получить результат выполнения, она пишет логи в больших объёмах и последовательно запускает удалённые методы параллельно.Фреймворк проверяет наличие исключения в удалённых вызовах перед COMMIT. Если удалённый метод выбрасывает исключение, фреймворк откатывает бизнес-операцию до коммита. Это обеспечивает простоту модели программирования и облегчает правильность и понятность кода.
Бизнес-код может использовать EasyTransaction с помощью Maven:
<dependency>
<groupId>com.yiqiniu.easytrans</groupId>
<artifactId>easytrans-starter</artifactId>
<version>1.1.3</version>
</dependency>
Этот Starter содержит несколько стандартных реализаций, включая: распределённый журнал транзакций на основе RDBS, реализацию HTTP RPC на основе Netflix-Ribbon, очередь на основе Kafka. Если вы хотите заменить эти реализации, просто исключите их.
Для инициаторов бизнес-процесса фреймворк предоставляет только один интерфейс.
public interface EasyTransFacade {
/**
* Начать простую транзакцию
* @param busCode appId+busCode+trxId является глобально уникальным идентификатором, независимо от того, будет ли эта транзакция завершена или отменена
* @param trxId см busCode
*/
void startEasyTrans(String busCode, String trxId);
/**
* Выполнить удалённую транзакцию
* @param params
* @return
*/
<P extends EasyTransRequest<R, E>, E extends EasyTransExecutor, R extends Serializable> Future<R> execute(P params);
}
Удалённый метод можно вызвать напрямую, не учитывая конкретный тип распределённой транзакции и последующее обработку, как показано ниже:
@Transactional
public int покупатьЧто(int userId, long деньги) {
@Transactional
public int покупать_что(int userId, long деньги) {
``` /**
* завершите локальную транзакцию сначала, чтобы повысить производительность и создание бизнес-идентификатора
*/
Integer id = saveOrderRecord(jdbcTemplate, userId, деньги);
/**
* аннотировать глобальный транзакционный идентификатор, он объединяет appId + business_code + id
* эта строка кода может быть опущена, тогда фреймворк будет использовать "по умолчанию" как бизнес-код, и сгенерирует идентификатор
* но это сделает нам труднее связывать глобальную транзакцию с конкретной бизнес-операцией
*/
transaction.startEasyTrans(BUSINESS_CODE, id);
/**
* вызвать удалённый сервис для списания средств, это TCC сервис,
* фреймворк будет поддерживать конечную согласованность на основе окончательного статуса транзакции метода покупатьЧто
* если вы считаете введение объекта транзакции (EasyTransFacade) недопустимым связыванием
* то вы можете обратиться к другому демонстрационному примеру (interfacecall) в директории demos, он покажет вам, как выполнять транзакцию через пользовательски определенный интерфейс
*/
WalletPayRequestVO списатьЗапрос = new WalletPayRequestVO();
списатьЗапрос.setUserId(userId);
списатьЗапрос.setPayAmount(деньги);
/**
* вернуть будущее для повышения производительности (пакетное выполнение записи журнала и пакетное выполнение RPC)
*/
Future<WalletPayResponseVO> списатьБудущее = transaction.execute(списатьЗапрос);```markdown
/**
* Опубликовать сообщение при подтверждении этой глобальной транзакции,
* так что другие службы, подписанные на это событие, смогут получить это сообщение.
*/
OrderFinishedMessage заказЗавершенСообщение = new OrderFinishedMessage();
заказЗавершенСообщение.setUserId(userId);
заказЗавершенСообщение.setOrderAmt(деньги);
transaction.execute(заказЗавершенСообщение);
/**
* Вы можете добавить больше типов транзакций здесь, например SAGA-TCC、Компенсация и т.д.
* Фреймворк будет поддерживать конечную согласованность.
*/
/**
* Мы можем получить результат удалённого сервиса, чтобы определить, следует ли подтвердить эту транзакцию.
*
* списать_будущее.get();
*/
return id;
}
Примечание: В данном контексте были использованы русскоязычные названия для методов и переменных, чтобы обеспечить понятность переведенного текста. Однако, в реальных проектах такие названия могут быть адаптированы в соответствии с принятыми соглашениями и стандартами.#### Провайдер услуг Для провайдеров услуг реализуется соответствующий интерфейс и регистрируется в контейнере бинов Spring.
Например, в TCC требуется реализовать метод TccMethod
:
@Component
public class WalletPayTccMethod implements TccMethod<WalletPayTccMethodRequest, WalletPayTccMethodResult> {
@Resource
private WalletService walletService;
@Override
public WalletPayTccMethodResult doTry(WalletPayTccMethodRequest param) {
return walletService.doTryPay(param);
}
// Другие методы TccMethod...
}
``````java
@Override
public void doConfirm(WalletPayTccMethodRequest param) {
walletService.doConfirmPay(param);
}
@Override
public void doCancel(WalletPayTccMethodRequest param) {
walletService.doCancelPay(param);
}
WalletPayTccMethodRequest
— это параметр запроса, который является простым объектом JavaBeans (POJO), наследуемым от класса EasyTransRequest
. Для его использования требуется добавить аннотацию BusinessIdentifier
, которая указывает фреймворку значения AppID и бизнес-кода для данного запроса:
@BusinessIdentifier(appId = Constant.APPID, busCode = METHOD_NAME)
public class WalletPayTccMethodRequest implements TccTransRequest<WalletPayTccMethodResult> {
private static final long serialVersionUID = 1L;
private Integer userId;
private Long payAmount;
public Long getPayAmount() {
return payAmount;
}
public void setPayAmount(Long payAmount) {
this.payAmount = payAmount;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
Приведённый выше пример представляет собой традиционную форму вызова. Форма декомпозиции бизнес-логики выглядит следующим образом. Для более конкретного использования обратитесь к демонстрационному примеру (interfacecall
):
@Transactional
public String buySomething(int userId, long money) {
int id = saveOrderRecord(jdbcTemplate, userId, money);
// WalletPayRequestVO просто должен реализовать интерфейс Serializable
WalletPayRequestVO request = new WalletPayRequestVO();
request.setUserId(userId);
request.setPayAmount(money);
// payService — это сгенерированный фреймворком объект пользовательского интерфейса без каких-либо родительских классов
WalletPayResponseVO pay = payService.pay(request);
}
``` return "id:" + id + " заморожено:" + pay.getFreezeAmount();
}
Пожалуйста, обратитесь к директории easytrans-demo
.
Для более полной конфигурации и использования обратитесь к тестовым данным easytrans-starter
.
Каждая база данных предприятия должна добавить две таблицы.
-- Для записи того, находится ли глобальное состояние транзакции
-- Поля начинаются с 'p_', что представляет собой идентификатор родительской транзакции, соответствующий этой транзакции.
-- Когда выполняется select for update, если запись с соответствующим идентификатором транзакции отсутствует, то транзакция должна завершиться ошибкой.
-- Если запись существует, но статус равен 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 'источник транзакционного идентификатора',
`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 'идентификатор приложения обработчика',
`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;(Лог распределённой транзакции на основе RDBMS; если вы используете Redis, то это не обязательно.) Вам потребуется база данных RDBMS для записи логов транзакций и создание двух таблиц для неё. Каждый бизнес-сервис должен иметь базу данных логов транзакций. Несколько сервисов могут использовать одну базу данных логов транзакций. Создание таблицы `trans_log_detail`:
```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;
Создание таблицы trans_log_unfinished
:
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;
Фреймворк использует интерфейсы для соединения каждого модуля, что делает его гибко расширяемым. В следующих модулях рекомендовано расширяться:* Реализация RPC * Поддержка Alibaba DUBBO, SPRING CLOUD RIBBON/EUREKA в настоящий момент. * Добро пожаловать к дополнительной реализации.
Журналы транзакций на основе базы данных
Параметры и значения возврата
Как определить, был ли глобальный транзакт завершен после сбоев?
startEasyTrans()
фреймворк вносит запись в таблицу executed_trans
.startEasyTrans()
.executed_trans
до тех пор, пока мастер-транзакт не откатится или не завершится.select for update
, чтобы выбрать запись executed_trans
. Это гарантирует получение точной информации о том, был ли транзакт завершен (если мастер-транзакт всё ещё выполняется, то select for update
будет ждать).select for update
, чтобы избежать ошибочного запроса состояния глобального транзакта в модели многопользовательской версионной системы управления базами данных (MVCC).Публичный аккаунт WeChat автора
Если вам понравился этот фреймворк, пожалуйста, отметьте проект звездочками, спасибо.
Электронная почта: skyes.xu@qq.com
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )