Интервьюер: Поговорим о дизайне «сердцебиения»?

Java задняя часть
Интервьюер: Поговорим о дизайне «сердцебиения»?

Здравствуйте, я криворукий.

Правильно, я недавно видел эту статью «Решение для сценария распределенной службы ICBC C10K».

Почему снова?

Потому что я читал эту статью, когда она была впервые опубликована, и я думал, что она была хорошо написана в то время, и линия вселенной (ICBC) действительно выглядела много.

Но я видел это и видел, и я не думал об этом в то время.

Когда я увидел его на этот раз, он как раз возвращался с работы, поэтому я снова внимательно посмотрел на него.

Что ж, читать часто и часто новое все равно очень полезно.

Поэтому я написал статью, чтобы сообщить вам, что я узнал, прочитав ее еще раз.

Краткое содержание статьи

Я знаю, что многие студенты не читали эту статью, поэтому сначала поставлю ссылку,«Решение для сценария распределенного обслуживания ICBC C10K».

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

Содержание статьи, наверное, такое.

В архитектуре вселенной, с развитием бизнеса, в обозримом будущем возникнет сценарий, когда провайдер предоставляет услуги тысячам, а то и десяткам тысяч потребителей.

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

Проблема C10K обсуждаться не будет, посмотрите в интернете, это очень известная программная проблема, но эта проблема уже стала историей.

Фреймворк RPC линейки юниверсов использует Dubbo, поэтому их статья основана на этой проблеме:

Может ли распределенная сервисная платформа на основе Dubbo обрабатывать сложные сценарии C10K?

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

Во-первых, они используют версию Dubbo 2.5.9. Версия действительно низковата, но для банков все, кто ее знает, понимают, что апгрейд архитектуры можно сделать без переезда, а стабильная работа — это королевский путь.

В этой версии они построили сервер.Логика сервера заключается в том, чтобы спать в течение 100 мс, имитировать бизнес-звонки и развертывать его на сервере 8C16G.

Соответствующий потребитель настраивает время тайм-аута сервиса на 5с, а затем разворачивает потребитель на сотнях серверов 8C16G (я молодец, сотни серверов 8C16G, это все деньги на ветер, хорошо когда есть деньги) для развертывания 7000 сервиса потребителей контейнерным способом.

После запуска каждого потребителя сервис вызывается 1 раз в минуту.

Затем они настроили два тестовых сценария:

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

Но Сценарий 1 не должен быть.

Вы думаете, что тайм-аут, настроенный потребителем, составляет 5 с, а бизнес-логика провайдера обрабатывает только 100 мс. В любом случае, времени достаточно.

Одно дополнительное примечание: в этой статье также рассматривается Сценарий 1.

Но, друзья, но ах.

Хотя частота отправки запроса звонящим раз в минуту невелика, звонящих 7000. Эти 7000 звонящих — легендарный всплеск трафика, но этот «всплеск» — раз в минуту.

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

Вы можете написать небольшой пример, чтобы проиллюстрировать это, например:

Это создание пула потоков, а количество потоков равно 200. Затем отправьте 7000 задач, каждая задача занимает 100 мс, используйте CountDownLatch для имитации параллелизма, и для запуска на моей 12-ядерной машине требуется 3,8 с.

То есть, если в сценарии Dubbo каждый запрос добавляет немного времени передачи по сети, немного внутреннего потребления фреймворка, и это немного времени умножается на 7000, то последняя задача, которую нужно выполнить, это теоретически может превышать 5 с.

Так что случайный тайм-аут понятен.

Но, друзья, вот опять.

То, что я сказал ранее, является теоретическим, но практика — единственный способ проверить истину.

Взгляните на результаты проверки строки юниверса:

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

После анализа захвата пакетов они пришли к выводу:Причина тайм-аута транзакции находится не на стороне потребителя, а на стороне поставщика.

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

На самом деле, мы можем в основном подтвердить здесь, что некоторые операции в среде Dubbo должны были вызвать увеличение времени.

Трудность заключается в том, чтобы определить, что именно представляет собой операция?

Через серию операций, после тщательного анализа, вселенская линия пришла к выводу:

Интенсивные тактовые импульсы вызывают занятость рабочих потоков netty, что приводит к увеличению времени транзакций.

Именно об этом говорится в заключении:

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

Как упоминалось ранее, эта статья посвящена только Сценарию 1, поэтому давайте посмотрим на решение, данное для строки юниверса Сценария 1:

Все оптимизировано под сердцебиение.Эффект после обработки следующий:

Одной из самых значительных операций является «сериализация обхода сердцебиения».

Средняя разница во времени обработки между потребителем и поставщиком сократилась с 27 мс до 3 мс, увеличившись на 89%.

Время транзакций 99% лучших сократилось со 191 мс до 133 мс, увеличившись на 30%.

Что ж, написание этого является почти пересказом некоторых вещей, которые я видел в той статье в свое время, что не очень питательно.

Просто я помню, когда впервые увидел эту статью, я был таким:

Я думаю, что это довольно круто Небольшое сердцебиение превратилось в угрозу производительности в сцене C10K.

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

но...

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

Как сердцебиение обходит сериализацию

Как мне заниматься исследованиями?

Перейти непосредственно к исходному коду?

Да, это бросаться в исходный код.

Но прежде чем спешить, я зашел на гитхаб Дабба:

GitHub.com/Apache/Belly Daddy…

Затем я сначала искал «Heartbeat» в запросе на слияние, и этот поиск также нашел много хорошего:

Когда я увидел этих двух персов с первого взгляда, у меня загорелись глаза.

Хороший парень, я просто хотел взглянуть на это, но я не ожидал, что сразу найду то, что хочу изучить.

Мне нужно только взглянуть на эти два prs, чтобы узнать, как добиться «сериализации обхода сердцебиения», что напрямую избавляет меня от многих обходных путей.

Сначала посмотрите на это:

GitHub.com/Apache/Belly Daddy…

Как видно из этого описания, я нашел нужное место. Из его описания мы знаем, что «сердцебиение пропускает сериализацию», то есть процесс сериализации заменяется нулевым.

В то же время этот PR также объясняет свои собственные идеи трансформации:

Далее, давайте взглянем на код, отправленный на этот раз.

Что вы думаете?

Вы можете увидеть его файл, соответствующий этому коммиту, на git:

Вы можете найти соответствующее место в исходном коде, что также является способом поиска исходного кода.

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

Начать с его git на самом деле хороший угол.

Небольшой трюк для чтения исходного кода для вас.

Неважно, если вы не знакомы с инфраструктурой Dubbo, мы просто сосредоточимся на том, «как сердцебиение пропускает сериализацию». Что же касается того, кто, как и когда инициирует сердцебиение, то в этом разделе пока об этом говорить не будем.

Далее, давайте начнем с этого класса:

org.apache.dubbo.rpc.protocol.dubbo.DubboCodec

Из записи о представлении видно, что есть два основных изменения, и код двух изменений абсолютно одинаков, оба расположены в методе decodeBody, только одно в ветке if и одно в ветке else:

Что делает этот код?

Если вы хотите вызов RPC, он должен включать кодирование (кодирование) и декодирование (декодирование) сообщения, поэтому здесь главное — декодировать сообщения запроса и ответа.

Одно сердцебиение, одно туда и обратно, один запрос, один ответ, так что есть два изменения.

Итак, я покажу вам обработку пакета запроса здесь:

Можно видеть, что после модификации кода пакет сердцебиения подвергается специальной оценке.

Есть два метода, задействованных в специальной обработке событий пульса, оба из которых являются недавно добавленными методами в этом представлении.

Первый метод таков:

org.apache.dubbo.remoting.transport.CodecSupport#getPayload

Он заключается в преобразовании потока InputStream в массив байтов, а затем передаче массива байтов в качестве входного параметра второму методу.

Второй метод таков:

org.apache.dubbo.remoting.transport.CodecSupport#isHeartBeat

Из названия метода также известно, что он предназначен для определения того, является ли запрос пакетом пульса.

Как определить, что это пакет сердцебиения?

Прежде всего, мы должны посмотреть на место, где инициируется сердцебиение:

org.apache.dubbo.remoting.exchange.support.header.HeartbeatTimerTask#doTask

Откуда инициируется сердцебиение, мы можем знать, что то, что оно отправляет, равно нулю.

Поэтому в месте приема пакета оценивается, является ли его содержимое нулевым, если да, то это означает, что это пакет heartbeat.

Благодаря этим двум простым методам операция сериализации с пропуском пульса завершается, что повышает производительность.

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

org.apache.dubbo.remoting.transport.CodecSupport

В этом классе есть две маленькие детали, на которые вы можете взглянуть.

Сначала здесь:

На этой карте кэшируется нуль, соответствующий различным методам сериализации.Что делает код, так это то, что автор сказал здесь:

Еще одна деталь — посмотреть запись коммита этого класса:

Также есть коммит оптимизации, и на этот раз контент такой.

Сначала определите ThreadLocal и инициализируйте его 1024 байтами:

Так где же используется этот ThreadLocal?

При чтении InputStream нужно открывать байтовый массив, во избежание частого создания и уничтожения этих байтовых данных создается ThreadLocal:

Некоторые студенты спросят, увидев это: почему этот ThreadLocal не вызывает метод удаления, не будет ли утечки памяти?

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

Именно потому, что его можно использовать повторно, производительность повышается.

Это детали, а дьявол кроется в деталях.

Эта деталь - еще один pr, упомянутый ранее:

GitHub.com/Apache/Belly Daddy…

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

Но, друзья, но снова.

Когда я пишу это, я вдруг чувствую, что что-то не так.

Поскольку я написал эту статью раньше,Протокол Dubbo немного запутан.

В этой статье есть такая картинка:

Это было взято с официального сайта того времени.

В протоколе перед полем идентификатора события стоят только 0 и 1.

Но теперь все по-другому.С точки зрения кода область действия 1 расширена.Он не обязательно представляет сердцебиение, потому что в нем есть if-else.

Итак, сейчас я взглянул на описание протокола на официальном сайте.

Хироши Ватанабэ.apache.org/this/docs/v3. …

Конечно, что-то изменилось:

Это не значит, что 1 — это пакет пульса, но он изменен на: 1 может быть пакетом пульса.

Строгость, это строгость.

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

Разнообразие дизайнов для сердцебиения

Исследуя сердцебиение Даббо, я тоже нашел такой пр.

GitHub.com/Apache/Belly Daddy…

Название такое:

В переводе предлагается использовать IdleStateHandler вместо использования Timer для отправки тактов.

Присмотрелся, хорошая возможность, это не старый Сю пост-95-х, старый знакомый.

Взгляните на то, что сказал Лао Сюй Он предложил следующее:

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

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

Первая представляет собой битву между несколькими братьями в режиме реального времени.

Короче говоря, всем известно, что обнаружение сердцебиения Dubbo имеет определенную задержку, потому что оно основано на колесе времени, что эквивалентно заданию по времени, а своевременность срабатывания не может гарантировать срабатывание в реальном времени.

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

Это должен понять каждый.

Кроме того, результатом приведенного выше обсуждения является «в настоящее время 1/4 задержки пульса», но я посмотрел исходный код последней основной ветки, каково это — 1/3 задержки:

Как видно из исходного кода, параметр HEARTBEAT_CHECK_TICK равен 3 при расчете времени. Я так понимаю это 1/3 задержки.

Но не беда, не беда, ты же знаешь, что задержка все равно есть.

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

Это оптимизация в режиме реального времени.

И Лао Сюй почувствовал, что, помимо учета реального времени, IdleStateHandler на самом деле является элегантным дизайном для сердечных сокращений. Однако, поскольку он основан на Netty, когда коммуникационная структура не использует Netty, он бессилен, поэтому дизайн Timer можно зарезервировать для решения этой ситуации.

Вскоре брат Carryxyh дал очень конструктивный комментарий:

Потому что Dubbo поддерживает несколько коммуникационных фреймворков.

Упомянутое здесь «множественное» на самом деле я забыл упомянуть.Помимо Netty, он также поддерживает Girzzly и Mina, два базовых коммуникационных фреймворка, а также поддерживает настройку.

Но я думаю, что это 2021 год, Гирзли и Мина все еще используются?

Мы также можем найти их тени из исходного кода:

org.apache.dubbo.remoting.transport.AbstractEndpoint

У Гирзли, Мины и Нетти есть свой сервер и клиент.

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

Но как бы она ни менялась, ее по-прежнему зовут Нетти.

Что ж, вернемся к предыдущим конструктивным комментариям.

Если для выполнения пульса используется метод IdleStateHandler, а другие коммуникационные фреймворки поддерживают режим Timer, то должен появиться код, похожий на этот:

if transport == netty {
     don't start heartbeat timer
}

Это то, чего не должно быть в среде с открытым исходным кодом, потому что это увеличивает сложность кода.

Поэтому его предложение состоит в том, что лучше всего использовать тот же метод для обнаружения сердцебиения, то есть использовать режим Таймера.

Как раз тогда, когда я подумал, что слова этого приятеля имеют смысл, я прочитал ответ Лао Сюя и сразу же почувствовал, что то, что он сказал, имеет смысл:

Я не думаю, что мне нужно объяснять вышесказанное, вы можете просто прочитать и подумать.

Тогда посмотрите на точку зрения брата Carryxyh:

Вот и получается обратное.

Точка зрения Лао Сюя состоит в том, что сердцебиение должно быть, но он считает, что методы реализации различных коммуникационных фреймворков не обязательно должны быть согласованными (теперь все они основаны на колесе времени Таймера), и он не считает, что Таймер абстрагируется в единую концепцию, а реализация поддержания активности соединения — это элегантный дизайн.

В Dubbo мы в основном используем Netty, и механизм Netty IdleStateHandler, естественно, используется для сердцебиения.

Поэтому лично я считаю, что именно он считает использование IdleStateHandler более элегантным способом реализации, а второй — улучшение своевременности.

Но брат Carryxyh считает, что таймер, абстрагированный с помощью Timer, является очень хорошей конструкцией.Из-за его существования нам все равно, является ли нижний слой netty или mina, а нужно заботиться только о конкретной реализации.

Что касается решения IdleStateHandler, то он по-прежнему считает, что оно имеет преимущество в своевременности. Но я лично думаю, что его идея заключается в том, что если действительно есть преимущество, мы можем обратиться к его реализации и наделить другие коммуникационные фреймворки функцией «Idle», чтобы мы могли добиться большой унификации.

Глядя на это, я думаю, что смысл битвы между этими двумя братьями вот в чем.

Первая предпосылка заключается в том, что все вращается вокруг функции «сердцебиения».

Считается, что при использовании Netty существует лучшая реализация «сердцебиения», а Netty является основной коммуникационной структурой Dubbo, поэтому должна быть возможность просто изменить реализацию Netty.

Схема реализации, которая думает, что "пульс" должна быть унифицирована. Если схема Netty IdleStateHandler является хорошей схемой, мы должны перенести эту схему.

Я думаю, что все это имеет смысл, но какое-то время я не знаю, за кого голосовать.

Но, в конце концов, я решил проголосовать за Лао Сюй после прочтения этой статьи, написанной им:«Одно сердцебиение, два дизайна».

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

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

Затем эта фраза:

Лао Сюй занимается миддлваром в Али.Оказывается, люди, которые занимаются мидлваром, думают об этих вещах каждый день.

интересный.

посмотри на код

Я покажу вам код, но не буду делать детальный анализ.Это равносильно указанию пути.Если вы хотите узнать больше, то можете сами перейти к исходникам.

Сначала здесь:

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeClient

Вы можете видеть, что метод startHeartBeatTask вызывается в конструкторе HeaderExchangeClient для запуска пульса.

В то же время в нем есть HashedWheelTimer, я знаком с этой штукой, колесом времени, которое было проанализировано ранее.

Затем смотрим на метод startHeartBeatTask:

Это построить задачу сердцебиения, а затем бросить ее в колесо времени для запуска, здесь нет сложной логики.

Эта реализация является обработкой сердцебиения Dubbo по умолчанию.

Однако следует отметить, что весь метод обернут суждением if, которое имеет много предыстории. Имя называется canHandleIdle, то есть можно ли обработать операцию бездействия. По умолчанию установлено значение false:

Таким образом, результат предыдущего, если суждение является истинным.

Итак, когда canHandleIdle может быть истинным?

Правда при использовании Netty4.

То есть Netty4 не следует реализации сердцебиения по умолчанию.

Так как же это достигается?

Поскольку идеи сервера и клиента одинаковы, мы можем просто посмотреть на код клиента.

Взгляните на его метод doOpen:

org.apache.dubbo.remoting.transport.netty4.NettyClient#doOpen

В конвейер добавлено событие IdleStateHandler, о котором мы упоминали ранее.Это событие состоит в том, что если в течение миллисекунд HeartbeatInterval не происходит события чтения или записи, будет запущен метод, эквивалентный обратному вызову.

HeartbeatInterval по умолчанию равен 6000, что составляет 60 секунд.

Затем добавил nettyClientHandler, что он делает?

Взгляните на этот метод:

org.apache.dubbo.remoting.transport.netty4.NettyClientHandler#userEventTriggered

Этот метод отправляет события пульса.

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

Судя по текущему коду, Dubbo наконец принял предложение Лао Сюй, но реализация по умолчанию не изменилась, но в Netty4 используется механизм IdleStateHandler.

В данном случае я нахожу это еще более странным.

То же самое с Netty, один использует колесо времени, а другой использует IdleStateHandler.

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

Однако в процессе просмотра исходного кода я обнаружил небольшую проблему в коде.

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decode(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.buffer.ChannelBuffer, int, byte[])

В приведенном выше методе есть две строки кода:

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

Вы можете видеть, что оба метода выполняют эту логику:

int payload = getPayload(channel);
boolean overPayload = isOverPayload(payload, size);

Если finishRespWhenOverPayload не возвращает null, то и говорить не о чем, он возвращает return, и метод checkPayload выполняться не будет.

Если finishRespWhenOverPayload возвращает значение null, будет выполнен метод checkPayload.

В это время снова будет выполняться операция проверки размера пакета, не повторяется ли это?

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

Вы понимаете, что я имею в виду?

Еще одна возможность внести исходный код в Dubbo, для вас вы можете поднять волну.

Наконец, вот несколько ссылок для вас.

Во-первых, нужно узнать о механизме сердцебиения SOFA-RPC. SOFA-PRC также является платформой с открытым исходным кодом от Alibaba.

Реализация пульса полностью основана на IdleStateHandler.

Вы можете взглянуть на эти две официальные статьи:

У-у-у. Итак, отправьте стопку. Специальность /поиск/? Просканировал...

Второй — Geek Time «Изучение микросервисов с нуля» В 17-й лекции преподаватель немного поделился про сердцебиение и упомянул механизм защиты, о котором я раньше не догадывался:

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

Хорошо, поехали.

Эта статья размещена в личном блоге, играть могут все желающие.

www.whywhy.vip/