Позиционирование проблемы блокировки асинхронного вывода журнала Log4j2 одним махом

Java оптимизация производительности Apache Log4j
Позиционирование проблемы блокировки асинхронного вывода журнала Log4j2 одним махом

«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!"

Расположение проблемы блокировки вывода журнала Log4j2

проблемное явление

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

Позиционирование основной проблемы

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

JFRОн очень удобен для просмотра и обнаружения проблем постфактум, а при правильной настройке потеря производительности крайне мала, и не требует дополнительного сбора и унифицированного процесса обслуживания для интегрированного отображения, как системы APM. мыВозьмите это, как вы идетеЭтого достаточно, но JFR ориентирован на обнаружение проблемы в одном процессе, поиск проблемного процесса и определение местоположения межпроцессного отслеживания связей бизнес-проблем по-прежнему требует системы APM.

Собираем JFR соответствующего периода времени следующей командой:

jcmd 进程pid JFR.dump begin=2021-07-04T17:30:00.000Z end=2021-07-04T18:30:00.000Z

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

заМедленный ответ на некоторые запросы в истории, я обычно смотрю на это в соответствии со следующим процессом:

  1. Есть ли STW (Stop-the-world, см. мою другую статью:Связано с JVM - комплексное решение SafePoint и Stop The World):
  2. Есть ли длительный STW, вызванный GC
  3. Есть ли какая-либо другая причина, из-за которой все потоки процесса входят в точку сохранения, вызывая STW?
  4. Занимает ли слишком много времени ввод-вывод, например вызов других микросервисов, доступ к различным хранилищам (жесткий диск, база данных, кеш и т. д.)?
  5. Вы слишком долго блокируете некоторые замки?
  6. Слишком высокая загрузка ЦП, какие потоки вызывают это?

Далее разберем подробно.Первый это GC.Из рисунка ниже он уже давно серьезно не влияет на поток STW:image

Затем проверьте связанную с ней точку сохранения и обнаружите, что она не имеет никакого эффекта:

image

Затем мы рассмотрели события, связанные с вводом-выводом, и не обнаружили блокировок, связанных с бизнесом:

image

image

Затем мы проверяем, не блокируются ли некоторые блокировки слишком долго, и, наконец, находим исключение:

image

Через стек мы обнаружили, что это **журнал печати log4j2 застрял**.

анализ проблемы

1. Адрес монитора Java Monitor Blocked также изменится для того же объекта.

Прежде всего, по событию Java Monitor Blocked мы можем судить о том, является ли это той же блокировкой через адрес монитора. Например, здесь мы обнаруживаем, что эти потоки заблокированы вFFFF 4C09 3188Объект по этому адресу, который основан на относительном адресе объекта в куче Java.

нодля того же объекта,Этот адресне останется без изменений, каждый разКогда сканирование GC сортируется по этому объекту, если есть высвобожденная память, то вообще этот адрес изменится. Так как мы используем G1, этот объект не сканируется каждый GC, но если адрес меняется, это должно быть из-за GC

2. Введение в принцип асинхронного логирования log4j2

В нашей конфигурации приложения используемая структура журнала — log4j2, а используемая конфигурация —Конфигурация асинхронного журнала. Вот краткое введение в принцип асинхронного журнала Log4j2: Асинхронный журнал Log4j2 основан на высокопроизводительной структуре данных Disruptor, которая представляет собой кольцевой буфер и провела большую оптимизацию производительности (конкретные принципы см. ряд:Высококонкурентная структура данных (разрушитель)), приложение Log4j2 для этого выглядит так:

image

Проще говоря, многопоточность через класс фасада log4j2org.apache.logging.log4j.LoggerДля вывода журнала он инкапсулируется вorg.apache.logging.log4j.core.LogEvent, в кольцевой буфер Disruptor. На стороне потребителя есть один поток, который потребляет эти LogEvents и записывает их в соответствующий Appender.Здесь у нас есть только один Appender, и его конфигурация:

<RollingFile name="file" append="true"
             filePattern="./app.log-%d{yyyy.MM.dd.HH}">
    <PatternLayout pattern="${logFormat}"/>
    <Policies>
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    </Policies>
    <DirectWriteRolloverStrategy maxFiles="72"/>
</RollingFile>

Асинхронный регистратор настроен как (configincludeLocationУстановите значение false, чтобы избежать потери производительности из-за получения стека вызовов каждый раз, когда вы печатаете журнал):

 <Asyncroot level="info" includeLocation="false">
    <appender-ref ref="file"/>
</Asyncroot>

Компонент log4j дополнительно настраивается как:

log4j2.component.properties:

# 当没有日志的时候,消费线程通过 Block 等待日志事件到来,这样 CPU 占用最少
AsyncLoggerConfig.WaitStrategy=Block

3. Размер RingBuffer разрушителя log4j2

Поскольку это кольцевой буфер, его емкость ограничена, мы не изменили здесь его размер, мы используем конфигурацию по умолчанию, проверьте его исходный код:

AsyncLoggerConfigDisruptor.java

public synchronized void start() {
    //省略其他代码
    ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLoggerConfig.RingBufferSize");
    disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory, ProducerType.MULTI, waitStrategy);
}

DisruptorUtil.java

private static final int RINGBUFFER_MIN_SIZE = 128;
private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024;

static int calculateRingBufferSize(final String propertyName) {
    //是否启用了 ThreadLocal,如果是则为 4 kB,不是则为 256 kB
    int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE;
    //读取系统变量,以及 log4j2.component.properties 文件获取 propertyName(这里是 AsyncLoggerConfig.RingBufferSize) 这个配置
    final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
            String.valueOf(ringBufferSize));
    try {
        int size = Integer.parseInt(userPreferredRBSize);
        //如果小于 128 字节则按照 128 字节设置
        if (size < RINGBUFFER_MIN_SIZE) {
            size = RINGBUFFER_MIN_SIZE;
            LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize,
                    RINGBUFFER_MIN_SIZE);
        }
        ringBufferSize = size;
    } catch (final Exception ex) {
        LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
    }
    //取最近的 2 的 n 次方,因为对于 2 的 n 次方取余等于对于 2^n-1 取与运算,这样更快
    return Integers.ceilingNextPowerOfTwo(ringBufferSize);
}

Если ThreadLocal разрешено генерировать LogEvents таким образом, каждый раз, когда вновь созданные LogEvents не используются ранее, они сохраняются в ThreadLocals, что позволяет избежать создания новых LogEvents. Но рассмотрим следующую ситуацию:

logger.info("{}", someObj);

Это создаст сильную ссылку, что приведет к тому, что у потока нет новых журналов.someObjОн никогда не подвергался переработке. такДля веб-приложений log4j2 генерирует LogEvents по умолчанию без включенного ThreadLocal.:

Constants.java

public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty(
            "log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet"));
public static final boolean ENABLE_THREADLOCALS = !IS_WEB_APP && PropertiesUtil.getProperties().getBooleanProperty(
            "log4j2.enable.threadlocals", true);

Отсюда видно, что,Наш RingBuffer имеет размер 256 КБ..

4. Что происходит с log4j2, когда RingBuffer заполнен?

Когда RingBuffer заполнен, еслиlog4j2.component.propertiesнастроенAsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=false, он будет ждать (фактически парковаться) в методе производства Disruptor, ожидая использования следующего слота кольцевого буфера, который может быть создан; конфигурация по умолчанию истинна, то естьВсе потоки, создающие журнал, пытаются получить одну и ту же блокировку в глобальном(private final Object queueFullEnqueueLock = new Object();):

DisruptorUtil.java

static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
        .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true);
private boolean synchronizeEnqueueWhenQueueFull() {
    return DisruptorUtil.ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
            // Background thread must never block
            && backgroundThreadId != Thread.currentThread().getId();
}

private final Object queueFullEnqueueLock = new Object();

private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) {
    //如果 AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=true,默认就是 true
    if (synchronizeEnqueueWhenQueueFull()) {
        synchronized (queueFullEnqueueLock) {
            disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
        }
    } else {
        //如果 AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=false
        disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
    }
}

При настройке по умолчаниюСтек исключений такой же, как и в JFR.,Например:

"Thread-0" #27 [13136] prio=5 os_prio=0 cpu=0.00ms elapsed=141.08s tid=0x0000022d6f2fbcc0 nid=0x3350 waiting for monitor entry  [0x000000399bcfe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue(AsyncLoggerConfigDisruptor.java:375)
	- waiting to lock <merged>(a java.lang.Object)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueueEvent(AsyncLoggerConfigDisruptor.java:330)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logInBackgroundThread(AsyncLoggerConfig.java:159)
	at org.apache.logging.log4j.core.async.EventRoute$1.logMessage(EventRoute.java:46)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.handleQueueFull(AsyncLoggerConfig.java:149)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncDelegate(AsyncLoggerConfig.java:136)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:116)
	at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
	at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
	at org.apache.logging.log4j.core.Logger.log(Logger.java:162)
	at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2190)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2144)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2127)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2003)
	at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1975)
	at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1312)
	省略业务方法堆栈

При значении false стек выглядит так:

"Thread-0" #27 [18152] prio=5 os_prio=0 cpu=0.00ms elapsed=5.68s tid=0x000002c1fa120e00 nid=0x46e8 runnable  [0x000000eda8efe000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17-loom/Native Method)
	at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17-loom/LockSupport.java:410)
	at com.lmax.disruptor.MultiProducerSequencer.next(MultiProducerSequencer.java:136)
	at com.lmax.disruptor.MultiProducerSequencer.next(MultiProducerSequencer.java:105)
	at com.lmax.disruptor.RingBuffer.publishEvent(RingBuffer.java:524)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue(AsyncLoggerConfigDisruptor.java:379)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueueEvent(AsyncLoggerConfigDisruptor.java:330)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logInBackgroundThread(AsyncLoggerConfig.java:159)
	at org.apache.logging.log4j.core.async.EventRoute$1.logMessage(EventRoute.java:46)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.handleQueueFull(AsyncLoggerConfig.java:149)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncDelegate(AsyncLoggerConfig.java:136)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:116)
	at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
	at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
	at org.apache.logging.log4j.core.Logger.log(Logger.java:162)
	at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2190)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2144)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2127)
	at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2003)
	at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1975)
	at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1312)

5. Почему он заполнен и что делает в это время наш потребительский поток?

Давайте посмотрим, есть ли какие-либо аномалии в потребляющем потоке в это время, потому чтоВ то время жесткий диск io смотрел на системный мониторинг, и не было никаких отклонений.,Так что этот поток, скорее всего, будет Runnable, постоянно записывая в журнал. В то же время мы знаем, что нижний уровень написания java основан на нативных вызовах, поэтому мы рассмотрим получение нативных методов JFR. Используя собственный пример профилирования метода в средстве просмотра событий, щелкните правой кнопкой мыши и выберите «Создать новую страницу с выбранным типом события»:

image

На созданной новой странице просмотрите по группировке потоков, проверьте поток потребления разрушителя Log4j2, вы можете получить следующую цифру:image

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

"Log4j2-TF-2-AsyncLoggerConfig-1" #26 [23680] daemon prio=5 os_prio=0 cpu=406.25ms elapsed=2.89s tid=0x0000022d6f3d4080 nid=0x5c80 runnable  [0x000000399bbfe000]
   java.lang.Thread.State: RUNNABLE
	at java.io.FileOutputStream.writeBytes(java.base@17-loom/Native Method)
	at java.io.FileOutputStream.write(java.base@17-loom/FileOutputStream.java:365)
	at org.apache.logging.log4j.core.appender.OutputStreamManager.writeToDestination(OutputStreamManager.java:261)
	- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
	at org.apache.logging.log4j.core.appender.FileManager.writeToDestination(FileManager.java:272)
	- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
	at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.writeToDestination(RollingFileManager.java:236)
	- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
	at org.apache.logging.log4j.core.appender.OutputStreamManager.flushBuffer(OutputStreamManager.java:293)
	- locked <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
	at org.apache.logging.log4j.core.appender.OutputStreamManager.flush(OutputStreamManager.java:302)
	- locked <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
	at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:199)
	at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190)
	at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181)
	at org.apache.logging.log4j.core.appender.RollingFileAppender.append(RollingFileAppender.java:312)
	at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
	at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
	at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
	at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
	at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:543)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.callAppenders(AsyncLoggerConfig.java:127)
	at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:502)
	at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:485)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:121)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncLoggerConfigsOnCurrentThread(AsyncLoggerConfig.java:169)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:111)
	at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:97)
	at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:168)
	at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
	at java.lang.Thread.run(java.base@17-loom/Thread.java:1521)

log4j2 вызывался много разflush, JFR отображает собранные события при каждом вызовеflush. Каждый раз, когда вызывается flush, это приводит к тому, что собственный вызов фактически записывает файл. Причина медленного потребления на самом деле в том, что нативных вызовов слишком много, и система не может его записать.

задача решена

Мы можем решить эту проблему в следующих четырех направлениях:

  1. Сокращение вывода журнала и оптимизация журналов — это относительно базовый метод и относительно простой метод, но как технический специалист мы не можем быть удовлетворены этим.
  2. Увеличьте IO жесткого диска, это также относительно простое и простое решение.
  3. Можем ли мы уменьшить этот флеш? Ответ - да,Мы можем настроить AppenderimmediateFlushложно.
  4. Добавлен мониторинг содержания стекаorg.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueueСобытия блока монитора Java отслеживаются,При обнаружении длительного времени или большого количества событий сигнализировать или перестроить процесс

1. Настройте AppenderimmediateFlushложно

Мы можем настроить AppenderimmediateFlushложно,Например:

<RollingFile name="file" append="true"
             filePattern="./app.log-%d{yyyy.MM.dd.HH}"
             immediateFlush="false">
    <PatternLayout pattern="${logFormat}"/>
    <Policies>
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    </Policies>
    <DirectWriteRolloverStrategy maxFiles="72"/>
</RollingFile>

Принцип здесь соответствует исходному коду:

AbstractOutputStreamAppender.java

protected void directEncodeEvent(final LogEvent event) {
    getLayout().encode(event, manager);
    //如果配置了 immdiateFlush (默认为 true)或者当前事件是 EndOfBatch
    if (this.immediateFlush || event.isEndOfBatch()) {
        manager.flush();
    }
}

Итак, для асинхронного журнала Log4j2 Disruptor, когдаLogEventдаEndOfBatchШерстяная ткань? Когда потребляемый индекс равен наибольшему индексу, выпущенному производством, это также соответствует соображениям проектирования производительности, то есть, когда потребление не завершено, старайтесь не сбрасывать как можно больше, а затем сбрасывать, когда все ток потребляется:

BatchEventProcessor.java

private void processEvents()
{
    T event = null;
    long nextSequence = sequence.get() + 1L;

    while (true)
    {
        try
        {
            final long availableSequence = sequenceBarrier.waitFor(nextSequence);
            if (batchStartAware != null)
            {
                batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
            }

            while (nextSequence <= availableSequence)
            {
                event = dataProvider.get(nextSequence);
                //这里 nextSequence == availableSequence 就是 EndOfBatch
                eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
                nextSequence++;
            }

            sequence.set(availableSequence);
        }
        catch (final TimeoutException e)
        {
            notifyTimeout(sequence.get());
        }
        catch (final AlertException ex)
        {
            if (running.get() != RUNNING)
            {
                break;
            }
        }
        catch (final Throwable ex)
        {
            exceptionHandler.handleEventException(ex, nextSequence, event);
            sequence.set(nextSequence);
            nextSequence++;
        }
    }
}

2. Добавить мониторинг событий на основе JFR

Для этого требуется Java 14+

Configuration config = Configuration.getConfiguration("default");
//设置监控的锁 block 时间超过多少就会采集
config.getSettings().put("jdk.JavaMonitorEnter#threshold", "1s");
try (var es = new RecordingStream(config)) {
    es.onEvent("jdk.JavaMonitorEnter", recordedEvent -> {
        //如果堆栈包含我们关注的,则报警
        if (recordedEvent.getStackTrace().getFrames().stream().anyMatch(recordedFrame -> recordedFrame.toString().contains("org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue")))  {
            System.out.println("Alarm: " + recordedEvent);
        }
    });
    es.start();
}

Ищите «My Programming Meow» в WeChat, подписывайтесь на официальный аккаунт, чистите каждый день, легко улучшайте свои технологии и получайте различные предложения.: