Мы работаем над различными интеграциями с множеством устаревших баз данных с одинаковой структурой, которые в принципе невозможно изменить. Для этого мы добавили вспомогательную базу данных для хранения таких вещей, как метаинформация, правила маршрутизации и для временного хранения данных для устаревших баз данных.
В основном мы используем 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 Для одного метода службы при тестировании с более чем одним агентом первая дюжина или около того вызовов работает нормально, а затем начинает появляться следующая строка исключений, относящихся только к вспомогательной базе данных:
- «Произошла проблема при преобразовании IDataReader в NDataReader» / «Неверная попытка вызвать метаданные, когда программа чтения закрыта».
- "незаконный доступ к загрузке коллекции"
- «Начать не удалось с исключением SQL» / «Время ожидания истекло. Время ожидания истекло до завершения операции или сервер не отвечает».
- «не удалось выполнить запрос» / «ExecuteReader требует открытого и доступного соединения. Текущее состояние соединения закрыто».
- «не удалось инициализировать коллекцию:» / «Время ожидания истекло. Время ожидания истекло до завершения операции или сервер не отвечает».
- "Транзакция не была успешно начата"
- "Транзакция либо не связана с текущим соединением, либо уже завершена".
- "не удалось инициализировать коллекцию:" / "Неверная попытка вызвать Read, когда программа чтения закрыта".
Исключение 1, по крайней мере, указывает на то, что к одному и тому же сеансу обращаются несколько потоков (вероятно, вызовы). Также остальные указывают на то, что текущий сеанс прерван другими процессами. Но как это может быть, когда я пытался изолировать звонки и ставить их в очередь?
Для другого метода обслуживания эти проблемы не появляются со вспомогательной базой данных, но через некоторое время я начинаю получать исключения ZombiedTransaction (взаимоблокировки) с транзакциями к устаревшим базам данных. Еще... Что дает?
throw ex;
выше — это большое нет-нет. Вы уничтожаете трассировку стека, когда делаете это. Вы должны использоватьthrow;
Поскольку вы ничего не делаете, за исключением того, что не должно быть даже блока try catch. - person Cole W   schedule 05.01.2012