2012-01-05 7 views
5

Мы работаем над различными интеграциями с роем идентично структурированных устаревших баз данных, которые в принципе не могут быть изменены. Для этого мы добавили дополнительную базу данных для хранения таких вещей, как метаинформация, правила маршрутизации и временное хранение данных для устаревших баз данных.NHibernate & WCF: производительность (повторное использование сеанса) против параллелизма (одновременные запросы)

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

Я делаю балансирующий акт для решения этих двух проблем, но на самом деле не устранил проблемы параллелизма.

поведение службы установлено для обработки одного запроса в то время, как так:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode=ConcurrencyMode.Single] 
public class LegacyGateService : ILegacyGateService 

Ранее, после некоторых «вдохновений» (читайте: копирование/вставка) из Интернета, я в конечном итоге добавив набор классов, называемый как XxxNHibernateUtil, для вспомогательной базы данных и устаревших баз данных, соответственно. Эти классы контролируют сеансы NHibernate и генерируют или повторно используют сеансы из предварительно инициализированных SessionFactories.

Для вспомогательной базы данных выглядит следующим образом:

public static class LegacyGateNHibernateUtil 
{ 
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory(); 

    private static ISessionFactory BuildSessionFactory() 
    { 
     try 
     { 
      Configuration Cfg = new Configuration(); 
      Cfg.Configure(); 
      Cfg.AddAssembly("LegacyGate.Persistence"); 
      return Cfg.BuildSessionFactory(); 
     } 
     catch (Exception ex) 
     { 
      throw ex; 
     } 
    } 

    public static ISessionFactory GetSessionFactory() 
    { 
     return sessionFactory; 
    } 

    public static ISession GetCurrentSession() 
    { 
     if (!CurrentSessionContext.HasBind(GetSessionFactory())) 
      CurrentSessionContext.Bind(GetSessionFactory().OpenSession()); 

     return GetSessionFactory().GetCurrentSession(); 
    } 

    public static void DisposeCurrentSession() 
    { 
     ISession currentSession = CurrentSessionContext.Unbind(GetSessionFactory()); 

     if (currentSession != null) 
     { 
      if (currentSession.IsOpen) 
       currentSession.Close(); 
      currentSession.Dispose(); 
     } 
    } 
} 

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

EDIT: контекст сеанса, конечно, установить в hibernate.cfg.xml так:

<property name="current_session_context_class">call</property> 

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

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

Я думаю, мне нужно сгенерировать и обработать сеансы по-другому, так что разные транзакции в одни и те же базы данных обрабатываются упорядоченным образом и не мешают друг другу. Однако мне не хватает понимания, как это сделать. Любая помощь очень ценится!


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

  1. «Не удалось преобразовать IDataReader в NDataReader»/ «Неверная попытка вызова MetaData при закрытии считывателя».
  2. «Незаконный доступ к загрузке коллекции»
  3. «Начинать не удалось с исключением SQL»/«Истекло время ожидания». Период ожидания, прошедший до завершения операции, или сервер не отвечает ».
  4. «невозможно выполнить запрос»/«ExecuteReader требует открытого и доступного соединения. Текущее состояние соединения закрыто».
  5. «не удалось инициализировать коллекцию:«/«Истекло время ожидания». Период ожидания, прошедший до завершения операции, или сервер не отвечает ».
  6. «Сделка не была успешно запущена»
  7. «Сделка либо не связана с текущим соединением, либо была завершена».
  8. «не удалось инициализировать коллекцию:«/«Неверная попытка вызова Чтение, когда считыватель закрыт».

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

Для другого метода обслуживания эти проблемы не отображаются со вспомогательной базой данных, но через некоторое время я начинаю получать исключения ZombiedTransaction (взаимоблокировки) с транзакциями в устаревшие базы данных. Еще ... Что дает?

+0

Звучит так, будто вы сталкиваетесь с проблемами параллелизма. Какую версию NH вы используете? Если вы используете 3.2, я бы предложил использовать «wcf_operation» в качестве контекста сеанса.Если вы не используете 3.2, вы все равно можете сделать это. –

+2

Также выражение 'throw ex'' выше - это большое значение no-no. Когда вы это делаете, вы уничтожаете трассировку стека. Вы должны использовать 'throw;' Поскольку вы ничего не делаете с исключением, не должно быть даже блока catch try. –

+0

Извините. Я забыл ваши комментарии. Я использую версию 3.1, но я мог бы обновиться, если это поможет! :-) И ОК, я исправлю свое исключение no-no's и буду помнить об этом в будущем! Thnx до сих пор! –

ответ

12

Легкий ответ: Вы не используете повторные сеансы NHibernate.

Они не являются тяжеловесными объектами, и они предназначены для создания, управления и размещения в соответствии с шаблоном Единицы работы. Попытка «разделить» их по нескольким запросам идет вразрез с их предполагаемым использованием.

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

Помните, что фабрика сеансов NHibernate - это тяжеловесный объект. Это поточно-безопасный, поэтому вы можете и должны делиться одним экземпляром по всем запросам из коробки.

Ваш код должен выглядеть концептуально, как это:

public class Service : IService 
{ 
    static Service() 
    { 
     Configuration Cfg = new Configuration(); 
     Cfg.Configure(); 
     Cfg.AddAssembly("LegacyGate.Persistence"); 
     Service.SessionFactory = Cfg.BuildSessionFactory(); 
    } 

    protected static ISessionFactory SessionFactory { get; private set; } 

    public void ServiceMethod(...) 
    { 
     using(var session = Service.SessionFactory.CreateSession()) 
     { 
      // Do database stuff 
      ... 
     } 
    } 
} 

Как и в сторону: В идеале, вы бы зависимость, вводя ISessionFactory в службу.

+1

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

+0

Хммм ... Хороший вопрос! Поэтому в основном измените метод GetCurrentSession на одну строку: return GetSessionFactory(). OpenSession(); ? –

+0

Но это устранит тупики? –

Смежные вопросы