Перепечатано: GC-пауза службы Go в контейнере часто превышает 100 мс.
Пауза GC службы Go в контейнере часто превышает 100 мс.
Оригинал yifhao yifhao 2019-11-08 22:03
Паузы GC часто превышают 100 мс
Феномен
Некоторые коллеги сообщили, что недавно начали опробовать k8s компании, и процесс go, развернутый в docker, имеет проблемы, интерфейс долго работает, есть таймауты, логика очень проста, достаточно вызвать хранилище kv, а общее время отклика хранилища kv составляет <5 мс, а объем очень мал, менее 40 запросов в секунду, контейнеру выделяется квота в 0,5 ядра, а ежедневно работающий ЦП имеет менее 0,1 ядра.
повторяющийся
Я нашел контейнер и отключил трафик доступа. Используйте ab 50 для создания нескольких запросов одновременно. Задержка в сети составляет 60 мс, но среднее время обработки занимает более 200 мс, а 99% занимает 679 мс.
При обработке с аб смотрел инфу по ЦП и памяти,проблем нет.Докер выделяет 0.5 ядра.Здесь не так много используется.
Глядя на мониторинг, GC STW (stop the world) превышает 10 мс, есть много 50-100 мс, и многие из них превышают 100 мс.Гу не утверждал, что паузы GC в основном меньше 1 мс после 1.8?
gc информация и трассировка
Глядя на информацию о времени выполнения процесса, обнаруживается, что памяти очень мало, gc-пауза очень велика, а GOMAXPROCS составляет 76, что соответствует количеству ядер машины.
export GODEBUG=gctrace=1, перезапустите процесс, чтобы увидеть.Видно, что пауза gc действительно серьезная.
-
gc 111 @97.209s 1%: 82+7.6+0.036 ms clock, 6297+0.66/6.0/0+2.7 ms cpu, 9->12->6 MB, 11 MB goal, 76 P
-
gc 112 @97.798s 1%: 0.040+93+0.14 ms clock, 3.0+0.55/7.1/0+10 ms cpu, 10->11->5 MB, 12 MB goal, 76 P
-
gc 113 @99.294s 1%: 0.041+298+100 ms clock, 3.1+0.34/181/0+7605 ms cpu, 10->13->6 MB, 11 MB goal, 76 P
-
gc 114 @100.892s 1%: 99+200+99 ms clock, 7597+0/5.6/0+7553 ms cpu, 11->13->6 MB, 13 MB goal, 76 P
Запустите трассировку службы на сервере с помощью go sdk, а затем загрузите файл трассировки локально, чтобы посмотреть
-
curl -o trace.out 'http://ip:port/debug/pprof/trace?seconds=20'
-
sz ./trace.out
На рисунке ниже показано, что время стенки GC составляет 172 мс, а две фазы STW этого gc, окончание развертки и завершение метки, занимают более 80 мс, почти занимая все время GC, что, конечно, очень ненаучно. .
Причина и решение
причина
Эта служба работает в контейнере, а контейнер и хост-компьютер совместно используют одно ядро. Количество ядер ЦП, видимых процессом в контейнере, также является количеством ядер ЦП хост-компьютера. Для приложений Go количество P (для GOMAXPROCS) будет установлено по умолчанию как Количество ядер ЦП. Мы также можем видеть из предыдущего рисунка, что GOMAXPROCS равно 76, и каждый используемый P имеет связанный с ним m, поэтому количество потоков также довольно большое. lot, а цифра выше — 171. Однако число, отведенное под CPU quota контейнера, на самом деле не так много, всего 0,5 ядра, а количество потоков довольно много.
Угадайте: для linux cfs (полностью честный планировщик) все потоки (облегченные процессы) в текущем контейнере находятся в группе планирования.Чтобы обеспечить эффективность, для каждой запущенной задачи, если она не заблокирована и т. д. Если причина переключена активно, то по крайней мере гарантируется время работы /proc/sys/kernel/schedminggranularity_ns, которое можно увидеть как 4 мс.
Процесс Go в контейнере неправильно устанавливает количество GOMAXPROCS, в результате получается слишком много исполняемых потоков, и может возникнуть проблема задержки планирования.Бывает, что после того, как поток, который входит в gc для инициации STW, останавливает другие потоки, это отключен планировщиком в течение длительного времени Поток не запланирован, что, по сути, приводит к тому, что время STW становится очень длинным (процесс обработки 0,1 мс при нормальных обстоятельствах становится уровнем 100 мс из-за задержки планирования).
Решение
Решение, поскольку существует слишком много P, которые могут быть запущены, займет виртуальное время работы потока, инициировавшего stw, а квота ЦП невелика.Тогда нам нужно сопоставить P с Квота процессора Мы можем выбрать:
-
Увеличьте квоту ЦП для контейнера.
-
Уровень контейнера позволяет процессам в контейнере видеть количество ядер ЦП как квоту.
-
Установите правильный GOMAXPROCS в соответствии с квотой
Первый способ: Особого эффекта не дает, меняем квоту с 0,5 на 1, принципиальной разницы нет (попробовал, проблема осталась).
Способ 2: Поскольку я не очень хорошо знаком с k8s, я добавлю его после того, как проведу исследование.
Третий способ: Самый простой способ настроить GOMAXPROCS — запустить скрипт для добавления переменных окружения
GOMAXPROCS=2 ./svr_bin Это эффективно, но есть и недостатки: если разворачивается контейнер с большей квотой, скрипт нельзя изменить соответствующим образом.
библиотека uber automaxprocs
У Убера есть библиотека go.uber.org/automaxprocs, при запуске процесса go в контейнере GOMAXPROCS будет установлен корректно Шаблон кода изменен, ссылка на эту библиотеку есть в go.mod
-
go.uber.org/automaxprocs v1.2.0
И импортировать в main.go
-
import (
-
_ "go.uber.org/automaxprocs"
-
)
Эффект
Советы по библиотеке automaxprocs
При использовании библиотеки automaxprocs будут следующие логи:
-
Для виртуальных машин или физических машин
В случае 8 ядер: 2019/11/07 17:29:47 maxprocs: оставить GOMAXPROCS=8: квота процессора не определена
-
Для контейнеров с более чем 1 основным набором квот
08.11.2019 19:30:50 maxprocs: Обновление GOMAXPROCS=8: определяется из квоты ЦП
-
Для контейнеров с квотой менее 1 ядра
08.11.2019 19:19:30 maxprocs: Обновление GOMAXPROCS=1: использование минимально допустимого GOMAXPROCS
-
Если в докере не задана квота
07.11.2019, 19:38:34 maxprocs: выход из GOMAXPROCS=79: квота процессора не определена
На этом этапе рекомендуется явно установить GOMAXPROCS в сценарии запуска.
время ответа запроса
После настройки используйте ab запрос для проверки еще раз.Сетевое время приема-передачи составляет 60 мс, и 99% запросов меньше 200 мс, что раньше было 600 мс.При том же потреблении процессора qps почти удвоился.
информация о времени выполнения и трассировке сборщика мусора
Поскольку выделено 0,5 ядра, GOMAXPROC распознает его как 1. gc-пауза также очень низкая, в виде десятков нас.В то же время видно, что количество потоков упало с более чем 170 до 11.
-
gc 97 @54.102s 1%: 0.017+3.3+0.003 ms clock, 0.017+0.51/0.80/0.75+0.003 ms cpu, 9->9->4 MB, 10 MB goal, 1 P
-
gc 98 @54.294s 1%: 0.020+5.9+0.003 ms clock, 0.020+0.51/1.6/0+0.003 ms cpu, 8->9->4 MB, 9 MB goal, 1 P
-
gc 99 @54.406s 1%: 0.011+4.4+0.003 ms clock, 0.011+0.62/1.2/0.17+0.003 ms cpu, 9->9->4 MB, 10 MB goal, 1 P
-
gc 100 @54.597s 1%: 0.009+5.6+0.002 ms clock, 0.009+0.69/1.4/0+0.002 ms cpu, 9->9->5 MB, 10 MB goal, 1 P
-
gc 101 @54.715s 1%: 0.026+2.7+0.004 ms clock, 0.026+0.42/0.35/1.4+0.004 ms cpu, 9->9->4 MB, 10 MB goal, 1 P
переключатель контекста
Ниже приведено сравнение результатов perf stat с параллелизмом 50 и общим числом обработанных запросов 8000. Число ядер ЦП по умолчанию — 76 P, переключение контекста происходит более 130 000 раз, а pidstat проверяет, что системный ЦП потребляет 9% ресурсов процессора. ядро После установки количества P в соответствии с номером квоты переключение контекста происходит только более 20 000 раз, а процессор потребляет 3% ядер.
Анализ принципа automaxprocs
Как эта библиотека устанавливает GOMAXPROCS в соответствии с квотой?Код немного запутанный.Прочитав его,принцип не сложен.Докер использует cgroup для ограничения загрузки ЦП контейнера,а квоту ЦП можно получить с помощью cpu.cfsquotaus/cpu.cfsperiodus настроен контейнером, Итак, ключ в том, чтобы найти эти два значения для контейнера.
Получить информацию о монтировании cgroup
кошка /proc/self/mountinfo
-
....
-
1070 1060 0:17 / /sys/fs/cgroup ro,nosuid,nodev,noexec - tmpfs tmpfs ro,mode=755
-
1074 1070 0:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
-
1075 1070 0:22 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
-
1076 1070 0:23 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
-
1077 1070 0:24 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
-
1078 1070 0:25 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct,cpu
-
1079 1070 0:26 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
-
1081 1070 0:27 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls
-
....
cpuacct, cpu находится в каталоге /sys/fs/cgroup/cpu, cpuacct.
Получить подкаталог cgroup контейнера
кошка /proc/self/cgroup
-
10:net_cls:/kubepods/burstable/pod62f81b5d-xxxx/xxxx92521d65bff8
-
9:cpuset:/kubepods/burstable/pod62f81b5d-xxxx/xxxx92521d65bff8
-
8:cpuacct,cpu:/kubepods/burstable/pod62f81b5d-xxxx/xxxx92521d65bff8
-
7:hugetlb:/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8
-
6:blkio:/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8
-
5:devices:/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8
-
4:memory:/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8
-
....
cpuacct и cpu контейнера находятся в подкаталоге /kubepods/burstable/pod62f81b5d-xxxx/xxxx92521d65bff8.
рассчитать квоту
-
cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8/cpu.cfs_quota_us
-
50000
-
cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod62f81b5d-5ce0-xxxx/xxxx92521d65bff8/cpu.cfs_period_us
-
100000
Эти два делятся, чтобы получить 0,5, если оно меньше 1, GOMAXPROCS устанавливается равным 1, а если больше 1, ему присваивается рассчитанное число.
основная функция
Основные функции в библиотеке automaxprocs следующие, где cg — все пути конфигурации анализируемой контрольной группы.Прочитайте cpu.cfs_quota_us и cpu.cfs_period_us соответственно, а затем вычислите.
официальный выпуск
Я гуглил, и кто-то задавал этот вопрос
время выполнения: длительные паузы GC STW (≥80 мс) #19378 https://github.com/golang/go/issues/19378
Подведем итог
-
Количество ядер, видимых процессом в контейнере, — это количество ядер ЦП родительской машины.Как правило, это значение больше 32, что приводит к тому, что процесс go устанавливает для P большее число и открывает множество P и потоков.
-
Как правило, квота контейнера не велика, 0,5-4. Планировщик linux принимает контейнер как группу, и планирование потоков внутри справедливо, и каждый исполняемый поток будет гарантировать определенное время работы, потому что их много. потоки и квота мала, хотя количество запросов небольшое, но есть много переключений контекста, это также может вызвать задержку в планировании потока, который инициирует stw, в результате чего время stw возрастет до уровня 100 мс, что сильно влияет на запрос
-
Используя библиотеку automaxprocs, количество GOMAXPROCS и P может быть правильно установлено в соответствии с квотой процессора, выделенной для контейнера, а количество потоков может быть уменьшено, чтобы пауза GC была стабильной на уровне <1 мс. потребление, количество запросов в секунду может быть удвоено, а среднее время отклика уменьшено с 200 мс до 100 мс, переключение контекста потока уменьшено до 1/6 от исходного
-
При этом также кратко анализируется принцип работы библиотеки: находим каталог cgroup контейнера, вычисляем cpuacct, cpu.cfs_quota_us/cpu.cfs_period_us под cpu, что является количеством выделенных процессорных ядер.
-
Конечно, если процесс в контейнере увидит, что количество ядер процессора соответствует выделенной квоте, то и эту проблему можно решить, я мало что знаю об этом аспекте.
Статья была изменена 2019-11-08
Уведомление:
Квота ЦП контейнера должна быть настроена, а не по умолчанию