NHibernate & WCF: производительность (повторное использование сеанса) и параллелизм (одновременные запросы) - PullRequest
5 голосов
/ 05 января 2012

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

В основном мы используем 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();
        }
    }
}

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

РЕДАКТИРОВАТЬ: Контекст сеанса, конечно, устанавливается в hibernate.cfg.xml примерно так:

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

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

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

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


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

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

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

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

1 Ответ

12 голосов
/ 05 января 2012

Простой ответ: Вы не можете повторно использовать сеансы 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 в службу зависимостей.

...