
Apache Seata превращает распределенные транзакционные кошмары в Spring Boot Dreams
11 июня 2025 г.В то время, когда ностальгия, возможно, зашла слишком далеко, мир разработки программного обеспечения был охвачен горячими дебатами: имеет массовое принятиеМикросервисыОбразец действительно обеспечил ожидаемые выгоды, или его баланс более неопределен? Многие команды начинают задаваться вопросом, пришло ли время «возвращения на родину» до старого старого, надежного монолита - или если ответ находится где -то посередине, в форме модульного монолита.
За исключением фактора стоимости, это переосмысление часто связано с неотъемлемой сложностью распределенных систем, особенно одной из их важнейших болевых точек:Распределенные транзакцииПолем
Эта статья направлена на то, чтобы точно решить эту тему, демонстрируя, какApache SeataВ этом случае в сочетании с ловкостью весеннего ботинка удается преобразовать то, что для многих является кошмаром в удивительно управляемое решение. Я покажу вам, как Seata может помочь вам крепко спать по ночам, устраняя необходимость в сложных архитектурах и стратегиях отката и/или прибегнуть к более громоздким узорам, таким какШаблон исходящих ящиковПолем
Apache Seata Apache Seata является распределенным с открытым исходным исходным решением, в настоящее время в инкубации Apache, которое обеспечивает высокую производительность и простые в использовании распределенных транзакционных служб в архитектуре микросервисов.
Основными компонентами являются:
Координатор транзакций (TC): поддерживать статус глобальных и филиалов, стимулируйте глобальный коммит или откат. Менеджер транзакций (TM): Определите объем глобальной транзакции: начинайте глобальную транзакцию, совершать или откатить глобальную транзакцию. Менеджер ресурсов (RM). Поддерживаемые модели транзакций: AT, XA (2pc) и Saga.
(Если вы предпочитаете пропустить теорию, перейдите прямо к практическому решению!)
XA (2pc) XA является спецификацией, выпущенной в 1991 году X/Open (которая впоследствии объединилась с открытой группой).
Фаза 1: подготовить (Commit voal): TC (Seata Server), спрашивает все участвующие RMS (например, ваши микросервисы, взаимодействующие с базами данных), если они готовы совершить свою локальную транзакцию. Каждый RM выполняет свои операции, записывает журналы транзакций для обеспечения долговечности и блокирует ресурсы для гарантирования изоляции. Если RM готов, он отвечает «да» на TC. Если нет, это отвечает «нет». 2. Фаза 2: Коммит или откат:
Если все RMS отвечают «да», TC отправляет команду «Commit» на все RMS. Они завершают свои локальные транзакции и выпускают замки. Если какой -либо RM отвечает «нет», или если TC обнаруживает тайм -аут/сбой, TC отправляет команду «откат». Все среднеквадратичные помещения отменили их операции и выпускают замки. Плюсы и минусы (кратко):
Pro: обеспечивает прочную консистенцию данных (кислоту) для нескольких услуг, действуя как одна нерушимая транзакция. CON: может привести к блокированию ресурсов (высокая задержка), если участники или координатор являются медленными или сбой, потенциально влияя на доступность и масштабируемость. Он также опирается на базовые базы данных/ресурсы, поддерживающие стандарт XA. Сага Паттерн Саги является широко принятым подходом в архитектурах микросервисов для управления распределенными транзакциями. В отличие от 2pc, Saga жертвует немедленной сильной последовательности для более высокой доступности и масштабируемости, достигая возможной последовательности.
Сага - это последовательность локальных транзакций, где каждая локальная транзакция (в пределах одного микросервиса) обновляет свою собственную базу данных, а затем публикует событие или сообщение, чтобы запустить следующую локальную транзакцию в последовательности.
Никаких глобальных замков. Крайне, локальные транзакции сразу совершают и не имеют глобальных замков, что позволяет повысить параллелизм. Компенсация за неудачу: если какая -либо локальная транзакция не удается, сага не «откатана» в традиционном смысле. Вместо этого он выполняет серию компенсационных транзакций, чтобы семантически отменить эффекты ранее завершенных локальных транзакций. Эти компенсационные транзакции являются новыми операциями, предназначенными для обращения за влиянием на бизнес. Сага может быть реализована через:
Хореография: Услуги публикуют события и реагируют на них, что приводит к децентрализованному потоку. Оркестрация: центральный оркестратор координирует поток, отправляя команды и реагируя на ответы. Плюсы и минусы (кратко):
Pro: Отлично подходит для высокой доступности и масштабируемости из-за отсутствия давних распределенных замков. Идеально подходит для слабо связанных микросервисов. CON: достигает возможной последовательности, то есть данные могут быть временно непоследовательными. Требует значительных усилий по разработке для реализации всех компенсационных транзакций и управления сложной логикой саги, которая также может усложнить отладку. В режиме AT (автоматическая транзакция) есть флагманское решение Seata, направленное на то, чтобы предложить легкость использования 2 % с неблокирующим характером и масштабируемостью пособий SAGA. Это рекомендуемый дефолт для большинства микросервисов с использованием реляционных баз данных.
Фаза 1: Локальная транзакция и подготовка: когда микросервис (RM) выполняет операцию базы данных (например, обновление, вставка, удаление) в рамках глобальной транзакции: интеллектуальная данных Seata, перехватывает SQL. Он автоматически создает undo_log (запись состояния данных до модификации). Операция SQL выполняется сразу же в локальной базе данных. Затем Seata приобретает глобальный замок для модифицированного ресурса через координатора транзакций (TC). Эта блокировка не является традиционной блокировкой базы данных; Это предотвращает одновременно изменять другие глобальные транзакции, но не блокирует операции чтения. RM сообщает TC, что ее транзакция в отрасли «подготовлена». 2. Фаза 2: Глобальный коммит или откат:
Глобальный коммит: если все транзакции филиалов успешно готовятся, ТС инструктирует их совершать. Поскольку местные транзакции DB уже были совершены на фазе 1, RMS просто выпускает свои глобальные замки. Глобальный откат: если какая -либо филиальная транзакция не удается, или глобальная транзакция должна отказываться: TC инструктирует RMS обратно. RMS использует их сохраненный undo_log, чтобы автоматически компенсировать изменения, внесенные в их локальные базы данных, эффективно восстанавливая предыдущее состояние. Затем они выпускают свои глобальные замки. Плюсы и минусы (кратко):
Pro: обеспечивает сильную последовательность для глобальной транзакции. Предлагает отличную доступность и масштабируемость, поскольку локальные блокировки базы данных удерживаются лишь кратко. Это очень прозрачно для разработчиков, требующих минимальных изменений кода. Автоматический откат упрощает обработку ошибок. Con: в основном разработан для реляционных баз данных. В то время как не блокируя на уровне БД, все еще существует накладные расходы от создания undo_logs и управления глобальными замками.
Практическая демонстрация с весенним загрузкой
Давайте представляем сценарий с участиемДва различных микросервиса, каждый работает со своей собственной выделенной и автономной базой данных:
- Кредитный API: Отвечает за управление кредитом пользователя (баланс денег)
- Доставка API: Посвящается обработке покупок отгрузки
Орчест на эти две услугиBFF (бэкэнд для Frontend)Полем Его роль состоит в том, чтобы координировать операцию по покупке отгрузки, что переводится на последовательность распределенных вызовов:
- Проверка и/или вычитать пользовательский кредитчерез кредитный API.
- На самом деле покупка отправкиИспользуя кредит, через транспортный API.
Тогда возникает критический вопрос: как мы можем гарантировать, что эти операции, распределенные по различным службам и базам данных, поддерживали ихтранзакционная последовательность, гарантируя, что покупка завершена только в том случае, если кредит был успешно обновлен, и наоборот?
Архитектура
ТМ
BFF будет представлять менеджер транзакций, то есть он будет определять глобальную транзакцию.
@Service
public class BFFManager {
@Autowired
private CreditService creditService;
@Autowired
private ShippingService shippingService;
@GlobalTransactional
public ShippingResult buyShipping(Long userID, BigDecimal cost) {
var wallet = creditService.updateBalance(userID, cost);
var shipping = shippingService.buyShipping(userID, cost);
wallet = creditService.getWallet(userID);
var result = new ShippingResult();
result.setCost(cost);
result.setShippingID(shipping.getId());
result.setCurrentBalance(wallet.getBalance());
return result;
}
}
Так что@GlobaltransactionalАннотация достаточно? Конечно, нет, но мало что нужно:
Зависимости, необходимые для TM:
implementation 'org.apache.seata:seata-spring-boot-starter:2.3.0'
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-seata:2023.0.3.3') {
exclude group: 'org.apache.seata', module: 'seata-spring-boot-starter'
}
Весенний-клуб-Starter-Alibaba-Seata, среди прочего, он может сделать, гарантирует, что HTTP -связь между микросервисами всегда содержатГлобальный идентификатор транзакции (Xid)
Seata-Spring-Boot-Starterэто классический стартовый стартер весеннего ботинка, который автоконфигурирует сущность Seata (в данном случае TM), начиная с свойств:
seata:
enabled: true
data-source-proxy-mode: AT
enable-auto-data-source-proxy: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
Rm
Кредитный APIисудоходство-апидействовать как rm. Им нужна только зависимостьSeata-Spring-Boot-StarterС следующими свойствами:
seata:
enabled: true
data-source-proxy-mode: AT
enable-auto-data-source-proxy: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
Необходимо, чтобы RM DB содержалundo_logСтол для Seata.
В коде, который вы найдете на GitHub, создание таблицы управляется через композицию Docker, которая создает выделенный DB (через org.springframework.boot: спружите
В RMS не требуется конкретная аннотация. Напишите свой код в хранилище, как всегда. Если вы хотите, и я рекомендую это, продолжайте использовать@TransactionalДля местных транзакций.
ТК
ТС представлен Сердцем сито,Seata-сервер.Это требует двух основных конфигураций:
- Реестр: определяетРеестр услугЭто будет использовать Seata (Nacos, Eureka, Consul, Zookeper, Redis, File). Для этого примера я решил использоватьфайлтип
- Хранить: определяет постоянство данных о глобальных транзакциях и глобальных замков (файл, DB, Redis). Для этого примера я решил использоватьдБтип.
Вот настройка Docker Compose, используемая для инициализации сервера:
services:
mysql:
image: mysql:8.0.33
container_name: mysql-seata
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: seata
MYSQL_USER: seata_user
MYSQL_PASSWORD: seata_pass
ports:
- "3317:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./docker/seata/mysql.sql:/docker-entrypoint-initdb.d/seata.sql:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-prootpass"]
interval: 10s
timeout: 5s
retries: 5
seata-server:
image: apache/seata-server:2.3.0
container_name: seata-server
depends_on:
mysql:
condition: service_healthy
environment:
- SEATA_CONFIG_NAME=application.yml
volumes:
- "./docker/seata/resources/application.yml:/seata-server/resources/application.yml"
- "./docker/seata/mysql-connector-j-8.0.33.jar:/seata-server/libs/mysql-connector-j-8.0.33.jar"
ports:
- "7091:7091"
- "8091:8091"
volumes:
mysql_data:
И свойства конфигурации (Application.yaml):
server:
port: 7091
spring:
application:
name: seata-server
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata
username: seata_user
password: seata_pass
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${user.home}/logs/seata}
console:
user:
username: seata
password: seata
seata:
security:
secretKey: seata
tokenValidityInMilliseconds: 1800000
config:
type: file
registry:
type: file
store:
mode: db
db:
dbType: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata
user: seata_user
password: seata_pass
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
vgroup-table: vgroup_table
query-limit: 1000
max-wait: 5000
Как вы могли заметить, специальной базе данных требуется несколько таблиц для функционирования. Все сценарии создания SQL доступны
Играть!
@SpringBootTest
public class GlobalTransactionalTest {
@Autowired
private BFFManager bffManager;
@Autowired
private CreditService creditService;
@MockitoBean
private ShippingService shippingService;
@Test
public void globalTransactionalTest_OK() {
var wallet = creditService.getWallet(1L);
var shipping = new Shipping();
shipping.setId(2L);
when(shippingService.buyShipping(1L, new BigDecimal(4))).thenReturn(shipping);
bffManager.buyShipping(1L, new BigDecimal(4));
var newWallet = creditService.getWallet(1L);
assertEquals(new BigDecimal("4.00"), wallet.getBalance().subtract(newWallet.getBalance()));
}
@Test
public void globalTransactionalTest_KO() {
var wallet = creditService.getWallet(1L);
var shipping = new Shipping();
shipping.setId(2L);
when(shippingService.buyShipping(1L, new BigDecimal(4))).thenThrow(new RuntimeException());
try {
bffManager.buyShipping(1L, new BigDecimal(4));
} catch (Exception e) {}
var newWallet = creditService.getWallet(1L);
assertEquals(newWallet.getBalance(), wallet.getBalance());
}
}
Полный иработающийКод доступен на
Ключевые альтернативы
Атомикос : Xa (2pc). Он работает только в одном и том же микросервисе, который обращается к нескольким базам данных (нет координатора транзакций, нет распространения XID)- Двигатель рабочего процесса для саги -оркестровки (
Камнда ВThemoral.io ): Требуется внешний оркестратор и более высокие усилия по интеграции. - SAGA (хореографическое событие): возможный компромисс.
- Образец Outbox: возможная последовательность.
Эта статья не направлена на то, чтобы продвигать Apache Seata по сравнению с другими упомянутыми альтернативами, а скорее подчеркнуть еепростота использованияПолем Как всегда, правильный инструмент должен быть выбран на основе конкретного контекста и системных требований.
Следующий эпизод: «Насколько круты были монолиты? »Следите за обновлениями.
Оригинал