Логин:   Пароль:




Новости
Рассылки
Форум
Поиск


Java
- Апплеты
- Вопрос-ответ
- Классы
- Примеры
- Руководства
- Статьи
- IDE
- Словарь терминов
- Скачать

Мобильная Java
- Игры
- Примеры
- Статьи
- WAP, WML и пр.

JavaScript
- Вопрос-ответ
- Примеры
- Статьи

Веб-мастеринг
- HTML
- CSS
- SSI

Разминка для ума
Проекты
Книги
Ссылки
Программы
Юмор :)




Rambler's Top100

Java: СтатьиНекоторые способы реализации механизма распределенной транзакции

Некоторые способы реализации механизма распределенной транзакции

На сегодняшний день, нет универсального архитектурного решения в области корпоративных информационных систем [1], которое бы позволило удовлетворить множество, порой противоречивых требований. Обычно то или иное решение, как правило, принимается на основании множества факторов, в частности, специфики деятельности предприятия, опыта эксплуатации предыдущих систем, характеристик коммуникационных линий между подразделениями и пр. Следует отметить, что во всех без исключения случаях, во главу угла ставится надежность хранения данных, которая решается, как аппаратными, так и программными средствами. Также одним из важных вопросов, является проблема синхронизации данных. Которая может быть решена средствами СУБД, например различными типами репликации. Однако при таком подходе увеличивается нагрузка на СУБД, что влечет за собой снижение производительности всей системы.

Целью данной статьи, является описание способов синхронизации данных с помощью механизма двух фазной транзакции реализованных в корпоративной системе ОАО "РОСТОВЭНЕРГО", что повышает производительность всей системы в целом и позволяет пользователям взаимодействовать с системой в режиме реального времени. Новизна данной работы заключается, в том, что сам механизм двух фазной транзакции реализован различными средствами в различных архитектурных решениях:
на стороне клиента Swing приложения запускаемого по Web –start с помощью библиотеки Hibernate;
на сервере приложений с помощью менеджера распределенных транзакций JOTM;
в виде отдельной службы с планировщиком задач, для синхронизации данных с помощью Hibenate, JOTM и реестра rmiregistry;

Распределенная транзакция включает в себя несколько локальных транзакций, каждая из которых либо фиксируется, либо прерывается. Распределенная транзакция фиксируется только в том случае, когда зафиксированы все локальные транзакции, ее составляющие. Если хотя бы одна из них была прервана, то должна быть прервана и распределенная транзакция. Данный механизм может быть реализован как на распределенной БД, так и на множестве СУБД, имеющих часть общей информации необходимой для функционирования единой корпоративной системы [2].

Для этого в СУБД предусмотрен так называемый протокол двухфазовой фиксации транзакций (two-phase commit protocol - 2PC). Название отражает то, что фиксация распределенной транзакции выполняется в две фазы. Управляет транзакцией - менеджер распределенных транзакций, например JBoss, JOTM, WebSphere 6. Они, как правило, интегрируются в сервер приложений, но могут также работать в режиме standalone.

Первая фаза начинается, когда клиент выполняет оператор COMMIT. Менеджер транзакции направляет уведомление PREPARE TO COMMIT - "подготовиться к фиксации" всем серверам БД, выполняющим транзакцию. Последние после подготовки к фиксации остаются в состоянии готовности и ожидают от менеджера команды фиксации.

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

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

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

Аналогичная проблема, была решена нами в рамках задачи централизации корпоративной системы ОАО "РОСТОВЭНЕРГО". В единое корпоративное информационное пространство входит 7 серверов СУБД с MS SQL Server расположенных на филиалах по Ростовской области и один в аппарате управления. В общем случае задачу централизации была разбита на три подзадачи:

  1. Ведение единого реестра, какого либо объекта, входящего в корпоративную информационную систему;
  2. Синхронизация единого реестра со стороны центрального сервера;
  3. Обеспечение доступа, в режиме реального времени, к распределенным сущностям.

Рассмотрим первую подзадачу на примере решения проблемы реализации механизма распределенной транзакции при ведении единого реестра договоров ОАО "Ростовэнерго". Договора могут заключаться на всех филиалах по Ростовской области. Можно выделить три типа договоров:

  1. Договор, заключенный филиалом или аппаратом управления в своих интересах, если он заключен филиалом, то он должен быть сохранен на сервере аппарата управления и на сервере филиала, если аппаратом управления в своих интересах – то только на сервере аппарата управления.
  2. Договор заключенный аппаратом управления в интересах филиала. Его необходимо сохранять на сервере филиала, в интересах которого он был заключен и на сервере аппарата управления.
  3. Централизованный договор, заключенный аппаратом управления в интересах всех филиалов необходимо сохранять на сервере аппарата управления и на серверах всех филиалов.

Кроме того, любые изменения в договорах, должны быть реализованы по описанному выше взаимодействию см. рис.1.
Рисунок 1. Схема взаимодействия

  1. юристов филиалов при добавлении и редактировании данных;
  2. юристов филиалов при просмотре данных;
  3. юристы управления при добавлении, редактировании и просмотре данных

При внесении нового договора, он в начале должен быть сохранен на сервер аппара-та управления, в результате договору присваивается идентификатор, после чего, он при необходимости сохраняется на сервере филиала. Аналогичным образом должно выпол-няться и редактирование.

На первом этапе был реализован механизм распределенной транзакции средствами Hibernate. При авторизации пользователя происходит его идентификация на LDAP - сер-вере, после чего клиентскому приложению передается информация о филиале, к которому принадлежит пользователь, и в зависимости от этого на стороне клиента создается либо две Hibernate – фабрики сессий, если он не принадлежит к аппарату управления либо одна в противном случае. Далее при сохранении объекта, см листинг 1, выполняется последо-вательное сохранение, вначале на центральном сервере, а потом на сервере филиала. При этом если COMMIT на центральном сервере прошел успешно, выполняется фиксация транзакции на сервере филиала. В случае неудачной фиксации на сервере филиала проис-ходит последовательный "откат" транзакции, см листинг 2. Несмотря на очевидное несо-вершенство данного механизма реализации двух фазной транзакции, который нами рас-сматривался как временный, переходный вариант, промышленная эксплуатация системы в течение четырех месяцев с его использованием показала довольно неплохие, на наш взгляд результаты. При вводе тысячей объектов с удаленных серверов имели место еди-ничные случаи, когда объект сохранялся на центральном сервере, а на сервере филиала отсутствовал. Анализ данных случаев показал, что 90% из них произошло из-за загружен-ности каналов связи между аппаратом управления и филиалом. Оставшиеся 10% связаны с загруженностью локального сервера СУБД.

Листинг 1. Фрагмент метода сохранения объекта при реализации двух фазных тран-закций средствами Hibernate



/**
         * Метод сохранения объекта в БД
         * @param obj объект который нужно сохранить
         */
  public void saveObjDBDT(Object obj){
  
	 if(!cf.getPathSQLServer().equals("Gerpes")){            
               HbnSessionUtil.beginTransactionSrv();
	    	this.getSessionSrv().save(obj);
	    	this.getSessionSrv().flush();
               HbnSessionUtil.commitTransactionSrv();
	    	this.getSessionSrv().clear();
		    }         
       HbnSessionUtil.beginTransactionLoc();
	    this.getSessionLoc().save(obj);
	    this.getSessionLoc().flush();
       HbnSessionUtil.commitTransactionLoc();
  }

Листинг 2. Фрагменты методов принятия транзакций на локальном и центральном сер-верах.


public static void commitTransactionLoc()
	{
		Session	ses	= getCurrentSessionLoc();
		Transaction	trn	= ses.getTransaction();
                if(!rollbackRegLoc.get()){
                try {
                     if(trn != null && !trn.wasCommitted() && !trn.wasRolledBack())
                        trn.commit();
                    } catch(HibernateException ex) {
                        if (registrySrv.get()!=null) rollbackTransactionSrv();
                        rollbackTransactionLoc();
                        throw ex;
                    }
                }
	}
        public static void commitTransactionSrv()
	{
		Session	ses	= getCurrentSessionSrv();
		Transaction	trn	= ses.getTransaction();
                 try {
                 if(trn != null && !trn.wasCommitted() && !trn.wasRolledBack())
                    trn.commit();
                 } catch(HibernateException ex) {
                   rollbackTransactionSrv();
                   rollbackRegLoc.set( true );
                   
                   throw ex;
                 }
	}


Каждый объект - договор имеет атрибут принадлежности к филиалу, который явля-ется одним из параметров на основании, которого регламентируется доступ к нему. Таким образом, на центральном сервере хранится полный реестр договоров, а на филиале те ко-торые ему принадлежат, а так же централизованные и заключенные в его пользу. С учетом этого, необходимо было разработать механизм, который бы обеспечивал синхронизацию данных центрального сервера с филиальскими при вводе и редактировании централизо-ванных и заключенных в пользу филиала договоров. На начальном этапе были разработа-ны SQL - скрипты, с помощью которых, используя технологию связанных серверов (Linked - Servers) выполнялась синхронизация. В дальнейшем, нами для решения данной задачи был разработан инструмент, который запускается по расписанию в режиме stand-alone и выполняет рассылку. Работает инструмент следующим образом. При запуске фор-мируется контекст JNDI имен серверов, на которые необходимо выполнять рассылку. Для этого на локальной машине инициализируется rmi – сервер, в котором устанавливается связь между менеджером транзакций - JOTM, источниками данных - SQL серверов фи-лиалов и Hibernate – фабрик сессий, см Листинг 3,4.

Листинг 3. Метод для увязки в JNDI контексте Hibernate фабрик сессий.


public  static Map<String;, ThreadLocal<Session;>> getRegistrySes() {
        
        if (registrySes.isEmpty()){
            try {
            java.rmi.registry.LocateRegistry.createRegistry(1099);     
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
            try {
                 jotm = new Jotm(true, true);
            } catch (NamingException ex) {
                ex.printStackTrace();
            }
            Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
            try {
                ctx = new InitialContext(prop);
            } catch (NamingException ex) {
                ex.printStackTrace();
            }
            try {
                ctx.bind(USER_TRANSACTION_JNDI_NAME, jotm.getUserTransaction());
            } catch (NamingException ex) {
                ex.printStackTrace();
            }
            
           bindDataSource(ctx,jotm, getMapHost());
           
            try {
                NamingEnumeration lstCon = ctx.listBindings("")  ;
                              while (lstCon.hasMore()){
                                   Binding bd = (Binding)lstCon.next();
                                  if(  bd.getObject() instanceof SessionFactory)
                                  {
                                       ThreadLocal <Session;> trsSess = new ThreadLocal<Session;>();
                        trsSess.set(((SessionFactory)ctx.lookup(bd.getName())).openSession());
                            registrySes.put(bd.getName(),trsSess);
                                  }
                              }
            }                     
                  catch (NamingException ex1) {
                ex1.printStackTrace();
            }
            }
        return registrySes;
    }

Листинг 4. Метод для увязки в контексте источников данных и менеджера транзакций.


    public static void bindDataSource(Context ictx, TMService jotm, Map<String;,String> mapHost) {

        String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
        String dbName = "Balance";
        Iterator keys = mapHost.keySet().iterator();
        
        while(keys.hasNext()){
            
        Object nameHost = keys.next();
        
        // Создание для каждого сервера СУБД источника данных
        XADataSource  xaDataSource = new StandardXADataSource();
        try {
            ((StandardXADataSource)xaDataSource).setDriverName(driverName);
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
 ((StandardXADataSource) xaDataSource).setUrl("jdbc:sqlserver://" + ma-pHost.get(nameHost)+ ";databaseName="+dbName);
       ((StandardXADataSource) xaDataSource).setTransactionManager(jotm.getTransactionManager());
        try {
            ictx.bind("dataSource"+nameHost,xaDataSource);
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
        
Configuration config = new Configura-tion().configure("ru/rosten/jobcontract/hibernate/hibernateGermesJOTM.cfg.xml");                   
             log.info("Получение схемы данных с сервера ->"+nameHost);
config.setProperty("hibernate.jndi.class","com.sun.jndi.rmi.registry.RegistryContextFactory");
                 config.setProperty("dialect","org.hibernate.dialect.SQLServerDialect");
                 config.setProperty("hibernate.connection.datasource","dataSource"+nameHost);
                 config.setProperty("hibernate.connection.username",user_name);
                 config.setProperty("hibernate.connection.password",password);
                 config.setProperty("hibernate.jndi.url","rmi://"+ipServer+":1099");
                 config.setProperty("hibernate.session_factory_name",nameHost.toString());
                 config.addResource("ru/rosten/jobcontract/hibernate/treatiesg.hbm.xml"); 
                          config.addResource("ru/rosten/jobcontract/hibernate/addendumg.hbm.xml"); 
                          config.addResource("ru/rosten/jobcontract/hibernate/s_org.hbm.xml"); 
                          config.addResource("ru/rosten/jobcontract/hibernate/sdog.hbm.xml");        
                 SessionFactory sessFactory =config.buildSessionFactory();
}

После того как сформирован JNDI контекст и выполнено связывание фабрик сессий Hibernate,драйверов СУБД и менеджера транзакций, выполняется рассылка объектов, см. Листинг 5.

Листинг5. Фрагмент метода рассылки объектов на филиалы.


public void sendAllDepartment(){
Session ses = (Session) HibernateUtilJOTM.getRegistrySes().get(centrServ).get();
            try {
                utx =  (UserTransaction) ctx.lookup("UserTransaction");   
            } catch (NamingException ex) {
                ex.printStackTrace();
            }   
      for (Object elem : this.getNewCentralTreaties( ses, Treaties.class,"updateRec",1 )) {
           try {
          log.info("начало транзакции");
          utx.begin();
          Treaties treaties = (Treaties)elem;
          log.info("Обрабатывается договор -"+treaties.getId());
          // Если договор централизованный в данный момент
         if(treaties.getTransfer()){
               this.commonMerge(treaties,centrServ);
              }
           treaties.setUpdateRec(false);
           log.info("Обновляем на центральном серваке");
           registrySes.get(centrServ).get().merge(treaties);
           
           log.info("начало общего коммит");
           utx.commit();  
                } catch (Exception ex) {
                    ex.printStackTrace();
                try {
                    utx.rollback();
                } catch (SecurityException ex1) {
                    ex1.printStackTrace();
                } 
                
      }
      Iterator keyReg = registrySes.keySet().iterator();
           while (keyReg.hasNext()) {
               Object regN = (Object) keyReg.next();
                   log.info("Закрывается соединение с сервером  - "+regN.toString());
                   registrySes.get(regN).get().close();
           }
                 jotm.stop();
}

При этом в рамках одной транзакции выполняется предварительное сохранение на всех серверах см. Листинг 6.

Листинг 6. Метод выполняющий "прекоммит" на все филиальские сервера, кроме центрального сервера


  public void commonMerge(Object mergObj, String CentrServ){
      
           Iterator keyReg = registrySes.keySet().iterator();
           while (keyReg.hasNext()) {
                
               Object regN = (Object) keyReg.next();
               if(!(regN.toString().indexOf(CentrServ)>-1;))
               {
                   log.info("Обновляем на сервере - "+regN.toString());
                   registrySes.get(regN.toString()).get().merge(mergObj);
                 }
           }
}

На следующем этапе был выполнен полный перенос уровня бизнес логики на сервер приложений. Исходя из схемы взаимодействия (рис.1), оптимальным архитектурным ре-шением является наличие единственного сервера СУБД в аппарате управления и установ-ка серверов приложений как на каждом филиале так и в аппарате управления. Однако в силу целого ряда причин, например, таких как наличие АРМов реализованных на FoxPro и функционирующих на филиалах, трудоемкость удаленного администрирования семи сер-веров приложений и пр., нами было принято решение об использовании одного сервера приложений в аппарате управления, с которым взаимодействуют все филиалы. В связи с накопленным опытом, процесс перехода у нас не вызвал ни каких трудностей. В качестве сервера приложений используется Tomcat 6.14. Следует отметить некоторые особенности формирования JNDI контекста и источников данных. Традиционно это все описывается в файле конфигурации сервера приложений server.xml. Мы использовали другой подход, исходя из того, что на сервере работает одновременно несколько Web – приложений, ко-торые используют как общие источники данных так и индивидуальные необходимые только данному приложению, причем источники данных могут изменяться и добавляться, что в свою очередь требует перезапуска всего сервера приложений. Поэтому мы считаем целесообразным в данном случае реализовать для каждого приложения контекстный сервлет - слушатель, который при запуске приложения формирует контекст JNDI имен и любые его изменения не требуют перезапуска всего сервера см. Листинг 7. Так же в дан-ном классе создается коллекция с параметрами источников данных, а в случае, когда ис-точники данных одного типа, то в коллекции указывается только IP – адрес удаленного сервера, а остальное конфигурирование выполняется в "слушателе" в цикле. Единствен-ным недостатком данного способа является необходимость перекомпиляции всего прило-жения. Безусловно, конфигурирование с помощью xml файлов является более правиль-ным, надежным и удобным способом, однако когда речь идет о десятках пересекающихся в Web - приложениях источников данных, этот способ является на наш взгляд мене удоб-ным.

Листинг 7. Фрагмент "слушателя" Web – приложения.


public class ApplicationListener implements ServletContextListener {
    /** Коллекция серверов филиалов */
    private static Map  mapHost = new HashMap();
    /** Логгер событий в классе */
  private static Logger log = Logger.getLogger(ApplicationListener.class);  
   /** Менеджер транзакций */
  public  static TMService jotm;
 /** JNDI имя менеджера транзакций */
  private static final String USER_TRANSACTION_JNDI_NAME = "UserTransaction";
  /** Контекст JNDI имен  */
  private InitialContext ictx;

    public void contextInitialized(ServletContextEvent sce) {
        try {
            log.info("Старт конфигурирования" );
            jotm = new Jotm(true, true);
            Properties prop = new Properties();
           prop.put(Context.INITIAL_CONTEXT_FACTORY,
                org.apache.naming.java.javaURLContextFactory.class.getName());
            ictx = new InitialContext(prop);
            log.info("связь с менеджером транзакций");
            ictx.rebind(USER_TRANSACTION_JNDI_NAME, jotm.getUserTransaction());
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
        log.info("связь с источниками данных");
            this.bindDataSource(ictx,jotm,this.getMapHost());
    }
}

Таким образом, реализацию механизма распределенной транзакции без специализи-рованных средств, например, только средствами библиотеки Hibernate, следует рассмат-ривать как промежуточный вариант и использование его как основного решения, на наш взгляд недопустимо. Реализация механизма синхронизации данных на стороне сервера приложений увеличивает производительность СУБД, делает приложение независимым от типа сервера базы данных. БД. При наличии десятков источников данных, которые ис-пользуются множеством приложений, их конфигурирование на наш взгляд более удобно в контекстном сервлете – слушателе каждого Web – приложения.

Список используемых источников

  1. Фаулер М. Архитектура корпоративных программных приложений. : Учеб. посо-бие для программистов, проектировщиков. Вильямс – 2004г. – 544с.
  2. Технология тиражирования данных с распределенных системах, Г. Барон, Г. Ла-дыженский, "Открытые системы " №02 1994г.


Жмайлов Б.Б., Александров П.В.
ОАО "РОСТОВЭНЕРГО"


Н. Смирнов
"Java 2 Enterprise. Основы практической разработки распределенных корпоративных приложений"
Подробнее>>
Заказать>>


Е. Буткевич
"Пишем программы и игры для сотовых телефонов"
Подробнее>>
Заказать>>

Узнай о чем ты на самом деле сейчас думаешь тут.


[an error occurred while processing this directive]



Apache Struts 2.0.11
Apache MyFaces Trinidad Core 1.2.3.
Sun переводит мобильные устройства с Java ME на Java SE
Хакерская атака!