Ли Вэй, общественное число, «кофейное латте», распределенная транзакция SEATA сообщество.
1. О Сеате
Недавно я написал анализ Fescar, связующего программного обеспечения для распределенных транзакций. Через несколько дней команда Fescar обновила свой бренд и назвала его Seata (Simpe Extensible Autonomous Transaction Architecture), полное название Fast & Easy Commit And Rollback. Видно, что название Fescar более ограничено Commit и Rollback, в то время как новый бренд Seata нацелен на создание универсального решения для распределенных транзакций. После смены названия у меня больше уверенности в его дальнейшем развитии.
Вот общий отзыв о всей модели процессов Seata:
- ТМ: Инициатор транзакции. Используется, чтобы сообщить TC, что глобальная транзакция запускается, фиксируется и откатывается.
- RM: определенные ресурсы транзакции, каждый RM будет зарегистрирован в TC как транзакция филиала.
- Координатор сделок ТС. Его также можно рассматривать как Fescar-сервер, который используется для получения регистрации, фиксации и отката наших транзакций.
В предыдущей статье есть общее введение ко всей роли, в этой статье я сосредоточусь на центральной роли TC, которая является координатором транзакций.
2.Transaction Coordinator
Почему раньше подчеркивалось, что TC является ядром? Это потому, что роль ТС подобна богу, контролирующему РМ и ТМ всех живых существ. Если ТС плохо работает, то раз есть небольшая проблема с РМ и ТМ, будет бардак. Так что чтобы понять Сеата, надо понять его ТС.
Итак, какими способностями должен обладать хороший координатор транзакций? Думаю должно быть следующее:
- Правильная координация: может правильно координировать, что РМ и ТМ должны делать дальше, что следует делать неправильно, а что следует делать правильно.
- Высокая доступность: координатор транзакций очень важен в распределенных транзакциях.Если высокая доступность не может быть гарантирована, то в его существовании нет необходимости.
- Высокая производительность: производительность координатора транзакций должна быть высокой.Если есть узкое место в производительности координатора транзакций, RM и TM, которыми он управляет, часто сталкиваются с тайм-аутами, что приводит к частым откатам.
- Высокая масштабируемость: эта функция находится на уровне кода.Если это отличная структура, вам необходимо предоставить пользователю ряд пользовательских расширений, таких как регистрация/обнаружение службы, чтение конфигурации и многое другое.
Ниже я постепенно объясню, как Seaka делает это.
2.1 Дизайн Seata-Server
Общая модульная схема Seata-Server показана выше:
- Ядро координатора: Нижний модуль представляет собой основной код координатора транзакций, который в основном используется для работы с логикой координации транзакций, например, следует ли фиксировать, откатывать и другие действия по координации.
- Магазин: модуль хранения используется для сохранения наших данных, чтобы предотвратить потерю данных при перезапуске или простое.
- Обнаружение: модуль регистрации/обнаружения службы, используемый для предоставления адреса сервера клиенту.
- Конфигурация: используется для хранения и поиска конфигурации на стороне сервера.
- Блокировка: модуль блокировки используется для обеспечения функции глобальной блокировки Seata.
- RPC: и для другого конца связи.
- HA-Cluster: Кластер высокой доступности, исходный код еще не открыт. Обеспечивает надежную высокую доступность для Seata.
2.2 Discover
Во-первых, давайте поговорим о базовом модуле Discover, также известном как модуль регистрации/обнаружения службы. После того, как мы запустим Seata-Server, нам нужно открыть наш адрес другим пользователям, поэтому нам нужен этот модуль, чтобы помочь.
Этот модуль имеет основной интерфейс RegistryService, как показано выше:
- register: используется сервером для регистрации службы.
- unregister: используется сервером, обычно вызывается в обработчике выключения JVM, ShutdownHook.
- подписка: используется клиентом для регистрации и отслеживания событий для отслеживания изменений адресов.
- unsubscribe: используется клиентом для отмены регистрации событий прослушивания.
- поиск: используется клиентом для поиска списка адресов службы на основе ключа.
- close: оба могут использоваться для закрытия ресурса Register.
Если вам нужно добавить собственную регистрацию/обнаружение службы, вы можете реализовать этот интерфейс. На сегодняшний день, благодаря постоянному развитию сообщества, было зарегистрировано/обнаружено четыре службы, а именно redis, zk, nacos и eruka. Ниже приводится краткое введение в реализацию Nacos:
2.2.1 регистровый интерфейс
шаг 1: проверьте, является ли адрес законным;
Шаг 2: Получите экземпляр Name Nacos, а затем зарегистрируйте адрес для текущего имени кластера.
отмените вроде интерфейс, тут не объяснить.
2.2.2 интерфейс поиска
step1: Получите текущее имя clusterName;
шаг 2: Определить, был ли получен текущий Кластер, и если да, то взять его с Карты;
шаг 3: Получить адресные данные от Nacos и преобразовать их в то, что нам нужно;
Шаг 4: Слушатель изменяет нашу регистрацию события на Nacos.
2.2.3 Интерфейс подписки
Этот интерфейс относительно прост и состоит из двух шагов:
шаг 1: добавьте Clstuer и Listener на карту;
Шаг 2: Зарегистрируйтесь в Nacos.
2.3 Config
Модуль конфигурации также является относительно базовым и относительно простым модулем. Нам нужно настроить некоторые общие параметры, такие как: количество потоков Select для Netty, количество потоков Work, максимально допустимый сеанс и т. д. Конечно, эти параметры имеют свои собственные настройки по умолчанию в Seata.
Точно так же в Seata также предоставляется конфигурация интерфейса, позволяющая настроить, где нам нужно получить конфигурацию:
- getInt/Long/Boolean/Config(): получить соответствующее значение через DataId.
- putConfig: используется для добавления конфигурации.
- removeConfig: удалить конфигурацию.
- добавить/удалить/получить ConfigListener: добавить/удалить/получить прослушиватели конфигурации, обычно используемые для отслеживания изменений конфигурации.
Пока есть четыре способа получения Config: File (получение файла), Nacos, Apollo, ZK. В Seata вам сначала нужно настроить реестр.conf, чтобы настроить тип conf. Реализовать conf относительно просто, и мы не будем здесь его подробно анализировать.
2.4 Store
Реализация уровня хранения имеет решающее значение для высокой производительности и надежности Seata.
Если уровень хранения не реализован должным образом, то в случае сбоя данные, которые транзакционно распределяются в TC, будут потеряны. Поскольку используются распределенные транзакции, они не должны терпеть потери. Если уровень хранения хорошо реализован, но его производительность имеет большие проблемы, RM может часто откатываться, поэтому он вообще не справляется со сценариями с высоким параллелизмом.
В Saka хранилище файлов предоставляется по умолчанию. Данные, хранящиеся ниже, определяются как сеанс, в то время как глобальные данные транзакции, созданные TM, называются GLOABSESSE, а ветвь транзакции, создаваемая RM, называется подсветкой. Одному глазунию может иметь несколько филиалов. Наша цель - хранить так много сеансов.
В FileTransactionStoreManager # WRiteSion код:
Приведенный выше код в основном разделен на следующие шаги:
Шаг 1: Создайте TransactionWriteFuture.
Шаг 2: добавьте этот futureRequest в LinkedBlockingQueue. Зачем нужно кидать все данные в очередь? Конечно, здесь также можно использовать блокировки, и блокировки, используемые в другом RocketMQ с открытым исходным кодом Alibaba. Будь то очередь или блокировка, их цель — обеспечить однопоточную запись, почему это так? Некоторые люди объяснят, что необходимо обеспечить последовательную запись, чтобы скорость была очень быстрой.Это понимание неверно.Наш FileChannel на самом деле потокобезопасен и уже может гарантировать последовательную запись. Обеспечение однопоточной записи на самом деле состоит в том, чтобы сделать всю логику записи однопоточной, потому что некоторые файлы могут быть заполнены или записывать место записи данных и другую логику.Конечно, эти логики можно активно блокировать, но для простоты и удобства, напрямую Наиболее целесообразно заблокировать всю логику записи.
step3: Вызвать future.get и дождаться уведомления о завершении логики записи данных.
После того, как мы отправим данные в очередь, нам нужно их использовать следующим образом.Код выглядит следующим образом:
Здесь WriteDataFileRunnable() отправляется в пул потоков Метод run() этого Runnable выглядит следующим образом:
Делится на следующие этапы:
step1: Определите, нужно ли останавливаться, и верните null, если остановка истинна.
Шаг 2: Получить данные из очереди.
Шаг 3: Определите, истекло ли время ожидания для будущего. Если время ожидания истекло, установите результат на false. В это время наш метод get () производителя будет заблокирован.
Шаг 4: Запишите данные в файл. В это время данные все еще находятся в слое pageCache и не были сброшены на диск. Если запись прошла успешно, то определите, следует ли выполнять операцию сброса в соответствии с условиями.
Шаг 5: Когда количество операций записи достигает определенного значения или когда время записи достигает определенного значения, вам необходимо сохранить текущий файл как файл истории, удалить предыдущий файл истории, а затем создать новый файл. Этот шаг предназначен для предотвращения бесконечного роста файлов и большого количества недопустимых данных, которые приводят к пустой трате дисковых ресурсов.
В writeDataFile есть следующий код:
Шаг 1: Сначала получите ByteBuffer, если он превышает максимальный циркулирующий BufferSize, создайте новый напрямую, в противном случае используйте кешированный буфер. Этот шаг может значительно уменьшить GC.
Шаг 2: Затем добавьте данные в ByteBuffer.
Шаг 3: Наконец, запишите ByteBuffer в fileChannel, что будет повторено три раза. В настоящее время данные все еще находятся на уровне pageCache. Затронутые двумя аспектами, ОС имеет собственную стратегию обновления, но эту бизнес-программу нельзя контролировать. Чтобы предотвратить потерю большого количества данных, вызванную такими событиями, как простои , бизнесу необходимо самостоятельно контролировать сброс. Вот код для сброса:
Условием очистки здесь является запись определенного количества или время записи превышает определенное время, поэтому будет небольшая проблема.Если произойдет сбой питания, в pageCache могут быть данные, которые не были очищены, что вызвать небольшую потерю данных. В настоящее время режим синхронизации не поддерживается, то есть каждые данные необходимо сбрасывать, что может обеспечить размещение каждого сообщения на диске, но это также сильно повлияет на производительность.Конечно, поддержка будет продолжать развиваться. в будущем.
Основной процесс Store — это в основном описанные выше методы, конечно, есть и такие, как реконструкция сеанса, которые относительно просты, и читатели могут прочитать их самостоятельно.
2.5 Lock
Все мы знаем, что уровень изоляции базы данных в основном достигается за счет блокировок, той же перераспределенной платформе транзакций Seata также нужны блокировки для достижения уровня изоляции. Как правило, в базе данных существует четыре уровня изоляции базы данных: незафиксированное чтение, зафиксированное чтение, повторяемое чтение и сериализованное. В Seata может быть гарантировано взаимное исключение операций записи, в то время как уровень изоляции операций чтения, как правило, не зафиксирован, но предусмотрены средства для достижения изоляции чтения-фиксации.
Модуль блокировки также является основным модулем, в котором Seata реализует уровень изоляции. Модуль Lock предоставляет интерфейс для управления замками:
Есть три метода:
- AcquireLock: используется для блокировки BranchSession.Хотя это сеанс переданной транзакции ветки, он фактически блокирует ресурсы транзакции ветки и возвращает true в случае успеха.
- isLockable: запрос, был ли он заблокирован в соответствии с идентификатором транзакции, идентификатором ресурса и заблокированным ключом.
- cleanAllLocks: очистить все блокировки.
Для блокировок мы можем реализовать их локально, или мы можем использовать Redis или MySQL, чтобы помочь нам реализовать их. Официальное значение по умолчанию обеспечивает реализацию локальной глобальной блокировки:
Есть две константы, которые требуют внимания при реализации локальных блокировок:
- BUCKET_PER_TABLE: сколько BUCKE используется для определения каждой таблицы с целью снижения конкуренции, когда они заблокированы одной и той же таблицей.
- LOCK_MAP: Эта карта очень сложна с точки зрения определения. Существует много слоев карты внутри и снаружи. Вот таблица, объясняющая это подробно:
слои | key | value |
1-LOCK_MAP | идентификатор ресурса (jdbcUrl) | dbLockMap |
2- dbLockMap | tableName (имя таблицы) | tableLockMap |
3- tableLockMap | PK.hashcode%Bucket (хэш-код%bucket значения первичного ключа) | bucketLockMap |
4- bucketLockMap | PK | trascationId |
Видно, что фактическая блокировка находится на карте BucketLockMap. Конкретный метод блокировки здесь относительно прост и не будет разработан. Главное - постепенно найти BucketLockMap, а затем вставить текущий TrackationID. Если основной ключ В настоящее время имеет трансцитацию, а затем сравнить, является ли он сам, если нет, замок не удается.
2.6 RPC
Одна из ключевых характеристик гарантируется Seata Netty, используемой в качестве RPC-инфраструктуры, с конфигурацией модели многопоточности по умолчанию, как показано ниже:
Если принята базовая конфигурация по умолчанию, будет поток Acceptor для обработки клиентского соединения и будут NIO-Threads с номером cpu * 2. В этом потоке не будет бизнес-тяжелых вещей, а только несколько более быстрых такие вещи, как кодеки, события сердцебиения и регистрации TM. Некоторые трудоемкие бизнес-операции будут переданы в пул бизнес-потоков. По умолчанию пул бизнес-потоков настроен на минимум 100 потоков и максимум 500.
Здесь следует упомянуть механизм сердцебиения Seata, который выполняется с помощью Netty IdleStateHandler следующим образом:
На стороне Севера нет максимального времени простоя на запись и максимальное время простоя на чтение, по умолчанию 15с, если превысит 15с, то линк отключится и ресурсы будут закрыты.
Шаг 1: Определите, является ли это событием обнаружения простоя чтения;
step2: Если это так, отключите ссылку и закройте ресурс.
2.7 HA-Cluster
В настоящее время HA-Cluster официально не анонсирован, но с помощью некоторого другого промежуточного программного обеспечения и некоторых официальных раскрытий HA-Cluster может быть разработан следующими способами:
Конкретный процесс выглядит следующим образом:
Шаг 1: когда клиент публикует информацию, он гарантирует, что одна и та же транзакция находится на одном и том же мастере в соответствии с TranscationId, и обеспечивает производительность параллельной обработки за счет горизонтального расширения нескольких мастеров.
Шаг 2: На стороне сервера у ведущего есть несколько ведомых, и данные в ведущем синхронизируются с ведомым почти в реальном времени, гарантируя, что, когда ведущее устройство не работает, есть другие ведомые устройства, которые можно использовать сверху.
Разумеется, все вышеперечисленное является догадками, а конкретный дизайн и реализацию придется подождать до версии 0.5. В настоящее время существует Go-версия Seata-Server, также подаренная Seata (все еще в процессе), которая реализует согласованность реплик через Raft, другие детали не слишком ясны.
2.8 Metrics & Tracing
Этот модуль также является модулем, для которого не объявлена конкретная реализация. Конечно, он может предоставлять подключаемый порт для доступа к другим сторонним метрикам. Также недавно Apache SkyWalking обсуждает с командой Seata, как попасть внутрь.
3.Coordinator Core
Выше мы говорили о множестве основных серверных модулей.Я думаю, что у всех есть обзор реализации Seata.Далее я объясню, как реализована конкретная логика координатора транзакций, чтобы каждый мог лучше понять реализацию Seata. .
3.1 Процесс запуска
Метод запуска имеет основной метод в классе Server, который определяет наш процесс запуска:
Шаг 1: Создайте RpcServer, который содержит операции нашей сети и реализует сервер с Netty.
Шаг 2: Проанализируйте номер порта и адрес файла.
Шаг 3: Инициализируйте SessionHoler, самое главное — восстановить наши данные в папке dataDir и перестроить нашу сессию.
Шаг 4: Создайте CoordinatorDinator, который также является логическим основным кодом нашего координатора транзакций, а затем инициализируйте его.Внутренняя логика инициализации создаст четыре задачи синхронизации:
- RectryRollbacking: повторите попытку задачи времени отката, которая используется для повторения этих неудачных откатов, выполненных каждые 5 мс.
- retryCommitting: повторите задачу синхронизации фиксации, которая используется для повторной попытки этих неудачных коммитов и выполняет ее каждые 5 мс.
- asyncCommitting: задача синхронизации асинхронной фиксации, используемая для выполнения асинхронной фиксации каждые 10 мс.
- timeoutCheck: определение времени ожидания задачи, используемое для обнаружения задач с истекшим временем ожидания, а затем выполнение логики времени ожидания, которая выполняется каждые 2 мс.
Шаг 5: Инициализация UUIDGenerator. Это также базовый класс для генерации различных идентификаторов (transcationId, branchId).
Шаг 6: Установите локальный IP-адрес и порт прослушивания на XID, инициализируйте rpcServer и дождитесь подключения клиента.
Процесс запуска относительно прост.Ниже я расскажу, как Seata обрабатывает некоторые общие бизнес-логики в структуре распределенных транзакций.
3.2 Начать - начать глобальную транзакцию
Отправной точкой распределенной транзакции должно быть открытие глобальной транзакции.Для начала посмотрим, как реализована глобальная транзакция Seata:
Шаг 1: Создайте GloabSession в соответствии с идентификатором приложения, группой транзакций, именем и временем ожидания.Это также упоминалось ранее о том, что такое GloabSession.
шаг 2: добавьте к нему RootSessionManager для прослушивания некоторых событий, вот четыре типа Listeners, которые в настоящее время находятся в Seata (здесь следует объяснить, что все sessionManager реализуют SessionLifecycleListener):
- ROOT_SESSION_MANAGER: самый полный и самый большой, со всеми сессиями.
- ASYNC_COMMITTING_SESSION_MANAGER: используется для управления сеансами, которым необходимо выполнять асинхронные фиксации.
- Retry_committing_Session_Manager: используется для управления сеансом попытки фиксации.
- Retry_rollbacking_session_manager: Используется для управления сеансом повторения.
Поскольку здесь открывается транзакция, другим SessionManager не нужно беспокоиться, мы добавляем только RootSessionManager.
Шаг 3: Открыть глобальную сессию
Этот шаг изменит состояние на Начало, запишет время начала и вызовет метод слушателя onBegin в RootSessionManager, чтобы сохранить сеанс на карте и записать его в наш файл.
Шаг 4: Наконец, верните XID. Этот XID состоит из ip+port+transactionId, что очень важно. Когда TM запрашивает его, ему необходимо передать идентификатор RM, а RM решает, к какому серверу следует обращаться через ХИД.
3.3 BranchRegister - регистрация транзакций филиала
Когда глобальная транзакция открыта в TM, ветвь транзакции RM также должна быть зарегистрирована в глобальной транзакции. Вот как оно обрабатывается:
Шаг 1: Получите и проверьте, открыта ли глобальная транзакция через transactionId.
Шаг 2: Создайте новую транзакцию филиала, то есть BranchSession.
Шаг 3: Добавьте глобальную блокировку в транзакцию филиала Логика здесь заключается в использовании логики модуля блокировки.
Шаг 4: Добавьте BranchSession, в основном, добавив его в объект globalSession и записав его в наш файл.
Шаг 5: Возвратите branchId, этот идентификатор также очень важен, нам нужно использовать его позже, чтобы откатить нашу транзакцию или обновить статус нашей транзакции ветки.
После того, как транзакция филиала зарегистрирована, также необходимо сообщить, был ли последующий статус транзакции филиала успешным или неудачным.В настоящее время сервер просто сохраняет запись.Цель отчета состоит в том, что даже если транзакция филиала не удалась , если TM по-прежнему настаивает на отправке глобальной транзакции, то при обходе и отправке транзакции ветвления не нужно отправлять транзакцию ветвления, которая не удалась.
3.4 GlobalCommit — глобальная фиксация
Когда транзакция ветки завершена, решается очередь к TM-Transaction Manager поданной или прокатной, если поданной, то она будет идти по следующей логике:
Шаг 1: Сначала найдите globalSession. Если он докажет, что это было зафиксировано для Null, то прямая идемпотентная операция возвращает успех.
Шаг 2: Закройте GloabSession, чтобы предотвратить повторное появление новой ветки.
Шаг 3: Если статус равен «Начало», то он давно не был отправлен, и изменение его статуса на «Подтверждение» означает отправку.
Шаг 4: Определите, может ли он быть отправлен асинхронно.В настоящее время асинхронно может быть отправлен только режим AT, потому что это делается Undolog. И MT, и TCC должны синхронно обрабатывать представленный код.
Шаг 5: если он отправляется асинхронно, поместите его непосредственно в ASYNC_COMMITTING_SESSION_MANAGER и дайте ему выполнить шаг 6 асинхронно в фоновом потоке.Если он синхронный, выполните шаг 6 напрямую.
шаг 6: Пройдите BranchSession для отправки.Если транзакция филиала не удалась, определите, следует ли повторить попытку в соответствии с различными условиями.Асинхронный не нужно повторять попытку, потому что все это находится в диспетчере, пока это не удастся, это не будет удалено и будет повторяться все время. Если оно отправлено синхронно, оно будет помещено в очередь асинхронных повторных попыток для повторной попытки.
3.5 GlobalRollback — глобальный откат
Если наша ТМ решит откатиться глобально, она пойдет по следующей логике:
Эта логика в основном такая же, как и процесс подчинения, который можно рассматривать как обратную ему сторону, и здесь он обсуждаться не будет.
4. Резюме
Наконец, в начале резюме мы выдвинули четыре ключевых момента распределенных транзакций, как их решает Seata:
- Правильная координация: несколько правильных повторных попыток через фоновые запланированные задачи, и платформа мониторинга будет запущена в будущем, и, возможно, можно будет выполнить откат вручную.
- Высокая доступность: Высокая доступность гарантируется благодаря HA-Cluster.
- Высокопроизводительность: Письменная последовательность файлов, RPC реализован с помощью Netty, а SACA может расширяться горизонтально и улучшать производительность обработки.
- Высокая масштабируемость: Предоставляет места, которые пользователи могут свободно использовать, такие как конфигурация, обнаружение и регистрация служб, глобальные блокировки и многое другое.
Наконец, я надеюсь, что из этой статьи каждый сможет понять основные принципы проектирования Seata-Server.Конечно, вы также можете представить, как спроектировать сервер, реализующий распределенную транзакцию, самостоятельно?
Ссылки по теме в статье
- Адрес гитхаба Seata: https://github.com/seata/seata
- Дальнейшее чтение:Открытый исходный код и практика распределенных транзакций Ant Financial | Подарок на первую годовщину с открытым исходным кодом SOFA
Официальная учетная запись: распределенная архитектура финансового уровня (Antfin_SOFA)