Производительность Log4J 1.X В Синхронном И Асинхронном Режиме

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

Его специфика позволяет множеству пользователей выполнять схожие действия и соответственно «негласно» сервер приложений работает с одним и тем же кодом.

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

Первой мыслью было чрезмерное журналирование, но последующий анализ опроверг ее.

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

Это побудило меня провести исследование: есть ли способ оптимизировать процесс ведения журнала? Отказ от ответственности : из-за обилия в этой статье диаграмм, вызывающих психоделические ассоциации (вроде этой: это не сравнение синхронного и асинхронного режимов логирования, здесь дракон убивает змею!), она не рекомендуется к просмотру несовершеннолетним, людям с нестабильные психи и беременные женщины, код которых уже в разработке Следующий патч в этом году не выйдет. Будьте осторожны, под катком движение.



Какова причина?

Как обычно, причина банальна – «конкурентный доступ».

После нескольких экспериментов с плавающим числом параллельных потоков стало понятно, что время выполнения вызова логирования не является линейным, поэтому вывод — HDD сильно теряет. Вот результаты измерений:

Производительность Log4j 1.x в синхронном и асинхронном режиме



Какое решение?

Можно искать другие методы логирования, обновлять библиотеки, все это можно сделать, но задача — добиться максимального результата при минимуме усилий.

Я также могу рассказать о библиотеке «log4j 2», но это будет отдельная статья.

Теперь мы рассмотрим инструменты, предоставленные нам «из коробки» в log4j 1.x. Среди поставляемых в комплекте с библиотекой аппендеров есть AsyncAppender, который позволяет с помощью промежуточного буфера для накопления событий логирования организовать асинхронную работу с файловой системой (если финальный аппендером является файловый аппендер, т.к.

он изначально предназначался для SMTP-логгера) .

После генерации события журнала накапливаются и только при достижении определенного уровня заполнения буфера они попадают в файл.



Измерения

Теперь, когда подход определен, нам нужно понять, насколько он эффективен; для этого проведем соответствующие измерения.

Мы будем измерять так: 0) Заранее предупреждаю, «красивых данных я не сделал», местами видно, что процессор переключился на другую работу, и я оставил эти места как есть.

Это также часть реальной работы системы.

1) Разделим тесты на 3 группы: — 10 протоколируемых событий (от 1 до 10 с шагом 1) — 550 журналов событий (от 10 до 100 с шагом 10) — 5500 журналов событий (от 100 до 1000 с шагом 100) 2) В каждой группе будет 3 подгруппы тестов, в зависимости от размера буфера (попробуем найти оптимальную): — 500 событий — 1500 событий — 5000 событий 3) Тесты будут выполняться синхронно и асинхронно.

Конфигурация синхронного регистратора

  
  
   

<Эxml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" > <log4j:configuration> <appender name="fileAppender" class="org.apache.log4j.FileAppender"> <param name="file" value="st.log"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{ABSOLUTE} %5p %t %c{1}:%M:%L - %m%n"/> </layout> </appender> <root> <priority value="info" /> <appender-ref ref="fileAppender"/> </root> </log4j:configuration>

Конфигурация асинхронного регистратора

<Эxml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" > <log4j:configuration> <appender name="fileAppender" class="org.apache.log4j.FileAppender"> <param name="file" value="st.log"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{ABSOLUTE} %5p %t %c{1}:%M:%L - %m%n"/> </layout> </appender> <appender name="ASYNC" class="org.apache.log4j.AsyncAppender"> <param name="BufferSize" value="500"/> <appender-ref ref="fileAppender"/> </appender> <root> <priority value="info" /> <appender-ref ref="ASYNC"/> </root> </log4j:configuration>

4) Сами тесты представляют собой простые вызовы логирования, смешанные с «случайной работой» (длительностью от 1 до 15 мс, чтобы иметь возможность попеременного доступа к файлу).

Исходный код теста

package com.ice.logger_test; import org.apache.commons.lang3.time.StopWatch; import org.apache.log4j.Logger; import java.util.Random; public class SimpleTest { private static Logger logger = Logger.getLogger(SimpleTest.class); private static double NANOS_TO_SEC = 1000000000.0d; private static String LOG_MESSAGE = "One hundred bytes log message for performing some tests using sync/async appenders of log4j library"; public static void main(String[] args) throws InterruptedException { //performTest("Single thread"); ThreadRunner t1 = new ThreadRunner(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); new Thread(t1).

start(); } private static void performTest(String message) throws InterruptedException { logger.info("Warm-up."); logger.info("Warm-up."); logger.info("Warm-up."); StopWatch timer = new StopWatch(); Random ran = new Random(); for(int i = 1; i <= 10000; i += getIncrementator(i)) { timer.reset(); timer.start(); int iterations = 0; for(int j = 1; j <= i; j++) { timer.suspend(); Thread.sleep(ran.nextInt(15)+1); // some work timer.resume(); logger.info(LOG_MESSAGE); timer.suspend(); Thread.sleep(ran.nextInt(15)+1); // some work timer.resume(); iterations = j; } timer.stop(); System.out.printf(message + " %d iteration(s) %f sec\n", iterations, (timer.getNanoTime() / NANOS_TO_SEC)); } } private static int getIncrementator(int i) { if(i >= 0 && i < 10) return 1; if(i >= 10 && i < 100) return 10; if(i >= 100 && i < 1000) return 100; if(i >= 1000 && i < 10000) return 1000; if(i >= 10000 && i <= 100000) return 10000; return 0; } static class ThreadRunner implements Runnable { @Override public void run() { try { performTest(Thread.currentThread().

getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }

Визуализируем оба результата (плюс промежуточные, чтобы не потерять нить), синхронный и асинхронный, с диаграммами для наглядности.

Итак, начнем.



Синхронный запуск, 2 потока
Для начала посмотрим, как ведет себя логгер в синхронной конфигурации.

Давайте запустим все скрипты в 2 потока.

Вот результаты:

Производительность Log4j 1.x в синхронном и асинхронном режиме



Асинхронный запуск, 2 потока
Buffer = 500 Теперь перейдём в асинхронный режим и заодно попробуем найти оптимальный буфер, начиная с 500

Производительность Log4j 1.x в синхронном и асинхронном режиме

Buffer = 1500 Увеличим буфер в 3 раза и проведем те же тесты:

Производительность Log4j 1.x в синхронном и асинхронном режиме

Buffer = 5000 Увеличим буфер в 10 раз и проведем те же тесты:

Производительность Log4j 1.x в синхронном и асинхронном режиме

Резюме для 2-х потоков Теперь для наглядности соберу все асинхронные (чтобы попытаться определить оптимальный буфер) и синхронные (для наглядности, интересно, кто победит) тесты в одну диаграмму:

Производительность Log4j 1.x в синхронном и асинхронном режиме

Теперь думаю ускорение в асинхронном режиме хорошо видно.

Но прежде чем делать выводы, повторим наши тесты на 5 и 10 потоках.



Синхронный запуск, 5 потоков


Производительность Log4j 1.x в синхронном и асинхронном режиме



Асинхронный запуск, 5 потоков
Буфер = 500

Производительность Log4j 1.x в синхронном и асинхронном режиме

Буфер = 1500

Производительность Log4j 1.x в синхронном и асинхронном режиме

Буфер = 5000

Производительность Log4j 1.x в синхронном и асинхронном режиме

Итого за 5 потоков

Производительность Log4j 1.x в синхронном и асинхронном режиме



Синхронный запуск, 10 потоков


Производительность Log4j 1.x в синхронном и асинхронном режиме



Асинхронный запуск, 10 потоков
Буфер = 500

Производительность Log4j 1.x в синхронном и асинхронном режиме

Буфер = 1500

Производительность Log4j 1.x в синхронном и асинхронном режиме

Буфер = 5000

Производительность Log4j 1.x в синхронном и асинхронном режиме

Итого за 10 потоков

Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме

Вывод, как говорится, очевиден.

Поскольку теперь можно однозначно говорить о преимуществе метода асинхронного логирования, попробуем увеличить объём тестов ещё в 10 раз.

Добавим тест на 55 000 событий логирования (от 1000 до 10 000 с шагом 1000).

Возьмем буфер равный 500 (поскольку на первый взгляд, а потом будет доказано, он самый оптимальный в наших тестах).



Синхронный запуск, 10 потоков на больших объемах данных


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Асинхронный запуск, 10 потоков на больших объемах данных


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Сводка для 10 потоков с большими объемами данных


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Оптимальный буфер
На данный момент у нас уже накопилось достаточно много статистических данных, поэтому давайте их все усредним и посмотрим, какой буфер подойдет больше всего.

2 потока


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме

5 потоков


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме

10 потоков


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме

10 потоков, большой объем тестов


Производительность Log4j 1.x в синхронном и асинхронном режиме



Производительность Log4j 1.x в синхронном и асинхронном режиме

Всего - 500 событий , именно наш буфер позволяет нам наиболее эффективно работать в асинхронном режиме.

А теперь если сравнить общее (или среднее) время работы всех тестов, то можно получить константу, отображающую порядок, в котором асинхронный режим побеждает синхронный, для меня это — 8,9 (раз) .



Заключение

Из приведенного выше материала становится ясно, что стратегия асинхронного журналирования обеспечивает преимущества в производительности.

Тогда возникает вопрос – а почему бы не использовать его всегда? Для того, чтобы сделать выбор в пользу того или иного метода, необходимо представить себе механизмы, которые спрятаны внутри.

Ниже приведены несколько моментов, взятых с оффсайта: 1) AsyncAppender работает в своем собственном потоке (при этом сам FileAppender работает в контексте текущего потока), поэтому при его использовании желательно увеличить лимит потока сервера приложений.

2) При использовании AsyncAppender возникают накладные расходы памяти, поскольку события журналирования сбрасываются в файл не мгновенно, а после заполнения буфера.

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

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

Используйте асинхронность там, где она действительно необходима: - долговременные приложения - SMTP, JDBC — общая блокировка ресурсов — FTP, Локальное хранилище файлов Но прежде всего обязательно профилируйте свой код. Версия этой статьи в Excel: docs.google.com/spreadsheet/cccЭkey=0AkyN15vTZD-ddHV0Y3p4QVUxTXVZRldPcU0tNzNucWc&usp=sharing docs.google.com/spreadsheet/cccЭkey=0AkyN15vTZD-ddFhGakZsVWRjV1AxeGljdDczQjdNbnc&usp=sharing Спасибо за внимание.

Надеюсь, статья будет вам полезна.

Продуктивной недели! В опросе могут участвовать только зарегистрированные пользователи.

Войти , Пожалуйста.

Нужна ли аналогичная статья о log4j 2.x? 23,57% Да, только о log4j 2.x 37 59,87% Да, о log4j 1.x по сравнению с log4j 2.x 94 16,56% Нет Проголосовали 26 157 пользователей.

47 пользователей воздержались.

Теги: #log4j #log4j #sync #async # Performance #Высокая производительность #Разработка веб-сайтов #java

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.