Из цикла лекций «Internet-технологии разработки приложений» для студентов 4-го курса кафедры Компьютерных технологий физического факультета Донецкого национального университета проф. В. К. Толстых WCF-службы: Транзакции проф. В.К.Толстых, www.tolstykh.com Оглавление Понятие транзакции Координация транзакций Разрешение транзакций в WCF (transactionFlow, TransactionTimeout) Распространение транзакций в WCF (TransactionFlowOption) Внешняя транзакция и поведение операции WCF (TransactionScopeRequired) Распространение транзакций в зависимости от контракта, привязки и поведения операции Основные транзакции WCF Голосование и завершение (TransactionAutoComplete, SetTransactionComplete) Включение клиентов в транзакции (TransactionScope) Настройка транзакций для клиентов (TransactionScopeOption) Транзакции для служб уровня вызова Понятие транзакции Транзакцией называется набор операций, в которых сбой одной операции приводит к сбою всего набора как одной атомарной (неделимой) операции. Во время выполнения транзакции система временно находится в нестабильном состоянии, но после завершения транзакции она находится либо в новом стабильном состоянии B, либо в исходном А, в котором она находилась до начала транзакции. Стабильное состояние А Закреплённая транзакция Отменённая транзакция Временно нестабильное состояние Стабильное состояние B Ни А ни B не видят это состояние Транзакция переводит систему из одного стабильного состоянии в другое стабильное. Ресурсы транзакций – это базы данных, файлы, очереди сообщений. Некоторые ресурсы поддерживают автоматическое включение в транзакции при обнаружении обращения к ним из транзакций. Например, MS SQL Server автоматически включается во внешнюю транзакцию и не требует явного инициирования собственной транзакции. Не используйте в WCF транзакции ADO.NET напрямую. Координация транзакций Транзакция Транзакция Клиент Клиент Служба Служба Ресурс Транзакция без проблем координации: одна служба – один ресурс Служба Служба Ресурс Ресурс Ресурс Сервис-ориентированное приложение с распределёнными транзакциями Распределённая транзакция состоит из нескольких служб или одной службы с несколькими транзакционными ресурсами. В этом случае необходимо использовать менеджер транзакций (стандартные услуги третьей стороны). Во время транзакций все службы должны проголосовать хотят ли они закрепить транзакцию. Менеджер проверяет результаты голосования, если какая-либо служба или клиент проголосует за отмену, то все задействованные ресурсы получат приказ отменить внесённые изменения в ходе транзакции. Разрешение транзакций в WCF WCF может распространять транзакции за границы службы. Транзакции могут поддерживать привязки TCP, IPC, WS. По умолчанию транзакции не распространяются. Разрешение на распространение транзакций должно включаться как на стороне службы, так и на стороне клиента. Например, в файлах конфигурации: <bindings> <netTcpBindings> <binding name ="..." transactionFlow = "true" /> <reliableSession enabled="true" /> Включение надёжности снижает вероятность отмены транзакции. </netTcpBindings> Рекомендуется всегда включать при транзакциях </bindings> Всегда контролируйте transactionFlow у клиента после обновления ссылки на службу. По умолчанию время выполнения транзакции – 1 минута. Можно увеличить до 10 минут: [ServiceBehavior(TransactionTimeout = "00:10:00")] или <binding name ="..." transactionTimeout = "00:10:00" /> Для большего увеличения необходимо изменить время в файле machine.config. Когда транзакция распространяется к службе, настроенной на более короткий тайм-аут, транзакция принимает величину тайм-аута службы. Иначе тайм-аут службы игнорируется. Распространение транзакций в WCF Разрешение распространения транзакций не означает, что служба должна использовать клиентские транзакции в каждой операции и распространять их за свои пределы. Контракты транзакционных служб и операций должны содержать атрибут, указывающий когда, как и на какие операции распространяются клиентские транзакции. Например: [ServiceContract] Interface IMyContract Операция будет использовать клиентскую транзакцию, если она поступит, что возможно если { служба и клиент имеют transactionFlow = "true" [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void MyMethod(...); } Значения атрибута TransactionFlowOption прохождения транзакции: Allowed – служба примет клиентскую транзакцию если она будет и разрешит ей выйти за границы службы. Mandatory – служба требует обязательной транзакции от клиента. NotAllowed – служба запрещает прохождение транзакции от клиента. Принято по умолчанию. При завершении транзакции WCF, по умолчанию, уничтожает экземпляр службы. Это гарантирует, что все остаточные данные, которые могут поставить под угрозу стабильность, будут удалены из памяти. Рекомендуется настраивать операции, как минимум, на разрешение транзакций. Получение внешней транзакции и поведение операции WCF Транзакция в которой выполняется ваш код называется внешней. По умолчанию служба и её операции не принимают внешние транзакции. Это происходит даже при распространении транзакции клиента на службу. Для получения внешней транзакции (не создание новой) служба должна указать для каждого контрактного метода, что WCF необходимо выделить в теле метода область для транзакции с помощью свойства TransactionScopeRequired. Например: class MyService: IMyContract { [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod() { Transaction transaction = Transaction.Current; ... Идентификатор } транзакции. Если её нет, то } Current получает null При распространении на службу клиентской транзакции WCF установит её в качестве внешней транзакции операции. В противном случае WCF создаст новую транзакцию для этой операции и установит ее в качестве внешней транзакции. Распространение транзакций в зависимости от контракта, привязки и поведения операции Для службы 4 привязка и контракт запрещают распространение транзакции и поведение операции не требует транзакцию, поэтому WCF не создаёт новую транзакцию Служба 1 создаёт транзакцию А из-за её поведения. Служба 1 становится корнем транзакции Служба 2 работает во внешней транзакции А от службы 1 Для службы 3 привязка и контракт запрещают распространение транзакции, но поведение операции требует транзакцию, поэтому WCF создаёт новую транзакцию В. Служба 3 становится корнем новой транзакции Основные транзакции WCF Транзакция «клиент-служба». Служба использует транзакцию клиента, если она есть, или создаёт свою, если у клиента нет транзакции. 1. Выберите транзакционную привязку и задайте в её конфигурации transactionFlow = "true", 2. Включите распространение транзакций в контракте операции – TransactionFlowOption.Allowed, 3. Задайте поведение операции TransactionScopeRequired = true Транзакция «клиент». Служба использует только транзакцию клиента. 1. Выберите транзакционную привязку и задайте в её конфигурации transactionFlow = "true", 2. Включите распространение транзакций в контракте операции – TransactionFlowOption.Mandatory, 3. Задайте поведение операции TransactionScopeRequired = true Транзакция «служба». Служба всегда создаёт свою транзакцию отдельную от клиента. 1. Выберите любую привязку и оставьте значение по умолчанию или явно задайте transactionFlow = "false", 2. Не применяйте атрибут TransactionFlowOption или задайте TransactionFlowOption.NotAllowed, 3. Задайте поведение операции TransactionScopeRequired = true Голосование и завершение Каждая служба, участвующая в транзакции, должна проголосовать за её исход и высказать своё мнение по поводу закрепления или отмены транзакции. По умолчанию включается автоматический режим голосования за закрепление или отмену транзакции. Можно явно указать данный режим в атрибуте поведения операции: [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] public void MyMethod() { ... } Корневая служба транзакции может голосовать и выполнять завершение транзакции. Все остальные участники транзакции могут только голосовать. Служба голосует за отмену транзакции при наличии необработанного исключения. Если пользователь перехватит исключение для обработки, то для отмены транзакции необходимо генерировать исключение заново. Отказ от обработки такого исключения повышает производительность всей системы, поскольку во время обработки основного исключения и передаче нового исключения к корню транзакции все остальные участники транзакции работают «напрасно». Явное голосование (при TransactionAutoComplete = false) может применяться для завершения транзакций без наличия исключений. Для этого, после выполнения пользовательского транзакционного кода необходимо вызвать метод: OperationContext.Current.SetTransactionComplete(); Включение клиентов в транзакции Создание корневых транзакций клиентами (и не транзакционными службами) организуется при помощи класса System.Transactions.TransactionScope* в виде: Выполнить блок с посредником proxy и освободить связанные с ним ресурсы (не используйте using(), если планируете обрабатывать ошибки FaultException) using(MyContractClient proxy = new MyContractClient()) { //Попытка проведения транзакции со службой WCF try Инициировать, выполнить транзакцию и освободить связанные с нею ресурсы { using(TransactionScope scope = new TransactionScope()) { /* Транзакционные действия... proxy ... */ scope.Complete(); //исключений нет - транзакция закрепляется } Голосование за закрепление транзакции, иначе (по умолчанию) – будет отмена } cache(TransactionAbortedException e) { /* Сообщение пользователю e.Message о неудачной транзакции */ } } Перехватив исключение в транзакционной операции, всегда его перезапускайте. * Для доступа к классу Transactions добавьте в ветку <system.web><assemblies> корневого файла web.config <add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /> Настройка транзакций для клиентов Клиенты и службы, входящие во внешние транзакции, могут явно инициировать новые транзакции и становиться их корнем при помощи конструктора TransactionScope(): • using(TransactionScope scope = new TransactionScope( TransactionScopeOption.Required)) {...} - если есть внешняя транзакция, то клиент/служба присоединится к ней, иначе будет создана новая транзакция. Принято по умолчанию, т.е. параметр у конструктора может отсутствовать. • TransactionScopeOption.RequiresNew - создать новую транзакцию, не зависящую от внешней транзакции, включая закрепление или отмену внешней транзакции. • TransactionScopeOption.Suppress - независимо от внешней транзакции клиент/служба никогда не будет частью транзакции. Каждая новая транзакция должна закрепляться голосованием - scope.Complete(): [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod() { using(TransactionScope scope = new TransactionScope( TransactionScopeOption.RequiredNew)) { Голосование за закрепление данной транзакции даже если она ... входила во внешнюю (при TransactionScopeOption.Required) scope.Complete(); } Транзакции для служб уровня вызова Здесь транзакционное программирование почти не требует дополнительных усилий со стороны программиста. Для каждого вызова метода службы создаётся новый экземпляр службы, и этот вызов может принадлежать или не принадлежать той же транзакции, что и предыдущий вызов. Один посредник может участвовать в нескольких разных или одинаковых транзакциях: MyContractClient proxy = new MyContractClient using(TransactionScope scope = new TransactionScope()) { Транзакция А proxy.MyMethod(); Последовательный вызов метода в одной транзакции proxy.MyMethod(); scope.Complete(); } using(TransactionScope scope = new TransactionScope()) { Транзакция В proxy.MyMethod(); Вызов метода в новой транзакции scope.Complete(); } proxy.Close(); Один посредник для всех транзакций Транзакции для служб уровня сеанса По умолчанию WCF превращает любую транзакционную службу в службу уровня вызова, т. е. после выполнения транзакции в любом методе экземпляр службы уничтожатся (возможно даже до завершения транзакции). Для сеансового поведения транзакционной службы необходимо задать свойство ReleaseServiceInstanceOnTransactionComplete = false, например: [ServiceContract(SessionMode = SessionMode.Required)] Interface IMyContract { [OperationContract] [TransactionFlow()] void MyMethod(); } [ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete=false)] class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true)] [TransactionFlow()] public void MyMethod() {...} } Службу можно вызвать предыдущим клиентом, в котором теперь все транзакционные вызовы будут поступать к одному экземпляру службы Использование транзакционных сеансов обычно свидетельствует о некачественном проектировании, так как, оно создаёт проблемы с производительностью и масштабируемостью. Транзакционные синглетные службы По умолчанию транзакционные синглеты ведут себя как службы уровня вызова. После автоматического завершения транзакции WCF уничтожает синглетный экземпляр. Поэтому разработчик синглета должен обеспечивать контроль состояния и управлять состянием при каждом вызове метода. Помните, в любой момент времени существует не более одного экземпляра службы. Пример ID вне допустимого диапазона Исключение, полученное от о сбоя в методе GetNameById() Исключение, полученное от транзакции с методом SetNameById() Код клиента Вызов метода Auth(pwd): …продолжение клиента Вызов метода SetNameById(n, name) с транзакцией: …продолжение клиента Вызов метода GetNameById(n): Служба WcfTransactionAuthSession Метод Auth(int pwd): … продолжение службы Метод GetNameById(int value): … продолжение службы Метод SetNameById(int value): Контракты службы Файл конфигурации Источники • Джувел Лёве. Создание служб Windows Communication Foundation. – СПб.: Питер, 2008 . – 592 с.: ил. • http://msdn.microsoft.com