《Dubbo Series》 - процесс вызова службы Dubbo.

Java Dubbo
《Dubbo Series》 - процесс вызова службы Dubbo.

Ставьте лайк и смотрите снова, формируйте привычку и ищите в WeChat [Третий принц Ао Бин] Подпишитесь на этого программиста, который любит писать о чувствах.

эта статьяGitHub github.com/JavaFamilyВключено, и есть полные тестовые площадки, материалы и мой цикл статей для интервью с производителями первой линии.

предисловие

Я уже провел вас через два процесса предоставления услуги и введения службы, и эти два процесса предназначены для вызова службы.Сегодня C проведет вас через процесс вызова службы Dubbo.

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

Без лишних слов, давайте сразу к делу!

Просто подумайте об общем процессе

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

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

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

Звоните для уточнения информации

Какую конкретную информацию клиент должен сообщить серверу?

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

С таким небольшим количеством параметров сервер может четко знать, какой метод хочет вызвать клиент, и может делать точные вызовы!

Затем соберите ответ и верните его. Я опубликую здесь фактический список объектов запроса вызова.

data — это данные, которые я упомянул, остальные — это фреймворки, включая версию протокола, метод вызова и т. д. Это будет проанализировано позже.

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

процесс посадочного звонка

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

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

Например, я могу говорить по-английски и по-китайски, а вы можете говорить по-английски и по-китайски. Мы должны заключить соглашение между нами, чтобы выбрать язык, например, чтобы говорить по-китайски. Если кто-то говорит, что что-то не так, я могу понять Английский между вами и китайским. .

Это потому, что ваш мозг очень умен. Он может разумно распознавать язык общения, а компьютер — нет. Если вы думаете о своем коде, чтобы написать вывод 1, сможет ли он вывести 2?

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

нужно соглашение

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

Три распространенные формы соглашения

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

Форма фиксированной длины: Это означает, что длина протокола фиксирована, например, 100 байт - это единица протокола, тогда парсинг начинается после чтения 100 байт.

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

Недостаток в том,что он жесткий.Длину можно только фиксировать каждый раз,и длина не может превышать лимит.Кроме того,если он короткий,его нужно заполнять.В сценарии RPC не подходит.Кто знает как длинные параметры должны быть?

форма с разделителями специальными символами: На самом деле, это определение специального терминатора и оценка конца блока протокола в соответствии со специальным терминатором, например, с использованием новой строки и т.д.

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

форма заголовка+тела: То есть голова имеет фиксированную длину, а потом в голову будет заполняться длина тела. Тело не фиксированной длины, поэтому масштабируемость лучше. Можно сначала разобрать голову, а потом получить len тела по голове Разобрать тело.

Протокол Dubbo имеет вид Header + Body, а также есть специальный символ 0xDABB, который используется для решения проблем сцепления сети TCP.

Протокол Даббо

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

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

После 16 байтов находится тело протокола, включая версию протокола, имя интерфейса, версию интерфейса, имя метода и так далее.

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

Требуется сериализатор соглашения

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

Затем партнер десериализует эти потоки байтов в объекты.

протокол сериализации

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

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

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

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

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

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

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

Dubbo по умолчанию использует протокол сериализации hessian2.

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

Примерная блок-схема звонка

Давайте посмотрим на картинку на официальном сайте.

Короче говоря, клиент инициирует вызов, а фактический вызов - это прокси-класс. Прокси-класс, наконец, вызывает клиента (по умолчанию Netty). Необходимо создать заголовок протокола, а затем объект Java сериализуется для создания тела протокола. , а затем передается сетевой вызов.

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

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

Подробный процесс звонка

Ранее было проанализировано, что клиенту также необходимо сериализовать запрос на построение.Чтобы сделать диаграмму более сфокусированной, этот шаг опущен.Конечно, есть также шаг возврата ответа.

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

Конечно, в Cluster также есть механизмы отказоустойчивости, в том числе повторные попытки и так далее.

Запрос сначала поступит в пул потоков ввода-вывода Netty для чтения и записи, а также дополнительной сериализации и десериализации, доступ к которым можно получить черезdecode.in.ioКонтролируйте, а затем обработайте десериализованный объект через пул бизнес-потоков и найдите соответствующий Invoker для вызова.

Процесс вызова - анализ исходного кода клиента

Клиент вызывает код.

String hello = demoService.sayHello("world"); 

Вызов определенного интерфейса вызовет сгенерированный прокси-класс, а прокси-класс сгенерируетRpcInvocationвызов объектаMockClusterInvoker#invoke метод.

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

Затем давайте взглянем на код MockClusterInvoker#invoke.

Видно, что это для того, чтобы судить о том, есть ли в конфигурации мок настроенный.Если мок не настроен, анализ не будет проведен. Давайте посмотрим на реализацию this.invoker.invoke, которая на самом деле будет вызовите AbstractClusterInvoker#invoker .

Метод шаблона

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

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

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

Маршрутизация и балансировка нагрузки получают Invoker

Посмотрим это сноваlist(invocation), по сути, это найти Invoker через имя метода, а затем отфильтровать маршрут сервиса, а также создать MockInvoker.

Затем возьмите эти Invokers для другой волны выбора балансировки нагрузки, чтобы получить Invoker, который мы используем по умолчанию.FailoverClusterInvoker, то есть отказоустойчивый метод автоматического failover.По сути, маршрутизация, кластеризация и балансировка нагрузки - самостоятельные модули.Если развернуть, то там еще много контента, так что надо начинать еще одну статью.Это статья сначала будет использовать их как использование черного ящика.

Подводя итог немногоFailoverClusterInvoker получает список Invokers, возвращаемый Directory, и после маршрутизации позволяет LoadBalance выбрать Invoker из списка Invokers..

НаконецFailoverClusterInvokerПараметр будет передан методу вызова выбранного экземпляра Invoker для выполнения реального удаленного вызова.Давайте кратко рассмотрим FailoverClusterInvoker#doInvoke.Чтобы выделить ключевые моменты, я удалил многие методы.

Вызов, который инициирует вызов, заключается в вызове вызова в абстрактном классе, а затем вызове doInvoker подкласса. Метод в абстрактном классе очень прост, и я не буду его показывать. Он мало влияет. Просто посмотрите на doInvoke подкласса DubboInvoker.

Три способа позвонить

Из приведенного выше кода видно, что существует три типа вызовов, а именно односторонние, асинхронные и синхронные.

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

Асинхронный вызовНа самом деле Dubbo по своей природе асинхронный.Вы можете видеть, что после того, как клиент отправит запрос, он получит ResponseFuture, а затем завернет future в контекст, чтобы пользователь мог получить future из контекста, а затем пользователь может выполнить волну операций. Затем вызовите future.get и дождитесь результата.

Синхронный вызов, который у нас используется чаще всего, то есть фреймворк Dubbo помогает нам конвертировать асинхронно в синхронно Как видно из кода, он вызывается в исходном коде Dubbo.future.get, поэтому ощущение у пользователя такое, что я заблокирован после вызова метода этого интерфейса, и я должен дождаться прихода результата, прежде чем вернуться, поэтому он синхронный.

Видно, что Dubbo по своей природе асинхронный, почему есть синхронизация, потому что фреймворк помог нам ее повернуть, иРазница между синхронным и асинхронным заключается в том, чтоfuture.getВызывается ли он из пользовательского кода или из кода фреймворка?.

Возвращаясь к исходному коду, исходный код currentClient.request выглядит следующим образом: собрать запрос, создать будущее, а затем вызвать NettyClient для отправки запроса.

давайте посмотрим еще разDefaultFutureВнутренне вы когда-нибудь задумывались над вопросом, потому что он асинхронный, как найти соответствующее будущее после того, как будущее будет сохранено и ответ вернется?

Вот и раскрылось! заключается в использовании уникального идентификатора.

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

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

Сначала посмотрите на сообщение следующего ответа:

Response [id=14, version=null, status=20, event=false, error=null, result=RpcResult [result=Hello world, response from provider: 192.168.1.17:20881, exception=null]]

Посмотрите этот идентификатор, он в конечном итоге вызоветDefaultFuture#received Методы.

Для того, чтобы всем было понятнее, нарисую еще одну картинку:

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

Цепочка вызовов, которая инициирует запрос, показана на следующем рисунке:

Цепочка вызовов для обработки ответа на запрос показана на следующем рисунке:

Процесс вызова — анализ исходного кода на стороне сервера

После того, как сервер получит запрос, он проанализирует запрос и получит сообщение, которое имеет пять стратегий распространения:

По умолчанию стоит all, то есть все сообщения отправляются в пул бизнес-потоков Давайте посмотрим на реализацию AllChannelHandler.

Это должно инкапсулировать сообщение в ChannelEventRunnable и передать его в пул бизнес-потоков для выполнения.ChannelEventRunnable вызовет соответствующий метод обработки в соответствии с ChannelState, вотChannelState.RECEIVED, так что звонитеhandler.received, в конечном итоге вызовет HeaderExchangeHandler#handleRequest, давайте посмотрим на этот код.

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

Последний вызов мы уже прояснили.На самом деле будет вызываться прокси-класс, сгенерированный Javassist, который содержит настоящий класс реализации.Мы уже проанализировали его ранее и не будем здесь углубляться.Давайте посмотрим.getInvokerЭтот метод показывает, как найти соответствующий вызывающий объект на основе запрошенной информации.

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

Этот ключ выглядит так:

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

Суммировать

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

Сначала клиент вызывает метод интерфейса, а собственно вызывается прокси-класс, который через кластер получит кучу инвокеров из каталога (если их куча), а затем отфильтрует роутер ( конфигурация также добавит mockInvoker для использования из-за деградации службы), а затем получите loadBalance через SPI для волны балансировки нагрузки.

Здесь мы должны подчеркнуть, что кластер по умолчанию — FailoverCluster, повторит попытку отказоустойчивой обработки на следующий день после подробного анализа.

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

После того, как сервер NettyServer получает запрос, он получает информацию в соответствии с протоколом и десериализует ее в объект, а затем отправляет сообщение в соответствии со стратегией распространения.По умолчанию — All, и оно выбрасывается в пул бизнес-потоков.

Бизнес-поток будет судить в соответствии с типом сообщения, а затем получит serviceKey и соответствующий Invoker из exporterMap, сгенерированного предыдущим раскрытием службы, а затем вызовет реальный класс реализации.

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

А еще я немного рассказал о шаблоне проектирования шаблонного метода.Конечно, в нем скрыто много шаблонов проектирования, таких как цепочка ответственности, декораторы и т. д. Он слишком распространен в исходниках, в основном везде.

болтовня

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

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

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

Я Ао Бин,Чем больше вы знаете, тем больше вы не знаете, спасибо за ваши таланты:как,собиратьиКомментарий, увидимся в следующий раз!


Статья постоянно обновляется, вы можете искать в WeChat "Третий принц Ао Бин"Прочтите это в первый раз, ответьте [материал] Подготовленные мной материалы интервью и шаблоны резюме крупных заводов первой линии, эта статьяGitHub github.com/JavaFamilyОн был включен, и есть полные тестовые сайты для интервью с крупными заводами.Добро пожаловать в Star.