5.3 Удаленный вызов процедур¶

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

Рисунок 137. Временная шкала для RPC.

Транспортный протокол, поддерживающий парадигму запроса/ответа, — это намного больше, чем сообщение UDP, идущее в одном направлении, за которым следует сообщение UDP, идущее в другом направлении. Он должен иметь дело с правильной идентификацией процессов на удаленных хостах и ​​сопоставлением запросов с ответами. Также может потребоваться преодолеть некоторые или все ограничения базовой сети, указанные в формулировке проблемы в начале этой главы. Хотя TCP преодолевает эти ограничения, предоставляя надежную службу потока байтов, он также не полностью соответствует парадигме запроса/ответа. В этом разделе описывается третья категория транспортных протоколов, называемая Удаленный вызов процедур (RPC), которая более точно соответствует потребностям приложения, участвующего в обмене сообщениями запрос/ответ.

Основы RPC¶

Технически RPC не является протоколом — его лучше рассматривать как общий механизм для структурирования распределенных систем. RPC популярен, потому что он основан на семантике вызова локальной процедуры — программа приложения выполняет вызов процедуры независимо от того, является она локальной или удаленной, и блокируется до тех пор, пока вызов не вернется. Разработчик приложения может в значительной степени не знать, является ли процедура локальной или удаленной, что значительно упрощает его задачу. Когда вызываемые процедуры на самом деле являются методами удаленных объектов в объектно-ориентированном языке, RPC известен как вызов удаленного метода (RMI). Хотя концепция RPC проста, существуют две основные проблемы, которые делают ее более сложной, чем вызовы локальных процедур:

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

Таким образом, полный механизм RPC фактически включает в себя два основных компонента:

  1. Протокол, который управляет сообщениями, отправляемыми между процессами клиента и сервера и который обрабатывает с потенциально нежелательными свойствами базовой сети.
  2. Поддержка языка программирования и компилятора для упаковки аргументов в сообщение запроса на клиентском компьютере, а затем для преобразования этого сообщения обратно в аргументы на сервере, а также с возвращаемым значением (этот фрагмент RPC механизм обычно называется компилятором-заглушкой ).

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

Рисунок 138. Полный механизм RPC.

В этом разделе рассматриваются только связанные с протоколом аспекты механизма RPC. То есть он игнорирует заглушки и вместо этого фокусируется на протоколе RPC, который иногда называют протоколом запроса/ответа, который передает сообщения между клиентом и сервером. Преобразование аргументов в сообщения и наоборот рассматривается в другом месте. Также важно иметь в виду, что клиентские и серверные программы написаны на каком-то языке программирования, а это означает, что данный механизм RPC может поддерживать заглушки Python, заглушки Java, заглушки GoLang и т. Д., Каждая из которых включает специфичные для языка идиомы для описания процедур. вызывается.

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

Идентификаторы в RPC¶

Две функции, которые должны выполняться любым Протоколы RPC:

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

Первая проблема имеет некоторое сходство с проблемой идентификации узлов в сети (например, IP-адреса). Один из вариантов дизайна при идентификации вещей — сделать это пространство имен плоским или иерархическим. Простое пространство имен будет просто назначать уникальный неструктурированный идентификатор (например, целое число) каждой процедуре, и этот номер будет передаваться в одном поле в сообщении запроса RPC.. Это потребует какой-то централизованной координации, чтобы избежать присвоения одного и того же номера процедуры двум разным процедурам. В качестве альтернативы протокол мог бы реализовать иерархическое пространство имен, аналогичное тому, которое используется для имен путей к файлам, которое требует, чтобы только «базовое имя» файла было уникальным в пределах его каталог. Такой подход потенциально упрощает работу по обеспечению уникальности имен процедур. Иерархическое пространство имен для RPC может быть реализовано путем определения набора полей в формате сообщения запроса, по одному для каждого уровня именования, скажем, в двух- или трехуровневом иерархическом пространстве имен.

Ключом к сопоставлению ответного сообщения с соответствующим запросом является однозначная идентификация пар запрос-ответ с использованием поля идентификатора сообщения. В поле идентификатора сообщения Areply установлено то же значение, что и в сообщении запроса. Когда клиентский модуль RPC получает ответ, он использует идентификатор сообщения для поиска соответствующего невыполненного запроса. Чтобы транзакция RPC выглядела как вызов локальной процедуры для вызывающей стороны, вызывающая сторона блокируется до получения ответного сообщения. При получении ответа заблокированный вызывающий абонент идентифицируется на основе номера запроса в ответе, возвращаемое значение удаленной процедуры получается из ответа, а вызывающий абонент разблокируется, чтобы он мог вернуться с этим возвращаемым значением.

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

Один из способов устранить эту проблему — использовать идентификатор загрузки . Идентификатор загрузки машины — это число, которое увеличивается каждый раз при перезагрузке машины; этот номер считывается из энергонезависимой памяти (например, диска или флэш-накопителя), увеличивается и записывается обратно в запоминающее устройство во время процедуры запуска машины. Этот номер затем помещается в каждое сообщение, отправляемое этим хостом. Если получено сообщение со старым идентификатором сообщения, но с новым идентификатором загрузки, оно распознается как новое сообщение. Фактически, идентификатор сообщения и идентификатор загрузки объединяются, чтобы сформировать уникальный идентификатор для каждой транзакции.

Преодоление сетевых ограничений¶

RPC протоколы часто выполняют дополнительные функции, чтобы справиться с тем фактом, что сети не являются идеальными каналами. Две такие функции:

  • Обеспечение надежной доставки сообщений
  • Поддержка сообщений большого размера за счет фрагментации и повторной сборки

Протокол RPC может «устранить эту проблему», выбрав запуск поверх надежного протокола, такого как TCP, но во многих случаях протокол RCP реализует свой собственный уровень надежной доставки сообщений поверх ненадежной подложки (например, UDP/IP ). Такой протокол RPC, вероятно, будет обеспечивать надежность с использованием подтверждений и тайм-аутов, аналогично TCP.

Основной алгоритм прост, как показано на временной шкале на рисунке 139. Клиент отправляет сообщение запроса, а сервер подтверждает его. Затем, после выполнения процедуры, сервер отправляет ответное сообщение, и клиент подтверждает ответ.

Рис. 139. Простая временная шкала для надежного протокола RPC.

Либо сообщение, несущее данные (сообщение запроса или ответное сообщение), либо ACK, отправленный для подтверждения того, что сообщение может быть потерялся в сети. Чтобы учесть эту возможность, и клиент, и сервер сохраняют копию каждого сообщения, которое они отправляют, до тех пор, пока для него не поступит ACK. Каждая сторона также устанавливает таймер RETRANSMIT и повторно отправляет сообщение, если этот таймер истекает. Обе стороны сбрасывают этот таймер и пытаются еще раз некоторое согласованное количество раз, прежде чем отказаться от сообщения и освободить его.

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

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

Каждое сообщение включает поле идентификатора канала для укажите, к какому каналу принадлежит сообщение. Сообщение с запросом в данном канале неявно подтвердит предыдущий ответ в этом канале, если он еще не был подтвержден. Прикладная программа может открывать несколько каналов для сервера, если она хочет иметь более одной транзакции запроса/ответа между ними одновременно (приложению потребуется несколько потоков). Как показано на рисунке 140, ответное сообщение служит для подтверждения сообщения запроса, а последующий запрос подтверждает предыдущий ответ.. Обратите внимание, что мы видели очень похожий подход — названный параллельными логическими каналами — в предыдущем разделе, как способ улучшить производительность механизма надежности с остановкой и ожиданием.

Рис. 140. Временная шкала для надежного протокола RPC с использованием неявного подтверждения.

Еще одна сложность, которую должен решить RPC, заключается в том, что серверу может потребоваться сколь угодно много времени для получения результата, и, что еще хуже, он может дать сбой перед генерацией ответа. Имейте в виду, что мы говорим о периоде времени после того, как сервер подтвердил запрос, но до того, как он отправил ответ. Чтобы помочь клиенту отличить медленный сервер от неработающего, клиентская сторона RPC может периодически отправлять сообщение «Вы живы?» сообщение серверу, и сервер отвечает ACK. В качестве альтернативы сервер мог бы отправлять клиенту сообщения «Я все еще жив», даже если клиент их не запросил. Подход является более масштабируемым, потому что он возлагает большую нагрузку на каждого клиента (управление таймером тайм-аута) на клиентов.

Надежность RPC может включать свойство, известное как не более- Oncesemantics . Это означает, что для каждого сообщения запроса, отправляемого клиентом, на сервер доставляется не более одной копии этого сообщения. Каждый раз, когда клиент вызывает удаленную процедуру, эта процедура вызывается на сервере как минимум один раз. Мы говорим «максимум один раз», а не «ровно один раз», потому что всегда возможно, что либо сеть, либо серверная машина вышли из строя, что делает невозможным доставку даже одной копии сообщения запроса.

Чтобы реализовать семантику «не более одного раза», RPC на стороне сервера должен распознавать повторяющиеся запросы (и игнорировать их), даже если он уже успешно ответил на исходный запрос. Следовательно, он должен поддерживать некоторую информацию о состоянии, которая идентифицирует прошлые запросы. Один из подходов состоит в том, чтобы идентифицировать запросы с помощью порядковых номеров, поэтому серверу нужно запоминать только самый последний порядковый номер. К сожалению, это ограничит RPC одним невыполненным запросом (к данному серверу) за раз, поскольку один запрос должен быть выполнен до того, как запрос со следующим порядковым номером может быть передан. Еще раз, каналы предоставляют решение. Сервер мог распознавать повторяющиеся запросы, запоминая текущий порядковый номер для каждого канала, не ограничивая клиента одним запросом за раз.

Как ни очевидно, как звучит «не более одного раза», не все протоколы RPC поддерживают такое поведение. Некоторые поддерживают семантику, которая в шутку называется семантикой нуля или более ; то есть каждый вызов на клиенте приводит к тому, что удаленная процедура вызывается ноль или более раз. Нетрудно понять, как это может вызвать проблемы для удаленной процедуры, которая изменяла некоторую локальную переменную состояния (например, увеличенный счетчик) или имела какой-либо видимый извне побочный эффект (например, запуск ракеты) каждый раз, когда она вызывалась.. С другой стороны, если вызываемая удаленная процедура idempotent — множественные вызовы имеют тот же эффект, что и один — то механизму RPC не требуется поддерживать семантику «один раз»; будет достаточно более простой (возможно, более быстрой) реализации.

Как и в случае с надежностью, две причины, по которым протокол RPC может реализовывать фрагментацию и повторную сборку сообщений, заключаются в том, что это не обеспечивается базовым стеком протоколов или может быть реализован более эффективно с помощью протокола RPC. Рассмотрим случай, когда RPC реализован поверх UDP/IP и полагается на IP для фрагментации и повторной сборки. Если хотя бы один фрагмент сообщения не может быть доставлен в течение определенного времени, IP отбрасывает те фрагменты, которые действительно были доставлены, и сообщение фактически теряется. В конце концов, протокол RPC (при условии, что он обеспечивает надежность) истечет по тайм-ауту и ​​повторно передаст сообщение. В отличие от этого, рассмотрим протокол RPC, который реализует собственную фрагментацию и повторную сборку и агрессивно отправляет ACK или NACK (отрицательно подтверждает) отдельные фрагменты. Потерянные фрагменты будут быстрее обнаруживаться и повторно передаваться, и повторно передаваться будут только потерянные фрагменты, а не все сообщение.

Синхронные и асинхронные протоколы¶

Один из способов охарактеризовать протокол — определить, является ли он синхронным или асинхронным . Точное значение этих терминов зависит от того, где в иерархии протоколов вы их используете. На транспортном уровне точнее всего думать о них как о определяющих крайние точки спектра, а не как о двух взаимоисключающих альтернативах. Ключевым атрибутом любой точки в спектре является то, что процесс отправки знает после того, как операция по отправке сообщения возвращается. Другими словами, если мы предположим, что прикладная программа вызывает операцию send в транспортном протоколе, то именно то, что приложение знает об успехе операции, когда send операция возвращает?

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

Хотя мы не обсуждали их в этой главе, между этими двумя крайностями есть интересные моменты.. Например, транспортный протокол может реализовать send так, чтобы он блокировался (не возвращался) до тех пор, пока сообщение не было успешно получено на удаленной машине, но возвращалось до того, как одноранговый узел отправителя на этой машине фактически обработал и ответил на Это. Иногда это называют протоколом надежных дейтаграмм .

Реализации RPC (SunRPC, DCE, gRPC) ¶

Теперь мы перейдем к обсуждению некоторых примеров реализации протоколов RPC. Они будут служить для освещения некоторых различных проектных решений, принятых разработчиками протоколов. Нашим первым примером является SunRPC, широко используемый протокол RPC, также известный как Open Network ComputingRPC (ONC RPC). Наш второй пример, который мы будем называть DCE-RPC, является частью распределенной вычислительной среды (DCE). DCE — это набор стандартов и программного обеспечения для построения распределенных систем, который был определен Open Software Foundation (OSF), консорциумом компьютерных компаний, в который первоначально входили IBM, Digital Equipment Corporation и Hewlett-Packard; сегодня OSF носит название Open Group. Наш третий пример — это gRPC, популярный механизм RPC, открытый для Google, на основе механизма RPC, который они использовали внутри компании для реализации облачных сервисов в своих центрах обработки данных.

Эти три примера представляют собой интересные альтернативные варианты дизайна в В области решений RPC, но по крайней мере, вы думаете, что они единственные варианты, мы описываем три других RPC-подобных механизма (WSDL, SOAP и REST) ​​в контексте веб-сервисов в главе 9.

SunRPC¶

SunRPC стал де-факто стандартом благодаря широкому распространению среди рабочих станций Sun и центральной роли, которую он играет в популярной сетевой файловой системе Sun (NFS). . Впоследствии IETF принял его в качестве стандартного Интернет-протокола под названием ONC RPC.

SunRPC может быть реализован по нескольким различным транспортным протоколам. На рисунке 141 показан граф протокола, когда SunRPC реализован на UDP. Как мы отмечали ранее в этом разделе, строгий специалист по уровням может не одобрять идею запуска транспортного протокола поверх транспортного протокола или утверждать, что RPC должен быть чем-то отличным от транспортного протокола, поскольку он находится «над» транспортным уровнем. С прагматической точки зрения проектное решение о запуске RPC на существующем транспортном уровне имеет довольно большой смысл, как будет показано в следующем обсуждении.

Рисунок 141. Граф протокола для SunRPC поверх UDP.

SunRPC использует двухуровневые идентификаторы для идентификации удаленных процедур: a32- битовый номер программы и 32-битный номер процедуры. (Существует также 32-битный номер версии, но мы игнорируем его в следующем обсуждении.. ) Например, серверу NFS назначен номер программы x00100003 , а в этой программе getattr есть процедура 1 , setattr — процедура 2 , read — процедура 6 , write — это процедура 8 и так далее. Номер программы и номер процедуры передаются в заголовке сообщения запроса SunRPC, поля которого показаны на рисунке 142. Сервер, который может поддерживать несколько номеров программ, отвечает за вызов указанной процедуры указанной программы. Запрос SunRPC действительно представляет собой запрос на вызов указанной программы и процедуры на конкретном компьютере, на который был отправлен запрос, даже если тот же номер программы может быть реализован на других машинах в той же сети. Таким образом, адрес серверной машины (например, IP-адрес) неявно является третьим от адреса RPC.

Рисунок 142. Форматы заголовков SunRPC: (a) запрос; (б) ответ.

Различные номера программ могут принадлежать разным серверам на одной машине. Эти разные серверы имеют разные демультиплексоры транспортного уровня (например, порты UDP), большинство из которых не являются хорошо известными номерами, а назначаются динамически. Эти ключи демультиплексирования называются transportselectors . Как может клиент SunRPC, который хочет поговорить с конкретной программой, определить, какой транспортный селектор использовать для доступа к соответствующему серверу? Решение состоит в том, чтобы назначить общеизвестный адрес только одной программе на удаленном компьютере и позволить этой программе выполнять задачу по сообщению клиентам, какой транспортный селектор использовать для доступа к любой другой программе на машине. Исходная версия этой программы SunRPC называется Port Mapper , и она поддерживает только протоколы UDP и TCP. Его номер программы — x00100000 , а его хорошо известный порт — 111 . RPCBIND, который произошел от Port Mapper, поддерживает произвольные базовые транспортные протоколы. При запуске каждого сервера SunRPC он вызывает процедуру регистрации RPCBIND на собственной домашней машине сервера для регистрации своего транспортного селектора и номеров программ, которые он поддерживает. Затем удаленный клиент может вызвать процедуру поиска RPCBIND для поиска транспортного селектора для определенного номера программы.

Чтобы сделать это более конкретным, рассмотрим пример использования Port Mapper с UDP. Чтобы отправить сообщение запроса в процедуру NFS read , клиент сначала отправляет сообщение запроса в Port Mapper на хорошо известный порт UDP 111 , запрашивая эту процедуру 3 будет вызван для сопоставления номера программы x00100003 с портом UDP, на котором в настоящее время находится программа NFS.. Затем клиент отправляет сообщение запроса SunRPC с номером программы x00100003 и номером процедуры 6 на этот порт UDP, и модуль SunRPC, прослушивающий этот порт, вызывает NFS прочтите процедуру. Клиент также кэширует сопоставление номеров программ и портов, чтобы ему не приходилось возвращаться к сопоставителю портов каждый раз, когда он хочет поговорить с программой NFS. [1]

[1] На практике NFS — такая важная программа, что ему предоставлен собственный хорошо известный порт UDP, но в целях иллюстрации мы делаем вид, что это не так.

Чтобы сопоставить ответное сообщение с соответствующим запросом, так что результат RPC может быть возвращен правильной вызывающей стороне, заголовки как запроса, так и ответного сообщения включают поле XID (идентификатор транзакции), как показано на рисунке 142. A XID — это уникальный идентификатор транзакции, используемый только одним запросом и соответствующим ответом. После того, как сервер успешно ответил на данный запрос, он не запоминает XID . Из-за этого SunRPC не может гарантировать семантику «максимум один раз».

Детали семантики SunRPC зависят от базового транспортного протокола. Он не реализует собственную надежность, поэтому он является надежным только в том случае, если надежен базовый транспорт. (Конечно, любое приложение, работающее через SunRPC, может также реализовать свои собственные механизмы надежности выше уровня SunRPC.) Возможность отправлять запросы и отвечать на сообщения, размер которых превышает MTU сети, также зависит от базового транспорта. Другими словами, SunRPC не делает никаких попыток улучшить базовый транспорт, когда дело касается надежности и размера сообщения. Поскольку SunRPC может работать с множеством различных транспортных протоколов, это дает ему значительную гибкость без усложнения конструкции самого протокола RPC.

Возвращаясь к формату заголовка SunRPC, показанному на рисунке 142, сообщение запроса содержит переменную длину Credentials и Verifier , оба из которых используются клиентом для аутентификации на сервере, то есть для подтверждения того, что клиент имеет право вызывать сервер. Как клиент аутентифицирует себя на сервере — это общая проблема, которую должен решать любой протокол, который хочет обеспечить разумный уровень безопасности. Эта тема более подробно обсуждается в другой главе.

DCE-RPC¶

DCE-RPC — это протокол RPC в Ядро системы DCE и было основой механизма RPC, лежащего в основе DCOM и ActiveX Microsoft. Его можно использовать с компилятором-заглушкой представления сетевых данных (NDR), описанным в другой главе, но он также служит базовым протоколом RPC для архитектуры Common Object Request Broker Architecture (CORBA), которая является отраслевым стандартом для построения распределенных объектно-ориентированных системы.

DCE-RPC, как и SunRPC, может быть реализован поверх нескольких транспортных протоколов, включая UDP и TCP. Он также похож на SunRPC в том, что он определяет двухуровневую схему адресации: транспортный протокол демультиплексирует на правильный сервер, DCE-RPC отправляет на конкретную процедуру, экспортированную этим сервером, и клиенты обращаются к «службе отображения конечных точек» (аналогично Port Mapper в SunRPC. ), чтобы узнать, как добраться до конкретного сервера. Однако, в отличие от SunRPC, DCE-RPC реализует семантику однократного вызова. (На самом деле DCE-RPC поддерживает несколько callsemantics, включая идемпотентную семантику, аналогичную SunRPC, но по умолчанию используется butat-most-once.) Между этими двумя подходами есть и другие различия, которые мы выделим в следующих параграфах.

Рисунок 143. Типичный обмен сообщениями DCE-RPC.

На рис. 143 показан график типичного обмена сообщениями, где каждое сообщение помечено своим типом DCE-RPC. Клиент отправляет сообщение Request , сервер в конечном итоге отвечает сообщением Response , и клиент подтверждает ( Ack ) ответ . Однако вместо подтверждения сервером сообщений запроса клиент периодически отправляет на сервер сообщение Ping , которое отвечает сообщением Working , чтобы указать, что удаленная процедура все еще продолжается. Если ответ сервера получен достаточно быстро, запросы Ping не отправляются. Хотя это не показано на рисунке, также поддерживаются другие типы сообщений. Например, клиент может отправить серверу сообщение Quit с просьбой прервать предыдущий вызов, который все еще выполняется; сервер отвечает сообщением Quack (подтверждение выхода). Кроме того, сервер может ответить на сообщение Request сообщением Reject (указывающим, что вызов был отклонен), и он может ответить на Ping с сообщением Nocall (указывающим, что сервер никогда не слышал о вызывающей программе).

Каждая транзакция запроса/ответа в DCE- RPC выполняется в контексте активности . Активность — это логический канал запроса/ответа между парой участников. В любой момент времени на данном канале может быть только одно сообщение-транзакция. Подобно подходу с параллельными логическими каналами, описанному выше, прикладные программы должны открывать несколько каналов, если они хотят иметь более одной транзакции запроса/ответа между ними одновременно. Активность, к которой относится сообщение, определяется полем сообщения ActivityId . Поле SequenceNum затем различает вызовы, сделанные как часть одного и того же действия; он служит той же цели, что и поле SunRPC XID (идентификатор транзакции). В отличие от SunRPC, DCE-RPC отслеживает последний порядковый номер, использованный как часть определенного действия, чтобы гарантировать семантику как можно чаще. Чтобы различать ответы, отправленные до и после перезагрузки серверного компьютера, DCE-RPC использует поле ServerBoot для хранения идентификатора загрузки компьютера.

Другой вариант дизайна, сделанный в DCE- RPC, который отличается от SunRPC, — это поддержка фрагментации и повторной сборки в протоколе RPC. Как отмечалось выше, даже если базовый протокол, такой как IP, обеспечивает фрагментацию/повторную сборку, более сложный алгоритм, реализованный как часть RPC, может привести к более быстрому восстановлению и снижению потребления полосы пропускания при потере фрагментов. Поле FragmentNum однозначно идентифицирует каждый фрагмент, составляющий данное сообщение запроса или ответа. Каждому фрагменту DCE-RPC присваивается уникальный номер фрагмента (0, 1, 2, 3 и т. Д.). И клиент, и сервер реализуют механизм выборочного подтверждения, который работает следующим образом. (Мы описываем механизм в терминах отправки клиентом фрагментированного сообщения запроса на сервер; тот же механизм применяется, когда сервер отправляет клиенту фрагмент ответа.)

Во-первых, каждый фрагмент, составляющий запрос. сообщение содержит как уникальный FragmentNum , так и флаг, указывающий, является ли этот пакет фрагментом вызова ( frag ) или последним фрагментом вызова (); requestmessages, которые помещаются в один пакет, несут флаг. Сервер знает, что он получил сообщение с полным запросом, когда у него есть пакет и что в номерах фрагментов нет пробелов. Во-вторых, в ответ на каждый загружаемый фрагмент сервер отправляет клиенту сообщение Fack (подтверждение фрагмента). Это подтверждение определяет наивысший номер фрагмента, который сервер успешно получил. Другими словами, подтверждение является кумулятивным, как в TCP. Кроме того, однако, сервер выборочно подтверждает получение неупорядоченных фрагментов с более высокими номерами. Он делает это с помощью битового вектора, который идентифицирует эти неупорядоченные фрагменты относительно самого высокого полученного упорядоченного фрагмента. Наконец, клиент отвечает, повторно передавая недостающие фрагменты.

Рисунок 144 иллюстрирует, как все это работает. Предположим, что сервер успешно получил фрагменты до 20, плюс фрагменты 23, 25 и 26. Сервер отвечает Fack , который идентифицирует фрагмент 20 как фрагмент наивысшего порядка, плюс abit- вектор ( SelAck ) с включенными третьим (23 = 20 + 3), пятым (25 = 20 + 5) и шестым (26 = 20 + 6) битами. Чтобы поддерживать (почти) произвольно длинный битовый вектор, размер вектора (измеренный в 32-битных словах) задается в поле SelAckLen .

Рис. 144. Фрагментация с выборочными подтверждениями..

Учитывая поддержку DCE-RPC очень больших сообщений — поле FragmentNum имеет длину 16 бит, что означает, что оно может поддерживать фрагменты размером 64 КБ, это неуместно. чтобы протокол обрабатывал все фрагменты, составляющие сообщение, как можно быстрее, так как это может привести к переполнению получателя. Вместо этого DCE-RPC реализует алгоритм управления потоком, который очень похож на TCP. В частности, каждое сообщение Fack не только подтверждает полученные фрагменты, но также информирует отправителя о том, сколько фрагментов оно теперь может отправить. Для этого используется поле WindowSize на рис. 144, которое служит точно той же цели, что и поле TCP AdvertisedWindow , за исключением того, что оно подсчитывает фрагменты, а не байты. DCE-RPC также реализует механизм контроля перегрузки, аналогичный TCP. Учитывая сложность управления перегрузкой, возможно, неудивительно, что некоторые протоколы RPC избегают этого, избегая фрагментации.

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

gRPC¶

Несмотря на свое происхождение от Google, gRPC не означает Google RPC. Буква «g» в каждом выпуске означает что-то свое. Для версии 1.10 он означал «гламурный», а для версии 1.18 — «гусь». Гуглеры — дикие и сумасшедшие люди. Тем не менее gRPC популярен, потому что он делает доступным для всех — как открытый исходный код — десятилетний опыт использования RPC в Google для создания масштабируемых облачных сервисов.

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

В облачных службах клиент вызывает метод в службе , который для поддержки вызовов от произвольно большого количества клиентов одновременно, реализуется с помощью масштабируемого числа серверных процессов, каждый из которых может быть запущен на другом сервере. Именно здесь в игру вступает облако: центры обработки данных предоставляют, казалось бы, бесконечное количество серверных машин, доступных для масштабирования облачных сервисов. Когда мы используем термин «масштабируемый», мы имеем в виду, что количество идентичных серверных процессов, которые вы выбираете для создания, зависит от рабочей нагрузки (т. Е. Количества клиентов, которым требуется обслуживание в любой момент времени), и это количество может динамически корректироваться с течением времени.. Еще одна деталь заключается в том, что облачные службы обычно не создают новый процесс сами по себе, а скорее запускают новый контейнер , который по сути представляет собой процесс, инкапсулированный внутри изолированной среды, включающей все программное обеспечение. упаковывает процесс, который необходимо запустить. Docker — это сегодняшний канонический пример контейнерной платформы.

Рисунок 145. Использование RPC для вызвать масштабируемую облачную службу.

Вернемся к утверждению, что служба, по сути, представляет собой дополнительный уровень направления, расположенный поверх сервера, все это означает, что вызывающий абонент идентифицирует службу, которую он хочет для вызова, и балансировщик нагрузки направляет этот вызов одному из множества доступных серверных процессов (контейнеров), реализующих эту службу, как показано на Рисунке 145. Балансировщик нагрузки может быть реализован различными способами, в том числе на аппаратном устройстве, но обычно он реализуется с помощью процесса aproxy, который выполняется на виртуальной машине (также размещенной в облаке), а не как физическое устройство.

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

Нас интересует транспортный протокол, лежащий в основе gRPC. Здесь снова наблюдается серьезное отклонение от двух предыдущих примеров протоколов не с точки зрения фундаментальных проблем, которые необходимо решить, а с точки зрения подхода gRPC к их решению. Короче говоря, gRPC «передает» многие проблемы другим протоколам, оставляя gRPC по существу упаковать эти возможности в простой в использовании форме. Давайте рассмотрим детали.

Во-первых, gRPC работает поверх TCP вместо UDP, что означает, что он передает на аутсорсинг проблемы управления подключением и надежной передачи сообщений запросов и ответов произвольного размера. Во-вторых, gRPC на самом деле работает поверх защищенной версии TCP, называемой Transport Layer Security (TLS) — нижнего уровня, который находится над TCP в стеке протоколов, — что означает, что он передает ответственность за безопасность канала связи от злоумышленников. не может подслушивать или перехватить обмен сообщениями. В-третьих, gRPC фактически работает поверх HTTP/2 (который, в свою очередь, находится поверх TCP и TLS), что означает, что gRPC передает на аутсорсинг еще две другие проблемы: (1) эффективное кодирование/сжатие двоичных данных в сообщение, (2) мультиплексирование нескольких удаленная процедура вызывает одно TCP-соединение. Другими словами, gRPC кодирует идентификатор для удаленного метода как URI, параметры запроса удаленного метода как содержимое в HTTP-сообщении, а возвращаемое значение удаленного метода в HTTP-ответе.. Полный стек gRPC изображен на рисунке 146, который также включает в себя элементы, зависящие от языка (одной из сильных сторон gRPC является широкий набор языков программирования, которые он поддерживает, лишь небольшое подмножество показано на рисунке 146.)

Рис. 146. Ядро gRPC, установленное поверх HTTP, TLS и TCP и поддерживающее набор языков.

Мы обсуждаем TLS в главе 8 (в контексте широкого спектра тем по безопасности) и HTTP в главе 9 (в контексте того, что традиционно считается протоколами уровня приложений). Но мы находимся в интересном цикле зависимостей: RPC — разновидность транспортного протокола, используемого для реализации распределенных приложений, HTTP — пример протокола уровня приложения, и все же gRPC работает поверх HTTP, а не наоборот.

Краткое объяснение состоит в том, что разбиение на уровни дает людям удобный способ изучить сложные системы, но на самом деле мы пытаемся решить ряд проблем (например, надежно передавать сообщения произвольного размера, идентифицировать отправителей и получателей , сопоставление сообщений-запросов с ответными сообщениями и т. д.), и то, как эти решения объединяются в протоколы, а эти протоколы затем накладываются друг на друга, является следствием постепенных изменений с течением времени. Можно поспорить, что это историческая случайность. Если бы Интернет начинался с такого повсеместного механизма RPC, как TCP, HTTP мог бы быть реализован поверх него (как, возможно, почти все другие протоколы уровня приложений, описанные в главе 9), и Google потратил бы свое время на улучшение этот протокол, а не изобретать свой собственный (как они и другие делали с TCP). Вместо этого произошло то, что Интернет превратился в приложение-убийцу Интернета, а это означало, что его протокол приложения (HTTP) стал повсеместно поддерживаться остальной частью инфраструктуры Интернета: межсетевыми экранами, балансировщиками нагрузки, шифрованием, аутентификацией, сжатием и т. Д. Поскольку все эти сетевые элементы были разработаны для хорошей работы с HTTP, HTTP фактически стал универсальным транспортным протоколом Интернет-запросов/ответов.

Возвращаясь к уникальным характеристикам gRPC, наибольшую ценность он представляет для таблицы заключается во включении потоковой передачи в механизм RPC, то есть gRPC поддерживает четыре различных шаблона запроса/ответа:

  1. Простой RPC: клиент отправляет одно сообщение запроса, и сервер отвечает одним сообщением ответа.
  2. Server Streaming RPC: клиент отправляет одно сообщение запроса, а сервер отвечает потоком сообщений ответа. Клиент завершает работу после того, как получит все ответы сервера.
  3. Клиент RPC с потоковой передачей: клиент отправляет поток запросов на сервер, а сервер отправляет обратно один ответ, обычно (но не обязательно) после того, как он получил все запросы клиента.
  4. Двунаправленный потоковый RPC: вызов инициируется клиентом, но после этого клиент и сервер могут читать и писать запросы и отвечать в любом порядке; потоки полностью независимы.

Эта дополнительная свобода во взаимодействии клиента и сервера означает, что протокол gRPCtransport должен отправлять дополнительные метаданные и управляющие сообщения в дополнение к фактическим сообщениям запроса и ответа — между двумя сверстниками. Примеры включают коды Error и Status (чтобы указать успех или причину сбоя), Тайм-ауты (чтобы указать, как долго клиент готовы дождаться ответа), PING (уведомление о сохранении активности, указывающее, что одна или другая сторона все еще работает), EOS (конец -stream уведомление, чтобы указать, что больше нет запросов или ответов) и GOAWAY (уведомление от серверов клиентам, чтобы указать, что они больше не будут принимать какие-либо новые потоки). В отличие от многих других протоколов в этой книге, где мы показываем формат заголовка протокола, то, как эта управляющая информация передается между двумя сторонами, во многом определяется основным транспортным протоколом, в данном случае HTTP/2. Например, как мы увидим в главе 9, HTTP уже включает набор полей заголовков и кодов ответов, которые используются в gRPC.

Вы можете просмотреть обсуждение HTTP в главе 9, прежде чем продолжить, но следующее довольно просто. Простой RPCrequest (без потоковой передачи) может включать следующее HTTP-сообщение от клиента к серверу:

 HEADERS (flags = END_HEADERS): method = POST:  scheme = http: path =/google.pubsub.v2.PublisherService/CreateTopic:authority = pubsub.googleapis.comgrpc-timeout = 1Scontent-type = application/grpc + protogrpc-encoding = gzipauthorization = носитель y235.wef315yfh138   

, ведущее к следующему ответному сообщению от сервера обратно клиенту:

 HEADERS (flags = END_HEADERS): status = 200grpc-encoding = gzipcontent-type = application/grpc + protoDATA  ЗАГОЛОВКИ (flags = END_STREAM, END_HEADERS) grpc-status = 0 # OKtrace-proto  -bin = jher831yy13JHy3hc 

В этом примере HEADERS и DATA — это два стандартных элемента управления HTTP сообщения, которые эффективно разграничивают «заголовок сообщения» и «m полезная нагрузка essage «. В частности, каждая строка после HEADERS (но перед DATA ) представляет собой пару attribute = value , которая составляет заголовок (подумайте каждой строки аналогично полю заголовка); те пары, которые начинаются с двоеточия (например, : status = 200 ), являются частью стандарта HTTP (например, status 200 указывает на успех); а те пары, которые не начинаются с двоеточия, — это специфичные для gRPC настройки (например,. , grpc-encoding = gzip указывает, что данные в следующем сообщении были сжаты с помощью gzip , а grpc-timeout = 1S указывает, что у клиента установлен тайм-аут в одну секунду).

Осталось объяснить еще один момент. Строка заголовка

 content-type = application/grpc + proto 

указывает, что тело сообщения (обозначенное линией DATA ) имеет значение только для прикладной программы (т. е. метода сервера), у которой этот клиент запрашивает обслуживание. Более конкретно, строка + proto указывает, что получатель сможет интерпретировать биты в сообщении в соответствии с ProtocolBuffer (сокращенно proto ) спецификация интерфейса. ProtocolBuffers — это способ gRPC, определяющий, как параметры, передаваемые на сервер, кодируются в сообщение, которое, в свою очередь, используется для генерации заглушек, которые находятся между базовым механизмом RPC и фактическими вызываемыми функциями (см. Рисунок 138). Это тема, которую мы рассмотрим в главе 7.

Ключевые выводы

Суть в том, что сложные механизмы, такие как RPC, однажды упакованные как монолитный пакет программного обеспечения (как в случае с SunRPC и DCE-RPC), в настоящее время создается путем сборки набора более мелких частей, каждая из которых решает узкую проблему. gRPC является и примером этого подхода, и инструментом, который обеспечивает дальнейшее внедрение этого подхода. Архитектура микросервисов, упомянутая ранее в этом подразделе, применяет стратегию «построение из мелких частей» ко всем облачным приложениям (например, Uber, Lyft, Netflix, Yelp , Spotify), где gRPC часто является механизмом связи, используемым этими небольшими частями для обмена сообщениями друг с другом. [Далее]



Что такое процедура?

процедура может относиться к любому из следующего:

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

2. В программировании базы данных хранимая процедура — это набор программного кода (например, PL/SQL), который выполняет определенный запрос или функцию. Эта хранимая процедура часто используется для выполнения одной или нескольких серий команд, поиска, вставки, обновления или удаления данных в базе данных.

Язык программирования, Условия программирования, Подпрограмма

Оцените статью
logicle.ru
Добавить комментарий