JVM в контейнере неправильно вычисляет процессоры? - PullRequest
3 голосов
/ 09 января 2020

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

Итак, широко известно, что JVM долгое время игнорировал ограничения памяти, применяемые к контрольная группа. Почти так же широко известно, что теперь он учитывает их, начиная с Java 8, обновляя что-то и 9 и выше. К сожалению, вычисления, сделанные на основе ограничений cgroup, настолько бесполезны, что вам все равно придется делать все вручную. См. Google и сотни статей по этому вопросу.

Что я обнаружил всего за несколько дней go и не читал ни в одной из этих статей, так это то, как JVM проверяет число процессоров в cgroups. Количество процессоров используется для определения количества потоков, используемых для различных задач, включая сборку мусора. Поэтому очень важно получить его правильно.

В группе (насколько я понимаю, и я не эксперт) вы можете установить ограничение на доступное время процессора (параметр --cpus Docker). Это ограничивает только время, а не параллельность. Существуют также доли процессора (параметр --cpu-shares Docker), которые представляют собой относительный вес для распределения времени процессора под нагрузкой. Docker устанавливает значение по умолчанию 1024, но это чисто относительный масштаб.

Наконец, существуют наборы процессоров (--cpuset-cpus для Docker) для явного назначения cgroup, и такой контейнер Docker на подмножество процессоров. Это не зависит от других параметров и фактически влияет на параллелизм.

Итак, когда речь идет о проверке количества потоков, которые мой контейнер может выполнять параллельно, насколько я могу судить, релевантен только набор процессоров. , JVM, тем не менее, игнорирует это, вместо этого используя предел ЦП, если он установлен, в противном случае ЦП делится (предполагая, что значение по умолчанию 1024 является абсолютной шкалой). Это ИМХО уже очень неправильно. Он вычисляет доступное время процессора для определения размера пулов потоков.

В Кубернетах становится все хуже. Лучшая практика AFAIK - не устанавливать лимит ЦП, чтобы узлы кластера имели высокую загрузку. Кроме того, для большинства приложений следует установить низкий запрос ЦП, поскольку они будут простаивать большую часть времени, и вы хотите запланировать множество приложений на одном узле. Kubernetes устанавливает запрос в миллипроцессорах как долю процессора, которая, скорее всего, ниже 1000м. В этом случае JVM всегда использует один процессор, даже если ваш узел работает на каком-то 64-ядерном процессоре.

Кто-нибудь когда-нибудь также наблюдал это? Я что-то здесь упускаю? Или разработчики JVM на самом деле усугубили ситуацию при реализации ограничений cgroup для процессора?

Для справки:

1 Ответ

2 голосов
/ 13 января 2020

Являясь разработчиком крупномасштабной службы (> 15K контейнеров, выполняющих распределенные Java приложения в собственном облаке), я также признаю, что так называемая "Java поддержка контейнеров" слишком далека от совершенства. В то же время я могу понять аргументы разработчиков JVM, которые внедрили текущий алгоритм обнаружения ресурсов.

Проблема в том, что существует так много разных облачных сред и вариантов использования для запуска контейнерных приложений, что практически невозможно для решения всего многообразия конфигураций. То, что вы называете «лучшей практикой» для большинства приложений в Kubernetes, не обязательно типично для других развертываний. Например, это определенно не обычный случай для нашего сервиса, где большинству контейнеров требуется определенный минимальный гарантированный объем ресурсов ЦП, и, следовательно, также есть квота, которую они не могут превышать, чтобы гарантировать ЦП для других контейнеров. Эта политика хорошо работает для задач с низкой задержкой. OTOH, политика, которую вы описали, лучше подходит для высокопроизводительных или пакетных задач.

Целью текущей реализации в HotSpot JVM является поддержка популярных облачных сред из коробки и предоставление механизма для переопределения значений по умолчанию.

Существует ветка электронной почты , где Боб Вандетт объясняет текущий выбор. Существует также комментарий в исходном коде, объясняющий, почему JVM смотрит на cpu.shares и делит его на 1024.

/*
 * PER_CPU_SHARES has been set to 1024 because CPU shares' quota
 * is commonly used in cloud frameworks like Kubernetes[1],
 * AWS[2] and Mesos[3] in a similar way. They spawn containers with
 * --cpu-shares option values scaled by PER_CPU_SHARES. Thus, we do
 * the inverse for determining the number of possible available
 * CPUs to the JVM inside a container. See JDK-8216366.
 *
 * [1] https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
 *     In particular:
 *        When using Docker:
 *          The spec.containers[].resources.requests.cpu is converted to its core value, which is potentially
 *          fractional, and multiplied by 1024. The greater of this number or 2 is used as the value of the
 *          --cpu-shares flag in the docker run command.
 * [2] https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
 * [3] https://github.com/apache/mesos/blob/3478e344fb77d931f6122980c6e94cd3913c441d/src/docker/docker.cpp#L648
 *     https://github.com/apache/mesos/blob/3478e344fb77d931f6122980c6e94cd3913c441d/src/slave/containerizer/mesos/isolators/cgroups/constants.hpp#L30
 */

Что касается параллелизма, я также второй разработчик HotSpot, который JVM следует учитывать cpu.quota и cpu.shares при оценке количества доступных процессоров. Когда контейнеру присваивается определенное количество vcores (в любом случае), он может полагаться только на это количество ресурсов, поскольку нет никакой гарантии, что процессу будет доступно больше ресурсов. Рассмотрим контейнер с 4 ядрами, работающими на 64-ядерном компьютере. Любая ресурсоемкая задача (например, G C), выполняемая в 64 параллельных потоках, быстро исчерпает квоту, а ОС будет дросселировать контейнер в течение длительного периода. Например, каждые 94 из 100 мс приложение будет находиться в состоянии паузы "остановка мира", поскольку период по умолчанию для учетной квоты (cpu.cfs_period_us) равен 100 мс.

В любом случае, если алгоритм не работает хорошо в вашем конкретном случае всегда можно переопределить количество доступных процессоров с помощью опции -XX:ActiveProcessorCount или полностью отключить осведомленность о контейнере с помощью -XX:-UseContainerSupport.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...