🤔ТАК ЛИ GOLANG ХОРОШ В HIGHLOAD?
Golang обладает многими преимуществами для разработки высокопроизводительных, многопоточных программ. Именно за свои простоту и эффективность Go занял свое почетное место среди самых популярных языков для разработки микросервисов.
Однако при разработке высоконагруженных приложений на Go, в которых очень важны минимальные задержки, отзывчивость , могут возникнуть неожиданные проблемы…
Проблема 1. Непонятные задержки при большом количестве горутин.
Go использует ограниченное число потоков (М) для выполнения множества горутин (G), и увеличение числа горутин приводит к увеличению накладных расходов: балансировка горутин (G) между потоками (M), блокировки в глобальной очереди горутин, переключение контекста в потоке и т.д.
Также планировщик в Go не гарантирует четкого порядка исполнения горутин. Это означает, что каждый раз, когда мы создаем горутину, блокируемся где-то на мьютексе, системном вызове или канале, она идет «парковаться». Через какое время горутине посчастливится вновь исполняться дальше на потоке — мы не знаем… Чем больше горутин, тем выше шанс, что какой-то из них (а может нескольким) сильно не повезет и она будет очень долго ждать своего часа… Все это замедляет выполнение пользовательского запроса в нашем сервисе, особенно в 99 перцентиле.
Проблема 2. Сборщик мусора.
Go - язык со сборщиком мусора (GC) и его тюнинг может стать тем еще испытанием.
Частые паузы на сбор мусора (Stop The World) создают задержки, что непременно усугубляет проблемы с производительностью. Но это не главная проблема (операции STW довольны быстрые и не заметные в последних версиях Go). Главная боль - это сканирование heap-а за счет Mark Assist горутин- специальные горутины, которые начинают исполняться на потоках вместо наших горутин. Таким образом приложение по сути испытывает голодание потоков, потому что они заняты «полезной работой» по разметке объектов в куче. Когда сборщик мусора работает часто, наше приложение работает не на полную мощность и это все также ведет к замедлению обработки пользовательских запросов…
Как понять, что вы столкнулись с этими проблемами?
Вот метрики, за которыми точно нужно следить и подскажут вам, что что-то идет не так:
- Количество горутин. Тут нет конкретной универсальной цифры. Для каждого приложения есть свой определенный порог, после которого необходимо увеличивать количество инстансов вашего приложения.
- Время ожидания горутины на исполнение. Тут все просто: если 0.99q >= 1ms, стоит напрячься: добавить ресурсов приложению, увеличить количество потоков, увеличить число инстансов приложения, оптимизировать сборку мусора.
- Количество вызовов сборки мусора за минуту. В идеале этот показатель стоит держать в диапазоне 1-10 раз в минуту (не для всех типов приложений такое число актуально). Если чаще, стоит начать оптимизировать ваш код, и стараться добиться уменьшения количества вызовов GC.
В своем старом докладе рассказывал про работу сборщика мусора в Go и варианты его оптимизации.
Post #66
677