gifts2017

Глобальные транзакции в сервис-ориентированной архитектуре и... 1С

Опубликовал Андрей Межов (mini_root) в раздел Программирование - Теория программирования

В данной статье будет рассмотрен вопрос включения 1С в глобальные транзакции в рамках сервис-ориентированной архитектуры. Эта статья является продолжением статьи "Интеграция 1С с сервисной шиной OpenESB".

    В данной статье будет рассмотрен вопрос включения 1С в глобальные транзакции в рамках сервис-ориентированной архитектуры. Эта статья является продолжением статьи "Интеграция 1С с сервисной шиной OpenESB".

 

Введение

   Механизм транзакций является ключевым элементом, для построения надежных и отказоустойчивых информационных систем. Всем хорошо известна его полезность в реляционных СУБД - именно транзакции позволяют строить надежные системы, согласовано меняющие состояние и откатывающиеся, в случае сбоя, к предыдущему непротиворечивому состоянию. Ключевым свойствами транзакций, позволяющими все это осуществить, являются: атомарность, согласованность, изолированность и устойчивость(для их обозначения чаще используется аббревиатура ACID).

   Гораздо менее известен тот факт, что транзакции могут быть связаны с ресурсами отличными от традиционных баз данных. В общем случае транзакционным может быть назван любой ресурс, который поддерживает:

1. Явное или неявное начало транзакции, как группы согласованных действий.
2. Отмену всех действий в рамках транзакции и возвращение ресурса в исходное состояние.
3. Фиксацию всех действий, которые приводят ресурс к новому допустимому состоянию.

   Ну и, разумеется, в случае параллельного доступа к ресурсу должны поддерживаться сразу несколько транзакций, при этом для разделения доступа обычно используются механизм блокировок или версий.

   В качестве примера, системы обладающей транзакционностью, можно привести очереди сообщений. Причем в этом случае транзакции для отправителя и для получателя будут представлены по разному: "будет отправлено все или ничего" и "будет получено и удалено из очереди все или ничего".

    Все основные программные платформы (а к ним я отношу прежде всего Java и .NET) пришли к осознанию важности обобщенного механизма управления разнообразными транзакционными ресурсами, и, что самое интересное, к согласованному управлению несколькими транзакционными ресурсами в рамках одной транзакции. В Жабе 2 Йо-Йо есть JTS (Java Transaction Service), а в дотнете свой механизм, гнездящийся в пространстве System.Transactions.

    Немного терминологии:
    - транзакция с участием одного транзакционного ресурса и без двухфазного протокола подтверждения называется "локальной"
    - транзакция с несколькими транзакционными ресурсами и двухфазным протоколом подтверждения называется "глобальной".
    - "двухфазный протокол подтверждения" предусматривает подтверждение в две фазы: подготовку к подтверждению и собственно подтверждение, разумеется, если какой-то из ресурсов упал с ошибкой, и общего подтверждения транзакции не случилось, ни первая, ни вторая фаза не будет выполнена.

    Критичным местом для двухфазного протокола является стадия окончательного подтверждения: если на провод сядет птичка, то в Виллариба может оказаться одно, а в Виллабаджио совсем другое, поэтому-то и выделена в отдельную фазу подготовка. В фазе окончательного подтверждения не должно быть никаких тяжелых действий, она должна быть максимально короткой и с как можно более низкой вероятностью возникновения ошибок.

    В общем случае, механизм который рулит транзакцией называется "менеджером транзакции" (координатором), а рулежка транзакции сводится к рулежке транзакционными ресурсами, но не напрямую, а через некий адаптер, называемый "менеджером ресурса". Тут надо понимать, что терминология эта достаточна условна, и в другом месте вполне может использоваться термин "распределенные" вместо "глобальные" и пр.

   Наличие промежуточного слоя в виде менеджера ресурса позволяет включать в транзакцию самые разнообразные ресурсы, реализуя соответствующий менеджер. В .NET менеджер ресурса представляет собой по сути обработчик, который слушает состояние транзакции и при наступлении тех или иных событий предпринимает соответствующие действия в отношении подопечного ресурса.  Для примера, рассматриваемого в этой статье, создание такого менеджера ресурса для 1С будет играть ключевую роль - именно он позволит включать 1С в глобальные транзакции.

   В целом, схема такой глобальной транзакции будет иметь вид:

schem

   Природа же координатора нас волновать не будет.

 

Общие соображения

    Как ясно из введения, 1С поднятая через V81.Application прямо таки просится в качестве транзакционного ресурса. Тем более, что и делать-то ничего не надо - поддержка транзакций в 1С уже есть и все, что нужно сделать - это менеджер, который сможет управлять ими и при этом будет совместим с механизмом транзакций .NET'а.

   В качестве основы будет использован пример универсального адаптера OneCService из прошлой статьи, потребуется его только слегка переработать и дополнить. Поэтому детально OneCService здесь рассматриваться не будет.

    Доработки в основном коснутся:
1. Сервиса: нужно включить поддержку транзакций и включение транзакционного ресурса (1C через OneCAdapter) в транзакцию.
2. OnceAdapter'а: нужно добавить методы для управления транзакциями.
3. Создания менеджера, который будет управлять OneCAdpater'ом, и, как следствие, 1С'ом.

   При этом полная поддержка двухфазного протокола в менеджере ресурса реализована не будет - стадия подготовки будет оставлена пустой, так как сам ресурс (1С) не поддерживают двухфазное подтверждение. Также будет предпринят ряд шагов предотвращающий:

1. Потерю транзакции - дотнетовский менеджер транзакций там чего-то мудрит, так что периодически вылезала ошибка с неактивной транзакцией. Решение простое: не хранить OneCAdapter в менеджере ресурсов, а хранить его в параметрах домена .NET, использую в качестве ключа GUID, уникальный для экземпляра менеджера ресурсов.

2. Потерю ссылкой на V81.Application своей рантайм-обертки (RCW) - проявляется когда менеджер ресурсов получает команду завершить или откатить транзакцию, происходит это уже за рамками пользовательского кода, так что точная причина не понятна. Решение состоит в том, чтобы хранить также неуправляемый указатель на экземпляр V81.Application и в методах завершения/отмены транзакции восстанавливать по нему RCW.

  Также не будет реализовано повторное использование V81.Application - пул соединений. На каждый запрос буде подниматься свое соединение с 1С и включаться в транзакцию («любите ли Вы дедлоки так, как люблю их я?»).

 

Инструментарий

1. 1С 8.1 с файловой БД.
2. Sharpevelop 3.1  + .NET 3.5 SP1 + Windows SDK (там есть svcutil.exe, нужный для построения клиента к сервису).

 

Реализация

  Начнем с некоторых изменений, которые необходимо внести в сервис для включения поддержки транзакций:

[ServiceContract(Name="onecservice", Namespace="http://onecservice")]
public interface IOneCWebService
{
    [OperationContract(Name="ExecuteRequest")]
    [
TransactionFlow(TransactionFlowOption.Mandatory)]
   
ResultSet ExecuteRequest(string _file, string _usr, string _pwd, string _request);

    [
OperationContract(Name="ExecuteScript")]
    [
TransactionFlow(TransactionFlowOption.Mandatory)]
   
ResultSet ExecuteScript(string _file, string _usr, string _pwd, string _script);

    [
OperationContract(Name="ExecuteMethodWithXDTO")]
    [
TransactionFlow(TransactionFlowOption.Mandatory)]
   
ResultSet ExecuteMethodWithXDTO(string _file, string _usr, string _pwd, string _methodName, XmlNode[] _parameters);
}

 TransactionFlow.Mandatory гарантирует обязательное присутствие транзакции с клиентской стороны, иначе клиент получит отлуп, поэтому, кстати, данный пример не совместим с клиентами для изначального OneCService'а.

  Перейдем к менеджеру ресурса, который позволит включать 1С в транзакцию, как видно из кода он представляет собой обычной обработчик, реагирующий на изменение состояния транзакции(код приведен с сокращениями):

 

public class V8ResourceManager : IEnlistmentNotification, IDisposable
{
    private Guid        resourceGuid = Guid.NewGuid();
   
private AppDomain         domain = null;

   
public AppDomain Domain
    {
        set {.....}
        get {.....}
    }

    public OneCAdapter Adapter
    {
        set {......}
        get {......}
    }

    public Guid ResourceGuid
    {
        ......
   
}

    public void Prepare(PreparingEnlistment preparingEnlistment)
   
{
        ......
   
}

    public void Commit(Enlistment enlistment)
   
{
        try
       
{
            //Console.WriteLine("Commit GUID:" + resourceGuid);
           
Adapter.Commit();
           
enlistment.Done();
       
}
        catch (Exception _e)
       
{
            SimpleLogger.DefaultLogger.Severe("Error on commit: "+_e.ToString());
       
}
        finally
        {
            TryClose();
       
}
    }

    public void Rollback(Enlistment enlistment)
   
{
        .....
   
}

    public void InDoubt(Enlistment enlistment)
   
{
        .....
   
}

    .....

}

 

 Рассмотрим код, обеспечивающий включений 1С в транзакцию:

 

private void EnlistToTransaction(OneCAdapter _adapter)
{
    if (Transaction.Current != null)
   
{
        V8ResourceManager manager = new V8ResourceManager();
       
manager.Domain = AppDomain.CurrentDomain;
       
manager.Adapter = _adapter;

       
Transaction.Current.EnlistDurable(manager.ResourceGuid, manager, EnlistmentOptions.None);

       
_adapter.Begin();
   
}
    else
   
{
        Exception e = new Exception("Ambient transaction not found!");
       
SimpleLogger.DefaultLogger.Severe(e.ToString());
       
throw e;
   
}
}

   Все что теперь остается сделать - это добавить включение в транзакцию в каждый метод веб-сервиса и можно приступать к созданию клиента.

   Значительная часть в создании клиента приходится на автоматическую генерацию клиентского прокси для доступа к сервису, для чего потребуется svcutil.exe (есть в Windows SDK). bat-файл строящий прокси приведен в клиентском проекте, в архиве. Также будет сформирован конфигурационный файл, содержащий, в том числе, и настройки соединения с сервисом. Имея готовый прокси и конфигурацию к нему можно переходить к написанию клиента.

  Рассмотрим код клиента:

 

public static void Main(string[] args)
{
    using (TransactionScope t = new TransactionScope(TransactionScopeOption.Required))
   
{
        onecserviceClient client = new onecserviceClient();
        try
       
{
            //Первая база 1С через веб-сервис
           
ResultSet resultSet = client.ExecuteScript(
               
"C:\Work\OneCService\Base\First",
               
"", "",
               
"сотр = Справочники.Сотрудники.НайтиПоКоду(10);\n" +
               
"Если Не сотр.Пустая() Тогда сотр.ПолучитьОбъект().Удалить(); КонецЕсли;" +
               
"сотр = Справочники.Сотрудники.СоздатьЭлемент();\n" +
               
"сотр.Код = 10; " +
               
"сотр.Наименование = \""; " +
               
"сотр.Записать();"
                                                   
);
            if (
!resultSet.Error.Equals(""))
           
{
                Console.WriteLine("Error: "+resultSet.Error);
               
throw new Exception(resultSet.Error);
           
}

            //Вторая база 1С через веб-сервис
           
resultSet = client.ExecuteScript(
               
"C:\Work\OneCService\Base\Second",
               
"", "",
               
"сотр = Справочники.Сотрудники.НайтиПоКоду(10);\n" +
               
"Если Не сотр.Пустая() Тогда сотр.ПолучитьОбъект().Удалить(); КонецЕсли;" +
               
"сотр = Справочники.Сотрудники.СоздатьЭлемент();\n" +
               
"сотр.Код = 10; " +
               
"сотр.Наименование = \""; " +
               
"сотр.Записать();"
                                           
);
            if (
!resultSet.Error.Equals(""))
           
{
                Console.WriteLine("Error: "+resultSet.Error);
               
throw new Exception(resultSet.Error);
           
}

            //Завершение транзакции
           
t.Complete();
       
//try
       
finally
        {
            client.Close();
           
Console.ReadKey();
       
}
    //using TransactionScope
}

 

    Как видно из кода, клиент, рассмотренный в этом примере будет работать с двумя базами 1С, в каждой из которых есть справочник "Сотрудники". В ходе работы клиента в этот справочник будет добавляться Сиськин с кодом 10. Весь процесс будет происходить в глобальной транзакции, то есть, если при добавлении во вторую базу произойдет ошибка, то Сиськин не должен будет появится и в первой базе, несмотря на то, что скрипт уже выполнен.  Хочется также отметить, что выполнения каждого скрипта будет осуществляться через отдельный вызов сервиса (через HTTP) и, в общем случае, эти сервисы могут находится на разных узлах сети и обеспечивать взаимодействие с разными базами 1С.

   Таким образом пример соотвествует схеме:

transaction

Как обычно, видео с демонстрацией лежит здесь (не забываем переключаться в HD)

Архив с исходниками, бинарниками и базами прилагается.

Недостатки реализации

1. Отсутствие пула поднятых V81.Application (в разрезе баз и пользователей, с обязательной проверкой пароля).
2. Очень просто организовать блокировку, а если постараться, то и дедлок. Причем в отличии это предыдущей версии это все может быть растянуто по времени и перемешено с другим вызовами (как других баз 1С, так и обычных СУБД или удаленных сервисов).

Лучшее средство от головы... или неполиткорректные мысли о глобальных транзакциях

   Распределенные транзакции - это конечно замечательно, но у всего есть свои недостатки и своя обратная сторона. В случае с транзакциями главная опасность кроется глубоко внизу - в механизме разделения доступа к ресурсу. По большому счету таких механизмов два: блокировка и версионность.

   С блокировкой все более или менее понятно: при параллельном доступе к ресурсу первый блокирует его, а все последующие ждут. Минусы состоят именно в том, что остальные ждут (любителям клюшек должны быть знакомы тормоза из-за блокировки таблицы журнала документов). И переключение на READ_UNCOMMITED ничего хорошего тоже не даст.

   Версионный механизм решает эту проблему: любой, кто изменяет ресурс работает со своей версией ("пишущие не блокируют читающих"). Но и тут есть свои минусы - вилка, которая возникает когда кто-то начал модификацию раньше чем сосед, а завершил позже. Тут в качестве примера можно привести СУБД Firebird.

  В случае же с глобальной транзакцией все эти типы разделения доступа и, соответсвенно, все проблемы могут сойтись в одном месте.

  Безусловно с такими негативными эффектами можно и нужно бороться, например, устанавливая таймаут на время жизни транзакции, использую промежуточные БД, синхронизируемые с основными, перестраивая схему взаимодействия и другими способами. Но тем не менее, серебряной пули нет и у любого преимущества всегда есть обратная сторона, глобальные транзакции здесь не исключение. Хотя безусловно этот механизм крайне интересен и позволяет строить весьма сложные системы.

 

Литература

1. Джувел Лёве «Создание служб WCF», O'REILLY/Питер.

 

P.S. Этот пример, как раз и иллюстрирует почему я так скептически отношусь ко встроенному движку веб-сервисов, который есть в 1С - такая задача ему не по зубам, равно как множество других вещей, например, поддержка стандарта WS-Security и пр. Причем я и не могу сказать, что эти вещи не нужны - движки, реализующие широкий набор стандартов, уже есть и они массово распространяются (WCF у каждого обладателя Висты и 7, Metro в каждом сервере приложений GlassFish). В конечном счете веб-сервисы становятся преобладающим средством взаимодействия и тут уже маркетинговой галочкой "поддержка веб-сервисов в 1С:Предприятие 8.1" не обойтись. Как обычно жду полезной критики и обсуждения.

 

 

Скачать файлы

Наименование Файл Версия Размер
1c-transaction.zip 32
.zip 454,21Kb
02.07.13
32
.zip 454,21Kb Бесплатно

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Андрей Межов (mini_root) 02.11.09 00:23
Так, ну вроде ссылки и архив проверил, остальные ляпы буду исправлять по мере всплытия.
2. Dladimir Girin (Enot5467) 02.11.09 05:12
А нормальными методами такое не решаемо? Интересно, и кому такой продукт нужен, если 1Ц стоит не под виндой?
Хотелось бы услышать пример, где это крайне необходимо. Если честно, то я еще не сталкивался с такими, а предостеречься очень хочется
3. dushelov (Душелов) 02.11.09 08:12
(2) Используйте MONO .NET с описанными выше функциями.
4. dushelov (Душелов) 02.11.09 08:13
А вообще, идея - занятная, надо будет выделить время и заняться вплотную этим вопросом.
5. Андрей Межов (mini_root) 02.11.09 09:19
(2) Нормальными НЕТ, потому что за три года движок веб-сервисов в 1С так и остался маркетинговой фишкой. Штатными средствами это будет возможно, когда я смогу поднять 1С'овский веб-сервис с поддержкой сессии (или транзакций) и получить все это искаропки, и тупо дергать его, тогда необходимость в таких поделках отпадет. Это нужно если у тебя стоит задача организации согласованного взаимодействия сразу множества распределенных система (НЕ только 1С). Если стоит только задача удовлетворять бушек, то это действительно совершенно лишнее.
(3) В моно нету WCF, кроме того непонятно, что использовать как аналог V81.Application. Ну вот оставили 1С'овцы единственный удобный способ поднять контекст одинэса программными средствами. Только про 8.2 с его сишным API не надо - ты же сам ковырял 8.2, прекрасно знаешь, что граблей там до одури.
6. Dladimir Girin (Enot5467) 02.11.09 11:03
Даже и не знаю, сделал ПХП-шник сайт, связали с 1Ц, все работает. Целостность данных реализована в полной мере. Не важно кто где что забивает. И не важно кто на чем пишет. За то важно понимать некоторые простейшие моменты по связи чего-то с чем-то. А именно: для объемов больше, чем минимальные не поднимать 1С прямым соединением ни при каких обстоятельствах. Но эта концепция не входит в рамки данного текста
7. Андрей Межов (mini_root) 02.11.09 11:49
(6) Обмен с сайтиком как организован?
8. Вячеслав Кадацкий (marsohod) 05.11.09 07:31
А в 8.2 механизм веб-сервисов (тонкий клиент) такой же или получше?
9. Андрей Межов (mini_root) 05.11.09 09:18
(8)Еще не смотрел, да и если честно, пока еще рано, там и без него граблей выше крыши:

http://infostart.ru/forum/forum15/topic27420/messages305854/#message305854

Есть мысль, что они его вообще не трогали как не приоритетный. Но это пока мои домыслы - будет время, постараюсь посмотреть.
10. Артем Титеев (a_titeev) 08.11.09 15:06
а чё V8.Application? можно и COM заюзать... вот тебе и пул соединений... только объекты и методы не все доступны...
если подымать соединения каждый раз, то далеко уйдет... с УПП попробуй при нормальной нагрузке...

намек на несостоятельность движка ws не понятен... всё от реализации зависит... да и поддержка сессии не всегда нужна, а скорее даже чаще всего не нужна... во всяком случае там, с чем пока салкивался...

про WS-Security как то задавал вопрос 1с... ответ был таким, что basic-аутентификация + HTTPS комбинация по нашему меннию непобедимаю, ничего делать больше не будем никогда... видимо то же и 8.2 относится...
11. Андрей Межов (mini_root) 08.11.09 21:03
(10) До пула соединений руки не дошли, интересовал сам принцип.

>намек на несостоятельность движка ws не понятен... всё от реализации зависит...

ты сам себе ответил

>про WS-Security как то задавал вопрос 1с... ответ был таким, что basic-аутентификация + HTTPS комбинация по нашему меннию непобедимаю, ничего делать больше не будем никогда... видимо то же и 8.2 относится...

и это между прочил базовый стандарт, которые уже реализуют все направо и налево, в отличие от тех транзакций

Без сессий полностью можно обойтись когда как раз есть транзакции, потому что иначе в полный рост встает вопрос отката операций при сбое (когда процесс зависит сразу от нескольких вызовов сервисов).

P.S. А по поводу "не сталкивался " - возьми тот же пример, что и в статье, и попробуй реализовать транзакционность используя только штатный 1с'овский движок: процесс должен выполнятся именно с двумя удаленными вызовами, а не из одной 1С дергать другую - для чистоты эксперимента можешь использовать в качестве координатора процесса внешнее приложение-клиента. Как сумеешь откатить первого Сиськина, при ошибке со вторым - отпиши, будет интересно почитать (и желательно без сессий и без так называемых "компенсационных транзакций") :)
12. Артем Титеев (a_titeev) 09.11.09 16:34
Как сумеешь откатить первого Сиськина, при ошибке со вторым
нада мозг включать... :o будет время попробую...
13. Артем Титеев (a_titeev) 09.11.09 17:09
и желательно без .... "компенсационных транзакций")

а собственно почему???

да, ктати... вот "Coordinating Web Services Activities with WS-Coordination, WS-AtomicTransaction, and WS-BusinessActivity" - http://msdn.microsoft.com/en-us/library/ms996526.aspx... думаю натолкнет меня на мысли... :idea:
14. Андрей Межов (mini_root) 09.11.09 17:31
(13) Потому, что для более или менее сложного случае никто механизм компенсации с гарантией не разработает, а выгребать последствия обвалившегося процесса + последствия некорректно отработанной компенсации, то еще удовольствие.

Может и натолкнет, только потом натолкнешься на то, что штатные движок ничего из этих WS'ов не поддерживает, а реализованы они как раз в .NET WCF, который я и использовал.

15. Артем Титеев (a_titeev) 09.11.09 20:02
дык почему только штатный 1С-движок?
например, все таки исходим из того, что необходимо избавится от блокировок... такой ясно что процесс должен быть асинхронный... в асинхронном процессе принципы ACID не могут поддерживаться... приходим к тому что компенсационные транзакции надо использовать... сам механизм не очень сложен... хотя есть всякие моменты, которые необходимо предусматривать... но все же, вроде как можно сделать и на основе того материала, который есть... но кажись велосипед опять изобретается... во первых, спецификация WS-BusinessActivity есть (http://specs.xmlsoap.org/ws/2004/10/wsba/wsba.pdf), реализации я не нашел... во вторых, ввиду асинхронности лучше чем SOAP/JMS не будет... но кажется ты уже писал статейку про этот механизм... теоретически транзакционность реализацией ActiveMQ должна поддерживаться, на практике - понятия не имею... надо пробовать...
16. Андрей Межов (mini_root) 09.11.09 20:35
(15) Она и поддерживается вот только смысла в SOAP тогда особого нет - вполне хватает сообщений в XML произвольного формата (которые потом можно преобразовывать и мапить во что угодно), а различных транспортов там навалом + фреймоворк аля Camel с реализацией EIP для построения маршрутизации и пр.
Более того, для большого потока шина как раз предпочтительней, да и ACID там есть, только своеобразный. Но тем не менее, ничто не мешает коннекту к шине самому обладать транзакцией и быть частью глобальной.
17. Артем Титеев (a_titeev) 10.11.09 11:07
смысл я думаю есть... промышленный стандарт... где то мне попадался проЭктик, где товарисчи вязали таким образом SAP/R3 с ORACLE... а там стандарты сам понимаешь...
18. Андрей Межов (mini_root) 10.11.09 13:40
(17) Чем скоро и предстоит заниматься... немецкая быдлоподелка, мать ее... :evil:
19. Артем Титеев (a_titeev) 12.11.09 10:19
ну удачи... опытом потом поделись :!: