Каркасы журналов и многопоточная совместимость - PullRequest
1 голос
/ 16 февраля 2011

Обратите внимание, что я видел эти вопросы здесь на SO (которые обсуждают безопасность потоков log4net), и я подозреваю, что они отвечают на мой вопрос, но я все равно задам вопрос:

Многопоточность безопаснаlogging

Log4Net FileAppender не безопасен для потоков?

Недавно я написал службу WCF для ведения журнала.Идея очень похожа на Clog (или ищите Clog под Calcium ).По сути, я реализовал протоколирование API для использования в клиенте Silverlight (библиотека классов Silverlight).API журналирования, более или менее, является клоном Common.Logging for .NET API , который мы используем в других местах нашего приложения.Реализация API перенаправляет все сообщения регистрации в службу регистрации WCF, которая сама по себе реализована в терминах Common.Logging.

При просмотре Clog я обнаружил следующий код в классе Log4NetStrategy, который поразилМне немного странно:

/// <summary> 
/// All Write log calls are done asynchronously because the DanielVaughan.Logging.Log 
/// uses the AppPool to dispatch calls to this method. Therefore we need to ensure 
/// that the call to Log is threadsafe. That is, if an appender such as FileAppender is used, 
/// then we need to ensure it is called from no more than one thread at a time. 
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="loggingEvent">The logging event.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
static void WriteThreadSafe(ILogger logger, LoggingEvent loggingEvent)
{
  logger.Log(loggingEvent);
}

Log4NetStrategy (а также стратегии для NLog и EnterpriseLibrary) реализует интерфейс, имеющий метод наподобие Write (ILogEntry logEntry).ILogEntry - это, по сути, DTO, который служба регистрации Clog получила от клиента.Информация из logEntry извлекается и используется для создания log4net LoggingEvent.Соответствующий регистратор log4net также извлекается на основе имени регистратора в ILogEntry DTO.После того, как log4net LoggingEvent создан, он и регистратор отправляются в метод WriteThreadSafe, описанный выше, и регистрируются через log4net.Реализации NLog и EnterpriesLibrary похожи.

В псевдокоде метод "Write" в службе журналов Clog выглядит примерно так:

// Write on the logging service
// Send the input log entry to each configured strategy.
public void Write(ILogEntry logEntry)
{
  foreach (ILoggingStrategy ls in loggingStrategies)
  {
    ls.Write(logEntry);
  }
}

// Write on the Log4NetStrategy
// Convert the logEntry to log4net form and log with log4net
public void Write(ILogEntry logEntry)
{
  log4net.LoggingEvent le = ConvertToLoggingEvent(logEntry);
  ILog logger = log4net.GetLogger(logEntry.Logger);
  WriteThreadSafe(logger, le);
}

Так вот мой вопрос ... Вообщеlog4net (и NLog и, я полагаю, EnterpriseLibrary) считаются совместимыми с многопоточностью.То есть пользователь общедоступного API может просто вызывать log.Info, log.Log и т. Д. И не беспокоиться о работе в многопоточной среде.Каркас журналирования должен позаботиться о том, чтобы вызовы журналирования (и любая обработка внутри вызовов журналирования) были поточнобезопасными.

Если каркасы журналирования совместимы с многопоточностью, тогда используется

[MethodImpl(MethodImplOptions.Synchronized]

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

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

Имеет ли это смысл?Действительно ли необходима явная синхронизация вызовов журналирования (по крайней мере для log4net и NLog)?

1 Ответ

5 голосов
/ 17 февраля 2011

Почему замки?

Насколько я помню, в Enterprise Library используется класс .NET Tracelistener, у которого есть свойство IsThreadSafe. Если слушатель является потокобезопасным, синхронизация не выполняется. Для файла в качестве устройства вывода нет причин синхронизировать доступ из нескольких потоков, если только StreamWriter , который содержит буфер записи, может быть поврежден из-за несинхронизированных записей. По этой причине вам нужно получить синхронизированный StreamWriter для записи в файл из разных потоков.

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

... Как получить несколько процессов для входа в тот же файл?

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

Производительность форматирования

Даже если вы считаете, что блокировка снижает производительность, часто пропускаются и другие затраты. Настраиваемый формат вывода очень хорош, но по крайней мере форматирование в Enterprise Library до 4.0 было очень медленным . Я сделал это в 13 раз быстрее, что привело к увеличению пропускной способности чистых файлов в 2 раза.

Одновременная запись в файл

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

Scalabilty

Если вас беспокоит масштабируемость, вам нужен буфер для каждого потока, который объединяется в определенный момент времени, чтобы обеспечить быстрый сбор данных. Затем вы можете реализовать глобальный кольцевой буфер, который получает из всех потоков отформатированные данные, где самые старые данные перезаписываются. Когда происходит что-то интересное, вы можете сбросить весь кольцевой буфер на диск. Этот дизайн в ~ 10-20 раз быстрее любого файлового приложения. Windows Event Tracing использует трюк с кольцевым буфером, который невероятно быстр.

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

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

Lock Free

Если вы хотите создать масштабируемую инфраструктуру трассировки, вам нужно передать все свои данные для одного вызова трассировки в качестве неизменяемых данных, отформатировать данные в строку и передать их на ваше устройство вывода. Это может быть так просто, как (C # псевдокод)

  1. Trace.Info ("blahh {0}", data);
  2. traceString = Format (formatString, args, CurrentTime);
  3. Write (traceString);

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

  1. Изоляция Это, безусловно, лучшее решение. Если проблему можно разделить так, чтобы несколько потоков могли работать независимо, вам никогда не нужно думать о блокировке или общих данных.
  2. Неизменность Вторым лучшим решением является обмен данными, но как только несколько потоков могут «видеть» их, они никогда не должны изменяться. Это может быть достигнуто, например, с объектами, которые имеют только геттеры и все данные передаются через ctor.
  3. Замок Если вы используете блокировку без блокировки или какая-то стратегия блокировки не имеет большого значения. Вы находитесь в мире боли, где вы должны обращать внимание на кэши процессора, ложное совместное использование , и если вы отважны, вы даже можете попробовать какой-нибудь метод без блокировки только для того, чтобы узнать, что в лучшем случае он работает в два раза быстрее, чем нормальный замок. В некоторых случаях код без блокировки может быть медленнее.

Если вы хотите больше узнать о деталях потоков, я рекомендую блог Джо Даффи .

Трассировка стратегии

Для написания поддерживаемого программного обеспечения вы должны иметь возможность следить за вашим приложением и потоком данных. Только тогда вы сможете диагностировать проблемы, которые происходят только на вашем сайте клиента. Это окупается, чтобы «загромождать» ваш код следами, которые дают вам достаточно информации для быстрой диагностики проблемы. Вы можете отслеживать слишком много или слишком мало или не соответствующие данные. Одна хорошая линия трассировки, которая действительно коррелирует, например, Ваша строка запроса XPath с файлом, с которым она работает, намного лучше, чем разбросанные фрагменты информации в вашем файле журнала.

С уважением, Алоис Краус

...