Добрый день, хабражители! В настоящее время я работаю над повышением производительности крупного корпоративного решения.
Его специфика позволяет множеству пользователей выполнять схожие действия и соответственно «негласно» сервер приложений работает с одним и тем же кодом.
И в один момент долгого пути ускорения приложения было замечено, что в топе самых низкопроизводительных областей log4j занимает верхние места.
Первой мыслью было чрезмерное журналирование, но последующий анализ опроверг ее.
Более того, это крайне важные данные для инженеров поддержки, и если удалить их сейчас, то либо код вернут обратно, либо анализ проблем производственного сервера будет существенно затруднен.
Это побудило меня провести исследование: есть ли способ оптимизировать процесс ведения журнала? Отказ от ответственности : из-за обилия в этой статье диаграмм, вызывающих психоделические ассоциации (вроде этой: это не сравнение синхронного и асинхронного режимов логирования, здесь дракон убивает змею!), она не рекомендуется к просмотру несовершеннолетним, людям с нестабильные психи и беременные женщины, код которых уже в разработке Следующий патч в этом году не выйдет. Будьте осторожны, под катком движение.
Какова причина?
Как обычно, причина банальна – «конкурентный доступ».
После нескольких экспериментов с плавающим числом параллельных потоков стало понятно, что время выполнения вызова логирования не является линейным, поэтому вывод — HDD сильно теряет.
Вот результаты измерений:
Какое решение?
Можно искать другие методы логирования, обновлять библиотеки, все это можно сделать, но задача — добиться максимального результата при минимуме усилий.Я также могу рассказать о библиотеке «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 потока.
Вот результаты:
Асинхронный запуск, 2 потока
Buffer = 500 Теперь перейдём в асинхронный режим и заодно попробуем найти оптимальный буфер, начиная с 500Buffer = 1500 Увеличим буфер в 3 раза и проведем те же тесты:
Buffer = 5000 Увеличим буфер в 10 раз и проведем те же тесты:
Резюме для 2-х потоков Теперь для наглядности соберу все асинхронные (чтобы попытаться определить оптимальный буфер) и синхронные (для наглядности, интересно, кто победит) тесты в одну диаграмму:
Теперь думаю ускорение в асинхронном режиме хорошо видно.
Но прежде чем делать выводы, повторим наши тесты на 5 и 10 потоках.
Синхронный запуск, 5 потоков
Асинхронный запуск, 5 потоков
Буфер = 500Буфер = 1500
Буфер = 5000
Итого за 5 потоков
Синхронный запуск, 10 потоков
Асинхронный запуск, 10 потоков
Буфер = 500Буфер = 1500
Буфер = 5000
Итого за 10 потоков
Вывод, как говорится, очевиден.
Поскольку теперь можно однозначно говорить о преимуществе метода асинхронного логирования, попробуем увеличить объём тестов ещё в 10 раз.
Добавим тест на 55 000 событий логирования (от 1000 до 10 000 с шагом 1000).
Возьмем буфер равный 500 (поскольку на первый взгляд, а потом будет доказано, он самый оптимальный в наших тестах).
Синхронный запуск, 10 потоков на больших объемах данных
Асинхронный запуск, 10 потоков на больших объемах данных
Сводка для 10 потоков с большими объемами данных
Оптимальный буфер
На данный момент у нас уже накопилось достаточно много статистических данных, поэтому давайте их все усредним и посмотрим, какой буфер подойдет больше всего.
2 потока | |
|
|
5 потоков | |
|
|
10 потоков | |
|
|
10 потоков, большой объем тестов | |
|
|
А теперь если сравнить общее (или среднее) время работы всех тестов, то можно получить константу, отображающую порядок, в котором асинхронный режим побеждает синхронный, для меня это — 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
-
Проблемы С Инструментами В Крупных Проектах
19 Oct, 24 -
Идеальные Наушники-Вкладыши За 5 Минут
19 Oct, 24 -
Выпуск Livestreet 0.5.1 И Переход На Github
19 Oct, 24 -
Написание Собственного Lisp На Javascript
19 Oct, 24 -
Лаборатория Веб-Телевидения
19 Oct, 24