|
Персональные инструменты |
|||
|
|
Инфраструктура System.Transactions в мире .NETМатериал из CustisWikiГеоргий Минашин, наш старший разработчик C#, опубликовал на портале «Хабр» пост, посвященный инфраструктуре System.Transactions. Что такое транзакции и когда они бывают полезны? Обязательно ли использовать СУБД для поддержания целостности данных? Какие инструменты для управления транзакциями есть в .NET? Об этом — в материале «Инфраструктура System.Transactions в мире .NET». Встречали ли вы в C# конструкцию типа Что за класс В данной публикации я постарался собрать материал, помогающий найти ответы на указанные вопросы и сформировать понимание транзакций в мире .NET. Содержание
ВведениеЧто такое транзакции и какие проблемы они решаютТранзакции, о которых здесь идет речь, — это операции, переводящие систему из одного приемлемого состояния в другое и гарантированно не оставляющие систему в неприемлемом состоянии даже при возникновении непредвиденных ситуаций. Что это за приемлемые состояния, в общем случае зависит от контекста. Здесь мы будем считать приемлемой ситуацию, в которой обрабатываемые нами данные целостны. При этом подразумевается, что изменения, составляющие транзакцию, все вместе либо совершаются, либо не совершаются. Кроме того, изменения одной транзакции могут быть изолированы от изменений, вносимых в систему другой транзакцией. Основные требования, предъявляемые к транзакциям, обозначаются аббревиатурой ACID. Для первого знакомства с ними подойдет статья в «Википедии». Классический пример транзакции — перевод денег между двумя счетами. В этой ситуации снятие денег со счета № 1 без зачисления на счет № 2 неприемлемо, точно так же, как и зачисление на счет № 2 без снятия со счета № 1. Другими словами, мы хотим, чтобы обе операции — и снятие, и зачисление — выполнялись сразу. Если же какую-то из них выполнить не удастся, то и вторая операция выполняться не должна. Можно называть этот принцип «все или ничего». Более того, желательно, чтобы операции выполнялись синхронно даже в случае таких системных сбоев, как отключение электроэнергии, то есть чтобы мы видели систему в приемлемом состоянии, как только она станет доступна после восстановления. В математических терминах можно сказать, что относительно системы существует инвариант, который мы хотели бы обязательно сохранить. Например, сумма на обоих счетах: нужно, чтобы после транзакции (перевода денег) сумма осталась такой же, как и до нее. Кстати, в классическом примере с переводом денег еще и фигурирует учет — предметная область, где естественным образом возникло понятие транзакции. Проиллюстрируем пример с переводом денег между двумя счетами. На первой картинке изображена ситуация, когда перевод 50 рублей со счета № 1 на счет № 2 завершился успешно. Зеленый цвет подчеркивает, что система находится в приемлемом состоянии (данные целостны). Теперь представим, что перевод осуществляется вне транзакции и после снятия денег со счета № 1 произошел сбой, из-за которого снятые деньги не были начислены на счет № 2. Система окажется в неприемлемом состоянии (красный цвет). Если же ошибка произошла между операциями снятия и зачисления, но перевод осуществлялся в рамках одной транзакции, то операция снятия денег будет отменена. В результате система останется в исходном приемлемом состоянии. Приведу примеры ситуаций из опыта нашей компании, в которых транзакции бывают полезны: учет товаров (учет количества товаров разных видов, которые находятся в определенных хранилищах и в пути), учет ресурсов хранилищ (учет объема помещения, который занимают товары определенного типа, объема помещения, свободного для размещения товаров, количества товаров, которое могут переместить сотрудники и автоматизированные системы хранилища за день). Проблемы, возникающие при нарушении целостности данных, очевидны. Информация, предоставляемая системой, не просто становится недостоверной — она теряет связь с действительностью и превращается в бессмыслицу. Какие транзакции здесь рассматриваютсяПредоставляемые транзакциями выгоды известны. Так что же, для поддержания целостности данных нам нужна реляционная база данных, поскольку именно там делаются транзакции? Не совсем. Выше было сказано, что понятие транзакции зависит от контекста, и сейчас кратко рассмотрим, о каких транзакциях можно говорить при обсуждении информационных систем. Для начала разделим понятия транзакций предметной области (бизнес-транзакций) и системных транзакций. Вторые могут быть реализованы в разных местах и разными способами. Зайдем с самого высокого уровня — предметной области. Интересант может объявить, что существуют некоторые приемлемые состояния и он не желает видеть информационную систему вне этих состояний. Не будем придумывать лишних примеров: перевод денег между счетами здесь подходит. Только уточним, что перевод — это не обязательно перевод денег между расчетными счетами двух клиентов банков. Не менее важна и задача бухгалтерского учета, когда счета должны отражать источники и предназначение средств организации, а перевод — изменение распределения средств по этим источникам и предназначению. Это был пример транзакции предметной области. Теперь посмотрим самые распространенные и интересные примеры реализации системных транзакций. В системных транзакциях разными техническими средствами обеспечиваются требования предметной области. Классическим проверенным решением такого рода являются транзакции реляционных СУБД (первый пример). Современные системы управления БД (как реляционными, так и не очень) предоставляют механизм транзакций, который позволяет или сохранить (зафиксировать) все сделанные за указанный период работы изменения, или отказаться от них (откатить). При использовании такого механизма операции снятия денег с одного счета и зачисления на другой счет, составляющие транзакцию предметной области, средствами СУБД будут объединены в системную транзакцию и либо выполнятся вместе, либо не выполнятся вовсе. Пользоваться СУБД, конечно, не обязательно. Грубо говоря, можно вообще реализовать механизм транзакций СУБД на своем любимом языке программирования и наслаждаться неустойчивым и заваленным ошибками аналогом существующих инструментов. Зато ваш «велосипед» может быть оптимизирован для конкретных ситуаций предметной области. Есть и более интересные варианты. Современные промышленные языки программирования (C# и Java в первую очередь) предлагают средства, предназначенные специально для организации транзакций с участием совершенно разных подсистем, а не только СУБД. В данной публикации будем называть такие транзакции программными. В случае C# это транзакции из пространства имен System.Transactions (второй пример), именно о них и рассказывается ниже. Прежде чем перейти к транзакциям Системные транзакции могут включать несколько информационных систем, в таком случае они становятся распределенными. Распределенными могут быть и транзакции СУБД, и программные; все зависит от того, какую функциональность поддерживает конкретное средство реализации транзакции. Подробнее распределенные транзакции рассматриваются в соответствующем разделе. Приведу картинку, чтобы было легче разобраться в обсуждаемых предметах. TL;DR по разделуСуществуют процессы, которые состоят из нескольких неделимых (атомарных) операций, применяемых к системе, в общем случае не обязательно информационной. Каждая неделимая операция может оставить систему в неприемлемом состоянии, когда целостность данных нарушается. Например, если перевод денег между двумя счетами представлен двумя неделимыми операциями снятия со счета № 1 и зачисления на счет № 2, то выполнение только одной из этих операций нарушит целостность данных. Деньги или пропадут неизвестно куда, или появятся неизвестно откуда. Транзакция объединяет неделимые операции так, чтобы они выполнялись все вместе (разумеется, последовательно, если это необходимо) или не выполнялись вообще. Можно говорить о транзакциях предметной области и о транзакциях в технических системах, которые обычно реализуют транзакции предметной области. Транзакции на основе System.TransactionsЧто этоВ мире .NET существует программный каркас, предназначенный создателями платформы для управления транзакциями. С точки зрения программиста, пользующегося транзакциями, этот каркас состоит из типов Транзакции из пространства имен Типовой сценарий использования транзакций для программиста C# следующий: using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { // Какой-то транзакционный код, например работа с СУБД. scope.Complete(); } Внутри блока Получается, что во время отката транзакции все операции, сделанные внутри такого блока Управляющие ресурсамиЧтобы нечто поддерживало транзакции на основе Какие бывают управляющие ресурсами? Если мы работаем из C# с СУБД, например SQL Server или Oracle Database, то обычно пользуемся соответствующими драйверами, а они и являются управляющими ресурсами. В коде они представляются типами Кроме управляющих ресурсами, у нас должен быть еще управляющий транзакциями (Transaction Manager), который будет следить за транзакцией и своевременно отдавать распоряжения управляющим ресурсами. В зависимости от того, какие управляющие ресурсами участвуют в транзакции (какие у них характеристики и где они расположены), к работе подключаются разные управляющие транзакциями. При этом выбор подходящей версии происходит автоматически и не требует вмешательства программиста. Если говорить конкретнее, то управляющий ресурсами — это экземпляр класса, реализующего специальный интерфейс Ясно, что во время выполнения может изменяться набор управляющих ресурсами, участвующих в транзакции. Например, после входа в блок Виды управляющих ресурсамиУправляющие ресурсами можно разделить на две большие группы: долговечные и непостоянные. Долговечный управляющий ресурсами (Durable Resource Manager) — управляющий ресурсами, который поддерживает транзакцию даже в случае недоступности информационной системы (например, при перезапуске компьютера). Непостоянный управляющий ресурсами (Volatile Resource Manager) — управляющий ресурсами, который не поддерживает транзакцию в случае недоступности информационной системы. Непостоянный управляющий ресурсами поддерживает транзакции только в оперативной памяти. Классические долговечные управляющие ресурсами — это СУБД (или драйвер СУБД для программной платформы). Что бы ни произошло — хоть сбой в работе операционной системы, хоть отключение электроэнергии, — СУБД будет гарантировать целостность данных после того, как она снова придет в рабочее состояние. За это, разумеется, приходится платить некоторыми неудобствами, но в данной статье мы их рассматривать не будем. Пример непостоянного управляющего ресурсами — упомянутая выше программная транзакционная память. Использование TransactionScopeПри создании объекта типа Во-первых, есть настройка, сообщающая среде выполнения о том, что нужно: 1) пользоваться уже существующей к этому моменту транзакцией; 2) обязательно создавать новую; 3) наоборот, выполнять код внутри блока За все это отвечает перечисление Во-вторых, можно задать уровень изоляции транзакции. Это параметр, который позволяет найти компромисс между независимостью изменений и скоростью работы. Самый независимый уровень — сериализуемый — гарантирует, что не существует таких ситуаций, когда изменения, внесенные в рамках одной еще не зафиксированной транзакции, можно увидеть в другой транзакции. Каждый следующий уровень добавляет по одной такой конкретной ситуации, когда одновременно выполняющиеся транзакции могут влиять друг на друга. По умолчанию транзакция открывается на сериализуемом уровне, что может быть неприятно (см., например, этот комментарий). Настройка уровня изоляции транзакции при создании Типовые сценарии работы c TransactionScope и существенные подводные камни (а именно вложенные транзакции) хорошо освещены в этой статье на «Хабре». Применимость программных транзакцийСледует сказать, что почти в любой информационной системе, находящейся в промышленной эксплуатации, запускаются процессы, которые могут привести систему в неприемлемое состояние. Поэтому бывает необходимо контролировать эти процессы, выяснять, является ли текущее состояние системы приемлемым, и, если нет, восстанавливать его. Программные транзакции — готовый инструмент для поддержания системы в приемлемом состоянии. В каждом конкретном случае конструктивно было бы рассматривать стоимость: 1) интеграции процессов в инфраструктуру программных транзакций (нужно, чтобы эти процессы знали о 2) поддержания этой инфраструктуры (например, стоимость аренды оборудования с Windows на борту); 3) обучения сотрудников (так как тема транзакций .NET мало распространена). Нельзя забывать и о том, что от транзакционного процесса может потребоваться сообщать о своем прогрессе «внешнему миру», например вести журнал действий вне транзакции. Очевидно, отказ от программных транзакций потребует создания или внедрения какого-то другого средства поддержания целостности данных, которое также будет иметь свою стоимость. В конце концов, возможны случаи, когда нарушения целостности данных так редки, что проще восстанавливать приемлемое состояние системы оперативными вмешательствами, чем поддерживать механизм автоматического восстановления. Пример непостоянного управляющего ресурсамиСейчас рассмотрим пример простейшего управляющего ресурсами, не поддерживающего восстановление после системного сбоя. У нас будет блок программной транзакционной памяти, хранящий некоторое значение, которое можно читать и перезаписывать. В отсутствие транзакции этот блок ведет себя как обычная переменная, а при наличии транзакции он сохраняет начальное значение, которое может быть восстановлено после отката транзакции. Код такого управляющего ресурсами представлен далее: internal sealed class Stm<T> : System.Transactions.IEnlistmentNotification { private T _current; private T _original; private bool _enlisted; public T Value { get { return _current; } set { if (!Enlist()) { _original = value; } _current = value; } } public Stm(T value) { _current = value; _original = value; } private bool Enlist() { if (_enlisted) return true; var currentTx = System.Transactions.Transaction.Current; if (currentTx == null) return false; currentTx.EnlistVolatile(this, System.Transactions.EnlistmentOptions.None); return true; } #region IEnlistmentNotification public void Commit(System.Transactions.Enlistment enlistment) { _original = _current; _enlisted = false; } public void InDoubt(System.Transactions.Enlistment enlistment) { _enlisted = false; } public void Prepare(System.Transactions.PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); } public void Rollback(System.Transactions.Enlistment enlistment) { _current = _original; _enlisted = false; } #endregion IEnlistmentNotification } Видно, что единственным формальным требованием является реализация интерфейса Дальше приведен код, показывающий пример использования нашего управляющего ресурсами: var stm = new Stm<int>(1); using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { stm.Value = 2; scope.Complete(); } Если сразу после выхода из блока Упрощенно последовательность вызовов при работе с транзакциями Видно, что в данном примере рассмотрены не все возможности, предоставляемые инфраструктурой TL;DR по разделуПрограммист может использовать класс Непостоянный управляющий ресурсами — это класс, реализующий интерфейс Распределенные транзакцииЧто этоРаспределенная транзакция задействует несколько информационных подсистем (на самом деле не все так просто, ниже об этом рассказано подробнее). Подразумевается, что изменения во всех системах, участвующих в распределенной транзакции, должны или фиксироваться, или откатываться. Выше были представлены разные средства реализации транзакций: СУБД, инфраструктура Транзакционные протоколыТранзакционный протокол — набор принципов, по которым взаимодействуют участвующие в транзакции приложения. В мире .NET чаще всего встречаются следующие протоколы. Lightweight. Используется не более одного долговечного управляющего ресурсами. Все транзакционные взаимодействия происходят внутри одного домена приложений, либо управляющий ресурсами поддерживает продвижение и однофазную фиксацию (реализует OleTx. Допускается взаимодействие между несколькими доменами-приложениями и несколькими компьютерами. Допускается использование множества долговечных управляющих ресурсами. Все участвующие компьютеры должны находиться под управлением Windows. Используется удаленный вызов процедуры (Remote Procedure Calls, RPCs). WS-AT. Допускается взаимодействие между несколькими доменами-приложениями и несколькими компьютерами. Допускается использование множества долговечных управляющих ресурсами. Участвующие компьютеры могут находиться под управлением различных ОС, не только Windows. Используется протокол передачи гипертекста (Hypertext Transmission Protocol, HTTP). Выше было отмечено, что действующий транзакционный протокол влияет на выбор управляющего транзакциями, а на выбор протокола влияют характеристики управляющих ресурсов, участвующих в транзакции. Теперь перечислим известные управляющие транзакциями. Lightweight Transaction Manager (LTM). Представлен в .NET Framework 2.0 и в более поздних версиях. Управляет транзакциями, использующими протокол Lightweight. Kernel Transaction Manager (KTM). Представлен в Windows Vista и Windows Server 2008. Управляет транзакциями, использующими протокол Lightweight. Может вызывать транзакционную файловую систему (Transactional File System, TxF) и транзакционный реестр (Transactional Registry, TxR) в Windows Vista и Windows 2008. Distributed Transaction Coordinator (MSDTC). Управляет транзакциями, использующими протоколы OleTx и WS-AT. Нужно также иметь в виду, что часть управляющих ресурсами не поддерживает все перечисленные протоколы. Например, MSMQ и SQL Server 2000 не поддерживают Lightweight, поэтому транзакции с участием MSMQ или SQL Server 2000 будут управляться MSDTC, даже если они являются единственными участниками. Технически это ограничение возникает от того, что указанные управляющие ресурсами, реализуя, конечно, интерфейс Сейчас должна стать очевидной неоднозначность понятия распределенной транзакции. Например, можно определять распределенную транзакцию как транзакцию, в которой участвует: 1) не менее двух любых управляющих ресурсами; 2) сколько угодно непостоянных управляющих ресурсами и не менее двух долговечных; 3) не менее двух любых управляющих ресурсами, обязательно расположенных на разных компьютерах. Поэтому лучше всегда уточнять, о каких именно транзакциях идет речь. И в этом контексте в первую очередь обсуждается MSDTC. Он представляет собой программный компонент Windows, управляющий распределенными транзакциями. Имеется графический интерфейс для конфигурации и наблюдения за транзакциями, который можно обнаружить в утилите «Службы компонентов», пройдя по пути «Компьютеры — Мой компьютер — Координатор распределенных транзакций — Локальная DTC». Для конфигурации нужно выбрать пункт «Свойства» в контекстном меню узла «Локальная DTC», а для наблюдения за распределенными транзакциями — пункт «Статистика транзакций» в центральной панели. Двухфазная фиксацияЕсли в транзакции участвует несколько управляющих ресурсами, то результаты их работы могут отличаться: например, у одного из них все завершилось успешно, и он готов фиксировать изменения, а у другого произошла ошибка, и он собирается откатить изменения. Однако суть распределенной транзакции заключается как раз в том, что изменения всех участвующих в транзакции управляющих ресурсов либо фиксируются все вместе, либо откатываются. Поэтому в таких случаях обычно применяется протокол двухфазной фиксации. В общем суть этого протокола заключается в следующем. Во время первой фазы участвующие в транзакции управляющие ресурсами подготавливают информацию, достаточную для восстановления после сбоя (если это долговечный управляющий ресурсами) и для успешного окончания работы в результате фиксации. С технической точки зрения управляющий ресурсами сигнализирует о том, что он закончил первую фазу, вызывая метод Когда все участвующие в транзакции управляющие ресурсами «проголосовали», то есть уведомили управляющий транзакциями о том, хотят ли они фиксировать или откатывать изменения, начинается вторая фаза. В это время управляющие ресурсами получают указание фиксировать свои изменения (если все участники проголосовали за фиксацию) или отказаться от изменений (если хотя бы один участник проголосовал за откат). Технически это выражается в вызове методов Управляющий ресурсами может вызвать метод Есть еще трехфазная фиксация и ее модификации со своими достоинствами и недостатками. Протокол трехфазной фиксации менее распространен, возможно, потому, что требует еще больше расходов на сообщения между взаимодействующими подсистемами. Шпаргалка по интерфейсам System.TransactionsЧто-то сложно получается. Чтобы немного разложить все по полочкам, я кратко опишу основные интерфейсы пространства имен IEnlistmentNotification. Управляющий ресурсами обязательно реализует этот интерфейс. Управляющий транзакциями вызывает реализованные методы в следующем порядке. Во время первой фазы он вызывает метод ISinglePhaseNotification. Управляющий ресурсами реализует этот интерфейс, если хочет предоставить управляющему транзакциями возможность оптимизировать выполнение за счет редуцирования второй фазы фиксации. Если управляющий транзакциями видит только один управляющий ресурсами, то на первой фазе фиксации он пытается вызвать у управляющего ресурсами метод ITransactionPromoter. Управляющий ресурсами реализует этот интерфейс (только не напрямую, а через интерфейс IPromotableSinglePhaseNotification. Управляющий ресурсами реализует этот интерфейс, чтобы, во-первых, реализовать интерфейс Еще немного посмотрим на класс Enlistment. Управляющий ресурсами может вызвать единственный метод этого типа — PreparingEnlistment. С помощью экземпляра этого типа во время первой фазы фиксации управляющий ресурсами может сигнализировать о своем намерении зафиксировать или откатить изменения. Долговечный управляющий ресурсами также может получить информацию, требующуюся для восстановления после системного сбоя. SinglePhaseEnlistment. С помощью экземпляра этого типа управляющий ресурсами может передать информацию управляющему транзакциями о результатах своей работы при использовании упрощенной схемы (однофазной фиксации). Ограничения программных распределенных транзакций и альтернативыКраткое исследование мнений, встречающихся в интернете, показывает, что во многих областях распределенные транзакции выходят из моды. Посмотрите, например, на этот ехидный комментарий. Основной объект критики, о котором вкратце сказано здесь, — это синхронная (блокирующая) природа распределенных транзакций. Если пользователь отправил запрос, во время обработки которого была организована распределенная транзакция, то он получит ответ только после того, как (успешно или с ошибкой) закончат работу все подсистемы, включенные в транзакцию. При этом есть подкрепленное исследованиями мнение, что протокол двухфазной фиксации показывает плохую производительность, особенно с ростом числа вовлеченных в транзакцию подсистем, о чем упоминается, например, в этой публикации на «Хабре». Если создатель системы предпочитает вернуть ответ пользователю как можно скорее, отложив согласование данных на потом, то ему больше подойдет какое-нибудь другое решение. В контексте теоремы Брюера (теоремы CAP) можно сказать, что распределенные транзакции подходят для случаев, когда согласованность данных (Consistency) важнее доступности (Availability). Существуют и другие практические ограничения на использование программных распределенных транзакций. Например, опытным путем было установлено, что распределенные транзакции по протоколу OleTx не должны пересекать сетевые домены. Во всяком случае, долгие попытки заставить их работать не увенчались успехом. Кроме того, было выявлено, что взаимодействие между несколькими экземплярами Oracle Database (распределенных транзакций СУБД) накладывает серьезные ограничения на применимость программных распределенных транзакций (опять же не удалось завести). Какие есть альтернативы распределенным транзакциям? Сначала надо сказать, что вообще без технических транзакций (обычных, не распределенных) будет обойтись очень сложно. В системе наверняка будут процессы, которые могут временно нарушить целостность данных, и потребуется каким-то образом обеспечить надзор за такими процессами. Точно так же и в терминах предметной области может возникнуть понятие, включающее процесс, реализующийся совокупностью процессов в разных технических системах, который должен начинаться и заканчиваться в области целостных данных. Переходя к альтернативам распределенных транзакций, можно отметить решения на основе служб сообщений, например RabbitMQ и Apache Kafka. В этой публикации на «Хабре» рассматривается четыре таких решения: 1) подсистема публикует сообщение, на основе которого происходит и запись в БД, и уведомление других подсистем; 2) подсистема пишет в БД, а уведомления других подсистем формируются на основе журнала транзакций БД (Transaction Log Tailing); 3) подсистема ведет в БД как таблицы сущностей, так и таблицы сообщений для внешних подсистем; 4) сущности в БД однозначно представляются очередью записей об их создании и изменениях (Event Sourcing). Еще одна альтернатива — шаблон «Сага». Он предполагает каскад подсистем со своими локальными транзакциями. По завершении работы каждая система вызывает следующую (либо самостоятельно, либо при помощи координатора). Для каждой транзакции имеется соответствующая отменяющая транзакция, и подсистема может вместо передачи управления дальше, наоборот, инициировать отмену изменений, сделанных ранее предыдущими подсистемами. На «Хабре» есть несколько хороших статей о шаблоне «Сага». К примеру, в этой публикации приводятся общие сведения о поддержании принципов ACID в микрослужбах, а в этой статье подробно рассматривается пример внедрения шаблона «Сага» с координатором. В нашей компании в некоторых продуктах успешно используются программные распределенные транзакции через WCF, но бывают и другие варианты. Однажды, когда мы пытались подружить новую систему с распределенными транзакциями, у нас возникло много проблем, включая столкновение с описанными выше ограничениями и параллельные неприятности с обновлением программной инфраструктуры. Поэтому в условиях дефицита ресурсов на обкатывание другого капитального решения мы применили следующую тактику. Вызываемая сторона фиксирует изменения в любом случае, но отмечает, что они находятся в состоянии черновика, поэтому эти изменения пока не влияют на работу вызываемой системы. Потом уже вызывающая сторона при завершении своей работы через распределенную транзакцию СУБД активизирует изменения, сделанные вызываемой системой. Таким образом, вместо программных распределенных транзакций мы воспользовались распределенными транзакциями СУБД, которые в данном случае оказались гораздо надежнее. Так это есть в .NET Core?В .NET Core (и даже в .NET Standard) есть все необходимые типы для организации транзакций и создания своего управляющего ресурсами. К сожалению, в .NET Core у транзакций на основе Дело в том, что .NET Core стараются делать независимым от операционной системы, поэтому завязка на такие управляющие транзакциями, как KTM и MSDTC, исключается, а именно они и нужны для поддержания транзакций с указанными свойствами. Возможно, будет реализовано подключение управляющих транзакциями в виде плагинов, но пока что это вилами по воде писано, поэтому рассчитывать на промышленное применение распределенных транзакций в .NET Core пока нельзя. На опыте можно убедиться в различиях распределенных транзакций в .NET Framework и в .NET Core, написав одинаковый код, скомпилировав и запустив его на разных платформах. Ниже приведен пример такого кода, вызывающего последовательно SQL Server и Oracle Database. Готовые для сборки проекты находятся на GitHub. private static void Main(string[] args) { using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) { MsSqlServer(); Oracle(); scope.Complete(); } } private static void Oracle() { using (var conn = new Oracle.ManagedDataAccess.Client.OracleConnection("User Id=some_user;Password=some_password;Data Source=some_db")) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } } private static void MsSqlServer() { var builder = new System.Data.SqlClient.SqlConnectionStringBuilder { DataSource = "some_computer\\some_db", UserID = "some_user", Password = "some_password", InitialCatalog = "some_scheme", Enlist = true, }; using (var conn = new System.Data.SqlClient.SqlConnection(builder.ConnectionString)) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1"; cmd.ExecuteNonQuery(); } conn.Close(); } } Запуск примера для .NET Core заканчивается ошибкой. От порядка вызова СУБД зависят место и тип выбрасываемого исключения, но в любом случае это исключение свидетельствует о недопустимой транзакционной операции. Запуск примера для .NET Framework заканчивается успешно, если в это время работает MSDTC; при этом в графическом интерфейсе MSDTC можно наблюдать регистрацию распределенной транзакции. Распределенные транзакции и WCFWindows Communication Foundation (WCF) — это программный каркас мира .NET, предназначенный для организации и вызова сетевых служб. По сравнению с более модными подходами REST и ASP.NET Web API он обладает своими достоинствами и недостатками. WCF очень хорошо дружит с транзакциями .NET, и в мире .NET Framework его удобно использовать для организации транзакций, распределенных между клиентом и службой. В .NET Core эта технология работает только со стороны клиента, то есть создать службу нельзя, а можно только обратиться к уже существующей. Это, однако, не слишком важно, потому что, как сказано выше, с распределенными транзакциями в .NET Core дела вообще обстоят не очень хорошо. Как устроен WCFДля читателей, мало знакомых с WCF, дадим здесь самую краткую справочную информацию о том, что же представляет собой эта технология на практике. Контекст — две информационные системы, называемые клиентом и службой. Клиент во время выполнения обращается к другой информационной системе, поддерживающей интересующую клиента службу, и требует выполнить некоторую операцию. Потом управление возвращается клиенту. Чтобы создать службу на WCF, обычно надо написать интерфейс, описывающий контракт создаваемой службы, и реализующий этот интерфейс класс. Класс и интерфейс размечаются особыми атрибутами WCF, выделяющими их среди остальных типов, и задают некоторые детали поведения во время обнаружения и вызова службы. Эти типы обертываются во что-то, работающее в виде сервера (например, в DLL, на которую натравливают IIS), и дополняются конфигурационным файлом (есть варианты), где указаны подробности реализации службы. После запуска к службе можно обращаться, например, по сетевому адресу; в интернет-обозревателе можно посмотреть контракты, которые реализует запрошенная служба. Программист, желающий обратиться к существующей службе WCF, использует консольную утилиту или встроенный в среду разработки графический интерфейс, чтобы по имеющемуся адресу службы сформировать типы на языке C# (или на другом поддерживаемом языке), соответствующие контрактам службы. Файл с полученными типами включается в проект клиентского приложения, и после этого программист пользуется теми же терминами, что содержатся в интерфейсе службы, наслаждаясь благами прогресса (статической типизацией). Кроме этого, в конфигурационном файле клиента указываются технические характеристики вызываемой службы (возможна настройка и в коде, без конфигурационного файла). WCF поддерживает разные виды транспорта, шифрования и иных более тонких технических параметров. Большинство из них объединяются понятием «привязка» (Binding). Есть три важных параметра службы WCF: 1) адрес, по которому она доступна; 2) привязка; 3) контракт (интерфейсы). Все эти параметры устанавливаются в конфигурационных файлах службы и клиента. В нашей компании WCF (и с распределенными транзакциями, и без них) широко используется во внедренных продуктах, однако, учитывая веяния моды, его использование в новых продуктах пока что находится под вопросом. Как завести распределенные транзакции в WCFЧтобы завести в WCF транзакции на основе Привязки, поддерживающие распределенные транзакции: NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding и WSFederationHttpBinding. Метод (операцию) интерфейса службы надо пометить атрибутом Ниже приведен код простейшей службы WCF, поддерживающей распределенные транзакции. Очевидно, что он отличается от кода обычной службы только использованием атрибута [System.ServiceModel.ServiceContract] public interface IMyService { [System.ServiceModel.OperationContract] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] int DoSomething(string input); } public class MyService : IMyService { [System.ServiceModel.OperationBehavior(TransactionScopeRequired = true)] [System.ServiceModel.TransactionFlow(System.ServiceModel.TransactionFlowOption.Mandatory)] public int DoSomething(string input) { if (input == null) throw new System.ArgumentNullException(nameof(input)); return input.Length; } } Конфигурация WCF для этой службы может быть, например, следующей. <system.serviceModel> <services> <service name="WcfWithTransactionsExample.MyService" behaviorConfiguration="serviceBehavior"> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="mainWsBinding" contract="WcfWithTransactionsExample.IMyService"/> <endpoint address="mex" contract="IMetadataExchange" binding="mexHttpBinding"/> </service> </services> <bindings> <wsHttpBinding> <binding name="mainWsBinding" maxReceivedMessageSize="209715200" maxBufferPoolSize="209715200" transactionFlow="true" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"> <security mode="None"/> <readerQuotas maxArrayLength="209715200" maxStringContentLength="209715200"/> </binding> </wsHttpBinding> </bindings> </system.serviceModel> Обратите внимание на то, что используется привязка типа WSHttpBinding и атрибут TL;DR по разделуРаспределенные транзакции включают несколько управляющих ресурсами, и все изменения должны либо фиксироваться, либо откатываться. Некоторые современные СУБД реализуют распределенные транзакции, которые представляют удобный механизм связи нескольких БД. Программные (реализуемые не в СУБД) распределенные транзакции могут включать разные комбинации управляющих ресурсами на разных компьютерах под управлением разных операционных систем, но они имеют ограничения, которые нужно учесть перед тем, как полагаться на них. Современной альтернативой распределенным транзакциям являются решения на основе службы сообщений. В .NET Core распределенные транзакции пока что не поддерживаются. WCF — это один из стандартных и проверенных инструментов создания и обращения к службам в мире .NET, поддерживающий несколько видов транспорта и шифрования. WCF очень близко дружит с распределенными транзакциями на основе Заключение-шпаргалкаЭта публикация представляет собой обзор основ программных транзакций .NET. Некоторые выводы относительно тенденций в программных транзакциях можно найти в разделах, посвященных применимости и ограничениям обсуждаемых предметов, а в заключении собраны основные тезисы публикации. Предполагаю, что их можно использовать в качестве шпаргалки при рассмотрении программных транзакций как одного из вариантов реализации технической системы или для освежения в памяти соответствующей информации. Транзакции (предметной области, СУБД, программные). Требования предметной области иногда формулируют в виде транзакций — операций, которые, начинаясь в области целостных данных, по завершении (в том числе и неудачном) должны приходить также в область целостных данных (возможно, уже другую). Эти требования обычно реализуются в виде системных транзакций. Классическим примером транзакции является перевод денег между двумя счетами, состоящий из двух неделимых операций — снятия денег с одного счета и зачисления на другой. Кроме известных всем транзакций, реализуемых средствами СУБД, существуют и программные транзакции, например в мире .NET. Управляющие ресурсами — это программные компоненты, знающие о существовании таких транзакций и имеющие возможность включаться в них, то есть фиксировать или откатывать сделанные изменения. Управляющие ресурсами получают инструкции о фиксации и откате изменений от управляющего транзакциями, составляющего основу инфраструктуры Долговечные и непостоянные управляющие ресурсами. Долговечные управляющие ресурсами поддерживают восстановление данных после системного сбоя. Драйверы СУБД для .NET как раз обычно предлагают такую функциональность. Непостоянные управляющие ресурсами не поддерживают восстановление после сбоя. Программную транзакционную память — способ управления объектами в оперативной памяти — можно рассматривать как пример непостоянного управляющего ресурсами. Транзакции и управляющие ресурсами .NET. Программист .NET пользуется программными транзакциями и создает собственные управляющие ресурсами с помощью типов из пространства имен Распределенные транзакции (СУБД, программные). Распределенная транзакция охватывает несколько подсистем, изменения в которых должны быть синхронизированы, то есть они либо фиксируются все вместе, либо откатываются. Распределенные транзакции реализованы в некоторых современных СУБД. Программные распределенные транзакции (это уже не те, которые реализуются СУБД) накладывают дополнительные ограничения на взаимодействующие процессы и платформы. Распределенные транзакции постепенно выходят из моды, уступая решениям на основе служб сообщений. Чтобы превратить обычную транзакцию в распределенную, программисту почти ничего не надо делать: при включении в транзакцию управляющего ресурсами с определенными характеристиками во время выполнения управляющий транзакциями автоматически сделает все, что нужно. Обычные программные транзакции доступны в .NET Core и .NET Standard, а распределенные — не доступны. Распределенные транзакции через WCF. WCF — это один из стандартных для .NET инструментариев создания и вызова служб, поддерживающий в том числе и стандартизированные протоколы. Другими словами, к определенным образом сконфигурированным службам WCF можно обращаться из любого приложения, не только .NET или Windows. Чтобы создать распределенную транзакцию поверх WCF, нужно разметить типы, составляющие службу, дополнительными атрибутами и внести минимальные изменения в конфигурационные файлы службы и клиента. В .NET Core и .NET Standard нельзя создавать службы WCF, но можно создавать клиенты WCF. Полезные ссылкиПример для проверки System.Transactions на GitHub Основные понятия ACID (русская «Википедия») Алгоритм двухфазной фиксации (документация Microsoft) Однофазная фиксация (документация Microsoft) Программная транзакционная память (русская «Википедия») Теорема Брюера (русская «Википедия») Трехфазная фиксация (английская «Википедия») Стандарты и конкретные реализации Стандарт распределенных транзакций X/Open XA (документ The Open Group) Java Transaction API (русская «Википедия») Описание транзакций в объектной БД Cache (сайт компании InterSystems) Материалы о .NET Подробное описание транзакций .NET (сборник личных журналов Tech Blog Collection) Подробное руководство по работе с TransactionScope («Хабр») Описание участника транзакции, поддерживающего продвижение и однофазную фиксацию (документация Microsoft) Что нового в .NET Standard 2.0 (репозиторий .NET Standard на GitHub) Проблемы с пулом соединений и уровнями изоляции транзакции (журнал YarFullStack) Материалы о микрослужбах Вводная статья по разработке транзакционных микрослужб («Хабр») Принципы и примеры разработки транзакционных микрослужб («Хабр») Обзорная статья по шаблону «Сага» и целостности данных в микрослужбах («Хабр») Подробная статья о применении шаблона «Сага» с координатором («Хабр») |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||