Connect with us

Разработка

Корутины Kotlin в продакшене: уроки и подводные камни

Главное — не только понять, как использовать корутины, но и когда они являются правильным инструментом для работы.

Опубликовано

/

     
     

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

Скрытые издержки переключения контекста

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

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

Перенасыщение пула потоков: тихий убийца производительности

Произошел инцидент, когда мы неосознанно перенасытили наши пулы потоков. Первопричина? Этот обманчивый шаблон:

Этот код создает новую корутину для каждого заказа, потенциально запуская тысячи корутин, которые конкурируют за ограниченный пул потоков ввода-вывода. Мы обнаружили это, когда наше приложение стало демонстрировать высокую задержку в часы пик. Для исправления ситуации потребовалось реализовать правильное обратное давление (backpressure):

Утечки памяти через отношения «родитель-ребенок

Особенно раздражающая проблема, с которой мы столкнулись, была связана с утечками памяти из-за неправильного понимания наследования скоупа корутины. Этот код был в нашей системе:

В чем проблема? Если OrderProcessor уничтожается, его корутины продолжают выполняться. Мы накапливали сотни зомби-корутин, обрабатывающих уже отмененные заказы. Решение заключалось в правильной обработке жизненного цикла:

Кошмар отладки и способы решения

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

Когда мы только начали отлаживать проблемы с корутинами, мы обратили внимание на Threads Monitor. Для тех, кто работает с традиционными Java-приложениями, монитор может показаться пугающим. Здесь много потоков с таймером ожидания (DefaultDispatcher-worker-x).

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

Корутины Kotlin в продакшене: уроки и подводные камни

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

Простое наблюдение за потоками не говорит вам обо всем. В фоновом режиме может происходить множество вещей. В связи с этим мы можем вывести некоторые дополнительные метрики, добавив MBean.

Корутины Kotlinx сами по себе не предоставляют MBean, однако мы можем реализовать простой MBean, который предоставит нам несколько метрик:

  • Количество активных корутинов
  • Количество приостановленных корутин
  • Какие корутины существуют и какой диспетчер они используют

Имейте в виду, что этот MBean следует использовать только для отладки, код, показанный ниже, также сопровождается снижением производительности, о чем можно прочитать в документации.

Сначала нам нужен интерфейс, который сообщит JMX, какие функции будут открыты:

Реализация MBean требует от нас добавления дополнительного пакета: org.jetbrains.kotlinx:kotlinx-coroutines-debug. После его добавления мы получим доступ к классу DebugProbes, который позволит нам дампить информацию о существующих корутинах.

После этого вы можете зарегистрировать MBean в своем приложении следующим образом:

В VisualVM подключитесь к своему приложению, перейдите на вкладку MBeans и найдите com.enapi.CoroutineMonitor, который должен выглядеть следующим образом:

Корутины Kotlin в продакшене: уроки и подводные камни

Есть и третий метод, который менее удобен для работы, но может помочь вам найти проблемы в вашем приложении. Это Java-агент, который можно загрузить и использовать следующим образом: java -jar your-application.jar -javaagent:kotlinx-coroutines-debug.jar. Это позволит включить отладочные зонды, как мы это делали в нашем MBean. Включение отладочных зондов позволяет вести дополнительное логирование. Кроме того, в Linux и MacOS вы можете kill -5 $pid, чтобы заставить ваше приложение вывести все активные корутины. Однако это не то, что вы можете сделать в производственной среде, и мы сами этим не пользовались.

Когда корутины — неправильный выбор

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

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

Простые последовательные операции: не все должно быть асинхронным. Мы обнаружили, что чрезмерно усложняем простые операции:

Успешное решение: параллельная обработка данных

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

Заключение

Корутины Kotlin — это мощный инструмент, но он сопряжен со скрытыми сложностями. Для успешного применения в производстве необходимо уделять пристальное внимание управлению пулом потоков, правильной обработке скоупа и соответствующими случаями использования. Внимательно следите за своими корутинами, реализуйте надлежащий backpressure и не бойтесь использовать традиционные подходы к работе с потоками, если они имеют больше смысла. Самое главное — всегда тестируйте код, основанный на корутинах, под нагрузкой перед развертыванием в производстве.

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

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: