Предыдущий"Микросервисная архитектура, сколько «микро» подходит?«Говорили о гранулярности микросервисов. Микросервисы неотделимы от фреймворка RPC.В этой статье мы расскажем о принципах, практиках и деталях фреймворка RPC.
Каковы преимущества сервитизации?
Одним из преимуществ сервитизации является то, что она может реализовать техническое разделение крупных компаний между командами, не ограничивая выбор технологий, используемых поставщиком услуг, как показано на следующем рисунке:
-
Сервис A: техническое обслуживание европейской команды, техническое образование — Java.
-
Сервис B: поддерживается американской командой, реализован на C++.
-
Сервис C: поддерживается китайской командой, технологический стек запущен.
Восходящий вызывающий объект службы может завершить вызов удаленной службы в соответствии с интерфейсом и протоколом.
Но на самом деле у большинства интернет-компаний есть ограниченные группы по исследованиям и разработкам, и большинство из них используют одну и ту же техническую систему для предоставления услуг:
В этом случае, если нет единой сервисной инфраструктуры, сервис-провайдерам каждой команды необходимо реализовать свой собственный набор.Сериализация, десериализация, сетевая структура, пул соединений, отправка и получение потоков, обработка времени ожидания, конечный автоматТакая повторяющаяся техническая работа «вне бизнеса» приводит к общей неэффективности.
Таким образом, унифицированная сервисная структура реализует вышеупомянутую работу «вне бизнеса» унифицированным образом, что является основной задачей, решаемой сервисом.
Что такое РПЦ?
Протокол удаленного вызова процедур, удаленный вызов процедур.
Что такое «далеко» и почему «далеко»?
Давайте сначала посмотрим, что такое «рядом», то есть «локальный вызов функции».
Когда мы пишем:
int result = Add(1, 2);
Что происходит в этой строке кода?
-
Передать два входных параметра
-
Функция в сегменте локального кода вызывается для выполнения логики операции.
-
вернуть вывод
Все эти три действия происходят в одном и том же пространстве процессов, т.локальный вызов функции.
Есть ли способ вызвать функцию кросс-процесса?
Обычно этот процесс развертывается на другом сервере.
Проще всего подумать, что два процесса согласовывают формат протокола и используют связь через сокет для передачи:
-
Вход
-
какую функцию вызывать
-
женьшень
Если это может быть достигнуто, это «удаленный» вызов процедуры.
Связь через сокет может передавать только непрерывный поток байтов Как поместить входные параметры и функции в непрерывный поток байтов?
Предположим, спроектируйте 11-байтовое сообщение запроса:
-
Первые 3 байта заполнены именем функции «добавить».
-
Средние 4 байта заполнены первым параметром "1"
-
Последние 4 байта заполняются вторым параметром "2"
Точно так же может быть разработано 4-байтовое ответное сообщение:
- 4 байта заполнены результатом обработки "3"
Код вызывающего абонента может стать:
запрос = MakePacket("добавить", 1, 2);
SendRequest_ToService_B(request);
response = RecieveRespnse_FromService_B();
int result = unMakePacket(respnse);
4 шага:
(1) преобразовать входящий параметр в поток байтов;
(2) отправить поток байтов службе B;
(3) Прием обратного потока байтов от службы B;
(4) Превратить возвращенный поток байтов в исходящий параметр;
Код для слуги может быть таким:
request = RecieveRequest();
args/function = unMakePacket(request);
result = Add(1, 2);
response = MakePacket(result);
SendResponse(response);
5 шагов также хорошо понятны:
(1) сервер получает поток байтов;
(2) преобразовать поток байтов в имена функций и параметры;
(3) Вызовите функцию локально, чтобы получить результат;
(4) преобразовать результат в поток байтов;
(5) Отправить поток байтов вызывающей стороне;
Этот процесс описывается на схеме следующим образом:
Этапы обработки вызывающей стороны и сервера очень понятны.
Что является самой большой проблемой в этом процессе?
Вызывающий код слишком громоздкий, чтобы каждый раз обращать внимание на множество низкоуровневых деталей:
-
Преобразование входных параметров в поток байтов, то есть сериализация деталей протокола прикладного уровня.
-
отправка сокета, т. е. сведения о сетевом транспортном протоколе
-
сокет получить
-
Преобразование потока байтов в выходной параметр, то есть десериализация деталей протокола прикладного уровня.
Может ли вызывающий слой не обращать внимания на эту деталь?
Да, инфраструктура RPC решает эту проблему, позволяя вызывающей стороне «вызывать удаленную функцию (службу) как локальную функцию».
Говоря об этом, вы немного относитесь к RPC, сериализации и сериализации? Глядя вниз, можно увидеть более низкоуровневые детали.
Каковы обязанности фреймворка RPC?
Платформа RPC должна защищать различные сложности от вызывающей стороны, а также защищать различные сложности от поставщика услуг:
-
Клиент, вызывающий службу, чувствует, что вызывает локальную функцию для вызова службы.
-
Сервер поставщика услуг чувствует, что реализует локальную функцию для реализации службы.
Таким образом, вся структура RPC делится наклиентский разделираздел сервера, за достижение вышеуказанных целей и защиту от сложности отвечает инфраструктура RPC.
Как показано на фиг.Обязанности деловой стороныДа:
-
Вызывающий абонент A передает параметры, выполняет вызов и получает результат
-
Сторона обслуживания B получает параметры, выполняет логику и возвращает результат.
Обязанности RPC FrameworkДа, часть большой синей коробки посередине:
-
сторона клиента: Сериализация, десериализация, управление пулом соединений, балансировка нагрузки, отработка отказа, управление очередями, управление тайм-аутом, асинхронное управление и т. д.
-
серверная часть: компоненты на стороне сервера, очереди отправки и получения пакетов на стороне сервера, потоки ввода-вывода, рабочие потоки, сериализация и десериализация и т. д.
Мы много знаем о технологии серверной части, далее сосредоточимся на технических деталях клиентской части.
Начнем с раздела «Сериализация-десериализация» раздела RPC-клиент.
Зачем сериализовать?
Инженеры часто используют «объекты» для манипулирования данными:
class User{
std::String user_name;
uint64_t user_id;
uint32_t user_age;
};
Пользователь u = новый пользователь ("shenjian");
u.setUid(123);
u.setAge(35);
Но когда данные должны бытьместо храненияиликоробка передачКогда «объект» не так прост в использовании, часто необходимо преобразовать данные в «двоичный поток байтов» непрерывного пространства. Вот некоторые типичные сценарии:
-
база данныхиндексированное дисковое хранилище: Индекс базы данных ab+tree в памяти, но этот формат не может храниться непосредственно на диске, поэтому необходимо преобразовать b+дерево в бинарный байтовый поток непрерывного пространства, прежде чем его можно будет хранить на диске .
-
Кэшированное хранилище KV: redis/memcache — это кеш типа KV, значение, хранящееся в кеше, должно быть двоичным потоком байтов в непрерывном пространстве, а не объектом пользователя.
-
сетевая передача данных: данные, отправляемые сокетом, должны быть двоичным потоком байтов в непрерывном пространстве и не могут быть объектом.
так называемыйСериализация(Сериализация) — это процесс преобразования данных в виде «объекта» в данные в виде «непрерывного пространственного двоичного потока байтов». Процесс, обратный этому, называетсядесериализовать.
Как сериализовать?
Это очень подробный вопрос, что бы вы сделали, если бы вас попросили преобразовать «объект» в поток байтов? Один из подходов, который приходит на ум, — это язык разметки с самоописанием, такой как xml (или json):
После указания правил преобразования отправитель может легко сериализовать объект класса User в xml, а после того, как сервер получит бинарный поток xml, также легко сериализовать его в объект User.
Голос за кадром: эта работа проста, когда язык поддерживает рефлексию.
Второй метод — реализовать бинарный протокол для сериализации или взять в качестве примера вышеприведенный объект User. Вы можете разработать общий протокол следующим образом:
-
Первые 4 байта представляют собой порядковый номер
-
4 байта после серийного номера представляют собой длину m ключа.
-
Следующие m байтов представляют собой значение ключа
-
Следующие 4 байта представляют длину n значения
-
Следующие n байтов представляют собой значение value
-
рекурсивно, как xml, пока не будет описан весь объект
Приведенный выше объект User, описываемый этим протоколом, может выглядеть так:
-
Первая строка: серийный номер — 4 байта (установите 0 для представления имени класса), длина имени класса — 4 байта (длина — 4), следующие 4 байта — имя класса («Пользователь»), всего 12 байт.
-
Вторая строка: порядковый номер — 4 байта (1 означает первый атрибут), длина атрибута — 4 байта (длина — 9), следующие 9 байт — имя атрибута («user_name») и длина значения атрибута 4 байта (длина 8), значение атрибута 8 байт (значение "shenjian"), всего 29 байт
-
Третья строка: серийный номер — 4 байта (2 означает второй атрибут), длина атрибута — 4 байта (длина 7), следующие 7 байт — имя атрибута («user_id») и длина значения атрибута 4 байта (длина 8), значение атрибута 8 байт (значение 123), всего 27 байт
-
Четвертая строка: серийный номер — 4 байта (3 — третий атрибут), длина атрибута — 4 байта (длина — 8), следующие 8 байт — имя атрибута («имя_пользователя»), а длина значения атрибута — 4 байта (длина 4), значение атрибута 4 байта (значение 35), всего 24 байта
Весь двоичный поток байтов имеет в общей сложности 12+29+27+24=92 байта.
Фактический протокол сериализации должен учитывать больше деталей, чем это.Например, строго типизированный язык должен не только восстанавливать имена атрибутов, значения атрибутов, но и типы атрибутов; сложные объекты должны учитывать не только общие типы, но также учитывать тип вложенности объектов, и т.п. В любом случае идея сериализации похожа.
Какие факторы следует учитывать в протоколе сериализации?
Независимо от того, используется ли для сериализации объектов зрелый протокол xml/json или пользовательский двоичный протокол, при разработке протокола сериализации необходимо учитывать следующие факторы.
-
Эффективность парсинга: это должно быть основным соображением для протокола сериализации. Например, анализ xml/json занимает много времени, а анализ дерева дум требует анализа. Пользовательский двоичный протокол очень эффективен для анализа.
-
Степень сжатия, эффективность передачи: для одного и того же объекта xml/json передает большое количество тегов xml, достоверность информации низкая, а пространство, занимаемое двоичным пользовательским протоколом, относительно невелико.
-
Расширяемость и совместимость: удобно ли добавлять поля и нужно ли принудительно обновлять старый клиент после добавления полей — все это вопросы, которые необходимо учитывать.
-
Читабельность и отладка: Это легко понять, читабельность xml/json намного лучше, чем бинарный протокол.
-
кросс язык: Вышеуказанные два протокола являются межъязыковыми, а некоторые протоколы сериализации тесно связаны с языком разработки.Например, протокол сериализации dubbo может поддерживать только вызовы Java RPC.
-
общность: xml/json очень универсальный, и есть хорошие сторонние библиотеки парсинга.Очень удобно парсить каждый язык.Хотя указанный выше бинарный протокол может быть межъязыковым, простой клиент протокола должен быть написан на каждом языке .
Каковы общие методы сериализации?
-
xml/json: эффективность синтаксического анализа, степень сжатия плохие, масштабируемость, удобочитаемость и универсальность хорошие.
-
thrift
-
protobuf: созданный Google, это должен быть бутик, хороший во всех аспектах, настоятельно рекомендуется, это двоичный протокол, читабельность немного плохая, но есть также аналогичный протоколу to-string, чтобы помочь отлаживать проблемы
-
Avro
-
CORBA
-
mc_pack: Если знаешь, то поймешь, если не знаешь, то не поймешь.Использовался в 2009. Говорят, что превосходит protobuf по всем параметрам. текущая ситуация.
-
…
RPC-клиент кроме:
- Часть, которая сериализует и десериализует (1, 4 на изображении выше)
Также содержит:
- Часть отправки потока байтов и приема потока байтов (2, 3 на рисунке выше)
Эта часть разделена на два способа: синхронный вызов и асинхронный вызов, которые будут представлены один за другим ниже.
Голос за кадром: Разобраться с RPC-клиентом действительно непросто.
Фрагмент кода для синхронного вызова:
Result = Add(Obj1, Obj2);// Блокируется до получения результата
Фрагмент кода для асинхронного вызова:
Add(Obj1, Obj2, callback);// Возврат сразу после вызова, не дожидаясь результата
Результат обработки передается через обратный вызов как:
callback(Result){// Эта функция обратного вызова будет вызвана после получения результата обработки
…
}
Эти два типа вызовов совершенно по-разному реализованы в RPC-клиенте.
Какова архитектура синхронного вызова RPC-клиента?
Так называемый синхронный вызов всегда находится в состоянии блокировки, пока не будет получен результат, и он всегда будет занимать рабочий поток.На приведенном выше рисунке кратко показаны компоненты, взаимодействия и этапы процесса:
-
левая рамка, который представляет рабочий поток вызывающей стороны
-
осталосьрозовая средняя рамка, который представляет клиентский компонент RPC
-
правильнооранжевая коробка, представляющий RPC-сервер
-
синие две маленькие коробки, представляющий два основных компонента синхронного RPC-клиента, компонент сериализации и компонент пула соединений.
-
белая коробка процессаи номера стрелок 1-10, представляющие последовательные шаги выполнения всего рабочего потока:
1) Бизнес-код инициирует вызов RPC:
Result=Add(Obj1,Obj2)
2) компонент сериализации, который сериализует вызов объекта в поток двоичных байтов, который можно понимать как пакет для отправки, package1;
3) Получить доступное подключение через компонент пула подключений;
4) Отправьте пакет package1 на RPC-сервер через соединение connection;
5) Отправить пакет на RPC-сервер для передачи по сети;
6) Ответный пакет передается по сети и отправляется обратно RPC-клиенту;
7) Получить ответный пакет package2 от RPC-сервера через установленное соединение;
8) Через компонент пула соединений поместите соединение обратно в пул соединений;
9) сериализовать компонент, сериализовать пакет2 в объект результата и вернуть его вызывающей стороне;
10) бизнес-код получает результат Result, а рабочий поток продолжает опускаться;
Голос за кадром: прочтите шаги 1–10 на архитектурной диаграмме.
Что делает компонент пула соединений?
Такие функции, как балансировка нагрузки, отработка отказа и тайм-аут отправки, поддерживаемые блокировкой инфраструктуры RPC, реализуются через компонент пула соединений.
Интерфейсы, предоставляемые типичным компонентом пула соединений:
int ConnectionPool::init(…);
Connection ConnectionPool::getConnection();
int ConnectionPool::putConnection(Connection t);
Что делает init?
А нижестоящий RPC-сервер (обычно кластер) устанавливает N длинных tcp-соединений, так называемый «пул» соединений.
Что делает getConnection?
Возьмите соединение из "пула" соединений, заблокируйте его (установите флаг) и верните вызывающему абоненту.
Что делает putConnection?
Поместите выделенное соединение обратно в «пул» соединений и разблокируйте его (также установите флаг).
Как добиться балансировки нагрузки?
Соединение с кластером RPC-серверов устанавливается в пуле соединений. Когда пул соединений возвращает соединение, оно должно быть случайным.
Как реализовать отказоустойчивость?
В пуле соединений устанавливается соединение с кластером RPC-серверов.Когда пул соединений обнаруживает, что соединение определенной машины является ненормальным, ему необходимо исключить соединение этой машины и вернуться к обычному соединению.После того как машина восстановил, добавь соединение обратно.
Как реализовать тайм-аут отправки?
Поскольку это синхронный блокирующий вызов, после установления соединения используйте команду send/recv с тайм-аутом, чтобы добиться отправки и получения с тайм-аутом.
В целом реализация синхронного RPC-клиента относительно проста: могут быть реализованы компоненты сериализации, компоненты пула соединений и несколько рабочих потоков.
Оставшаяся проблема,Каково наиболее подходящее количество рабочих потоков?
этот вопрос вКакое наиболее подходящее количество рабочих потоков установить?обсуждалось в , и не будет обсуждаться здесь.
Какова архитектура асинхронного обратного вызова RPC-клиента?
Так называемый асинхронный обратный вызов не будет находиться в заблокированном состоянии до тех пор, пока не будет получен результат.Теоретически ни один поток не находится в заблокированном состоянии в любой момент времени.Поэтому модель асинхронного обратного вызова теоретически требует лишь нескольких рабочих потоков для подключения к пропускная способность службы, как показано на рисунке выше:
-
левая рамка, это небольшое количество рабочих потоков (всего несколько) для выполнения вызовов и обратных вызовов
-
средняя розовая рамка, который представляет клиентский компонент RPC
-
правая оранжевая рамка, представляющий RPC-сервер
-
синие шесть маленьких коробочек, представляющий шесть основных компонентов асинхронного RPC-клиента:Диспетчер контекста, диспетчер времени ожидания, компонент сериализации, очередь нижестоящего приемопередатчика, поток нижестоящего приемопередатчика, компонент пула соединений
-
белая коробка процессаи номера стрелок 1-17, представляющие последовательные шаги выполнения всего рабочего потока:
1) бизнес-код инициирует асинхронный вызов RPC;
Add(Obj1,Obj2, callback)
2) менеджер контекста, в котором хранятся запросы, обратные вызовы и контексты;
3) компонент сериализации, который сериализует вызов объекта в двоичный поток байтов, который можно понимать как пакет для отправки, package1;
4) Нисходящая очередь отправки и получения помещает сообщение в «очередь для отправки», и вызов возвращается в это время, не блокируя рабочий поток;
5) Нисходящий поток отправки и получения берет сообщение из "очереди на отправку" и получает доступное соединение соединения через компонент пула соединений;
6) Отправьте пакет package1 на RPC-сервер через соединение connection;
7) отправить пакет на RPC-сервер для передачи по сети;
8) Ответный пакет передается по сети и отправляется обратно RPC-клиенту;
9) Получить ответный пакет package2 от RPC-сервера через установленное соединение;
10) Нисходящий поток отправки и получения помещает сообщение в «принятую очередь» и помещает соединение обратно в пул соединений через компонент пула соединений;
11) В нисходящей очереди отправки и получения сообщение вынимается, и в это время запустится обратный вызов, а рабочий поток не будет заблокирован;
12) Компонент сериализации, сериализовать пакет2 в объект результата;
13) Менеджер контекста, вывод результата, обратного вызова и контекста;
14) Обратный вызов бизнес-кода через callback, возврат результата Result, а рабочий поток продолжает опускаться;
Если запрос не возвращается в течение длительного времени, поток обработки:
15) Менеджер контекста, запрос давно не возвращался;
16) Менеджер тайм-аута получает контекст тайм-аута;
17) Обратный вызов бизнес-кода через timeout_cb, а рабочий поток продолжает падать;
Голос за кадром: Пожалуйста, внимательно прочитайте этот процесс несколько раз вместе с архитектурной схемой.
Компоненты сериализации и компоненты пула соединений были представлены выше, а очереди отправки и получения, а также потоки отправки и получения легче понять. Следующие основные моментыменеджер контекстаа такжетайм-аут менеджерЭти два полных компонента.
Зачем нужен контекстный менеджер?
Из-за отправки пакетов запросов обратные вызовы пакетов ответов являются асинхронными и даже не завершаются в одном и том же рабочем потоке.Необходим компонент для записи контекста запроса и сопоставления некоторой информации, такой как запрос-ответ-обратный вызов.
Как сопоставить информацию запрос-ответ-обратный вызов?
Это очень интересная задача: три пакета запроса a, b и c отправляются нижестоящему сервису через соединение, а три пакета ответа x, y и z принимаются асинхронно:
Как узнать, какой пакет запроса соответствует какому пакету ответа?
Как узнать, какой ответный пакет соответствует какой функции обратного вызова?
Конкатенация запрос-ответ-обратный вызов может быть достигнута с помощью «идентификатора запроса».
Весь процесс обработки такой же, как и выше, через идентификатор запроса, диспетчер контекста соответствует отношению сопоставления между запросом-ответом-обратным вызовом:
1) Сгенерировать идентификатор запроса;
2) генерировать контекст контекста запроса, который включает время отправки, функцию обратного вызова и другую информацию;
3) Менеджер контекста записывает отношение отображения между req-id и контекстом;
4) Введите req-id в пакет запроса и отправьте его на RPC-сервер;
5) RPC-сервер возвращает req-id в ответном пакете;
6) Найдите исходный контекст контекста через контекстный менеджер по req-id в ответном пакете;
7) Получить обратный вызов функции обратного вызова из контекстного контекста;
8) Обратный вызов возвращает Результат, чтобы способствовать дальнейшему выполнению бизнеса;
Как добиться балансировки нагрузки и аварийного переключения?
Подобно идее синхронного пула соединений, разница заключается в следующем:
-
Пул синхронных соединений использует режим блокировки для отправки и получения, и необходимо установить несколько соединений с одним IP-адресом одной службы.
-
Асинхронная отправка и получение, один ip службы должен установить только небольшое количество соединений (например, tcp-соединение)
Как добиться тайм-аута отправки и получения?
Тайм-аут отправки и получения отличается от реализации синхронной блокировки отправки и получения:
-
Тайм-аут синхронной блокировки может быть реализован напрямую с помощью отправки/получения с тайм-аутом.
-
Асинхронная неблокирующая отправка и получение сетевого пакета nio, поскольку соединение не всегда ожидает ответного пакета, тайм-аут реализуется менеджером тайм-аута.
Как менеджер тайм-аутов реализует управление тайм-аутом?
Диспетчер времени ожидания используется для реализации обработки обратного вызова времени ожидания пакета запроса.
Каждый запрос, отправленный нижестоящему RPC-серверу, будет сохранять req-id и информацию о контексте в диспетчере контекста.Контекст сохраняет много информации, связанной с запросом, такой как req-id, обратный вызов пакета, обратный вызов тайм-аута, время отправки, и т.п.
Менеджер тайм-аута запускает таймер для сканирования контекста в диспетчере контекста, чтобы увидеть, не слишком ли велико время отправки запроса в контексте.Если оно слишком велико, он не будет ждать ответного пакета, а напрямую перезвонит по тайм-ауту. , и отправьте бизнес-процесс для продолжения. Контекст удаляется.
Если нормальный ответный пакет прибывает после выполнения обратного вызова с тайм-аутом, а контекст не может быть найден в диспетчере контекстов через req-id, запрос будет отброшен напрямую.
Голос за кадром: Не удалось восстановить контекст, так как истекло время обработки.
В любом случае, по сравнению с асинхронными обратными вызовами и синхронными обратными вызовами, в дополнение к компонентам сериализации и компонентам пула соединений будет больше диспетчеров контекста, менеджеров тайм-аутов, очередей нижестоящих приемопередатчиков, нижестоящих потоков приемопередатчиков и других компонентов, и они будут влиять на вызов. привычки звонящего.
Голос за кадром: Привычки программирования, от синхронизации до обратного вызова.
Асинхронный обратный вызов может повысить общую пропускную способность системы.Конкретный метод, используемый для реализации RPC-клиента, может быть выбран в сочетании с бизнес-сценариями.
Суммировать
Что такое вызов RPC?
Вызовите удаленную службу, как вызов локальной функции.
Зачем вам нужна RPC-инфраструктура?
Платформа RPC используется для защиты технических деталей, таких как сериализация и передача по сети во время вызовов RPC. Пусть звонящий сосредоточится только на звонке, а сервер только на реализации звонка.
Что такое сериализация? Зачем нужна сериализация?
Процесс преобразования объекта в непрерывный двоичный поток называется сериализацией. Дисковое хранилище, кэш-хранилище и передача по сети могут работать только с двоичными потоками, поэтому они должны быть сериализованы.
Каковы основные компоненты синхронного RPC-клиента?
Основными компонентами синхронного RPC-клиента являются компоненты сериализации и компоненты пула соединений. Он реализует балансировку нагрузки и аварийное переключение через пулы соединений, а также реализует обработку тайм-аута посредством блокировки отправки и получения.
Каковы основные компоненты асинхронного RPC-клиента?
Основными компонентами асинхронного RPC-клиента являются компоненты сериализации, компоненты пула соединений, очереди отправки и получения, потоки отправки и получения, менеджеры контекста и менеджеры времени ожидания. Он связывает функцию обратного вызова пакет-ответ-запрос через «идентификатор запроса», использует диспетчер контекста для управления контекстом, использует таймер в диспетчере тайм-аута для запуска обратного вызова по тайм-ауту и продвигает обработку тайм-аута бизнес-процесса.
идеиважнее вывод.
Путь архитектора- Делитесь техническими идеями
Рекомендуемое чтение:
"«Архитектура кэша, One Piece» — итоги года (5)》
"«Как технический специалист руководит командой» — итоги года (6)》
"«MySQL надо знать и знать» — итоги года (7)》
"«Алгоритмы и структуры данных» — итоги года (8)》
Исходный код какой инфраструктуры RPC вы читали?