1. Введение
В этой лабораторной работе вы будете использовать gRPC-Go для создания клиента и сервера, которые составят основу приложения для сопоставления маршрутов, написанного на Go.
К концу руководства у вас будет клиент, который подключается к удалённому серверу с помощью gRPC для получения названия или почтового адреса объекта, расположенного в определённых координатах на карте. Полноценное приложение может использовать эту клиент-серверную архитектуру для перечисления или суммирования точек интереса на маршруте.
Служба определена в файле Protocol Buffers, который будет использоваться для генерации шаблонного кода для клиента и сервера, чтобы они могли взаимодействовать друг с другом, экономя ваше время и усилия при реализации этой функциональности.
Сгенерированный код учитывает не только сложности взаимодействия между сервером и клиентом, но также сериализацию и десериализацию данных.
Чему вы научитесь
- Как использовать Protocol Buffers для определения API сервиса.
- Как создать клиент и сервер на основе gRPC из определения Protocol Buffers с использованием автоматической генерации кода.
- Понимание клиент-серверного взаимодействия с помощью gRPC.
Эта практическая работа предназначена для разработчиков на Go, впервые использующих gRPC или желающих освежить свои знания, а также для всех, кто интересуется разработкой распределённых систем. Опыт работы с gRPC не требуется.
2. Прежде чем начать
Предпосылки
Убедитесь, что у вас установлено следующее:
- Набор инструментов Go версии 1.24.5 или более поздней. Инструкции по установке см. в руководстве по началу работы с Go.
- Компилятор буфера протокола
protoc
версии 3.27.1 или более поздней. Инструкции по установке см. в руководстве по установке компилятора. - Плагины компилятора буфера протокола для Go и gRPC. Чтобы установить эти плагины, выполните следующие команды:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Обновите переменную PATH
, чтобы компилятор буфера протокола мог найти плагины:
export PATH="$PATH:$(go env GOPATH)/bin"
Получить код
Чтобы вам не пришлось начинать всё с нуля, эта лабораторная работа предоставляет вам заготовку исходного кода приложения. Следующие шаги покажут вам, как завершить приложение, включая использование плагинов компилятора буфера протокола для генерации шаблонного кода gRPC.
Загрузите этот исходный код в виде архива .ZIP с GitHub и распакуйте его содержимое.
Кроме того, готовый исходный код доступен на GitHub, если вы не хотите вводить реализацию вручную.
3. Определите услугу
Первым шагом будет определение службы gRPC приложения, её метода RPC и типов сообщений запросов и ответов с помощью Protocol Buffers . Ваша служба будет предоставлять:
- Метод RPC, называемый
GetFeature
, который реализует сервер и вызывает клиент. - Типы сообщений
Point
иFeature
представляют собой структуры данных, которыми обмениваются клиент и сервер при использовании методаGetFeature
. Клиент предоставляет координаты карты в качествеPoint
в своём запросеGetFeature
к серверу, а сервер отвечает соответствующимFeature
, описывающим то, что находится в этих координатах.
Этот метод RPC и его типы сообщений будут определены в файле routeguide/route_guide.proto
предоставленного исходного кода.
Буферы протоколов обычно называются protobuf. Подробнее о терминологии gRPC см. в разделе «Основные концепции, архитектура и жизненный цикл gRPC».
Типы сообщений
В файле routeguide/route_guide.proto
исходного кода сначала определите тип сообщения Point
. Point
представляет собой пару координат (широта-долгота) на карте. В этой практической работе используйте целые числа в качестве координат:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Цифры 1
и 2
— это уникальные идентификационные номера для каждого из полей в структуре message
.
Затем определите тип сообщения Feature
. Feature
использует string
поле для имени или почтового адреса объекта, расположенного в месте, указанном Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Метод обслуживания
Файл route_guide.proto
имеет структуру service
с именем RouteGuide
, которая определяет один или несколько методов, предоставляемых службой приложения.
Добавьте метод rpc
GetFeature
в определение RouteGuide
. Как объяснялось ранее, этот метод ищет название или адрес местоположения по заданному набору координат, поэтому GetFeature
должен возвращать Feature
для заданной Point
:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Это унарный метод RPC: простой RPC , при котором клиент отправляет запрос серверу и ждет ответа, как при локальном вызове функции.
4. Сгенерируйте клиентский и серверный код.
Затем сгенерируйте шаблонный код gRPC для клиента и сервера из файла .proto
с помощью компилятора Protocol Buffer. В каталоге routeguide
выполните:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Эта команда генерирует следующие файлы:
-
route_guide.pb.go
, который содержит функции для создания типов сообщений приложения и доступа к их данным. -
route_guide_grpc.pb.go
, который содержит функции, используемые клиентом для вызова удаленного метода gRPC службы, и функции, используемые сервером для предоставления этой удаленной службы.
Далее мы реализуем метод GetFeature
на стороне сервера, чтобы сервер мог ответить, когда клиент отправляет запрос.
5. Реализуйте услугу
Функция GetFeature
на стороне сервера выполняет основную работу: она принимает сообщение Point
от клиента и возвращает в сообщении Feature
соответствующую информацию о местоположении из списка известных мест. Вот реализация функции в server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
При вызове этого метода после запроса от удалённого клиента функции передаются объект Context
описывающий RPC-вызов, и объект буфера протокола Point
из этого клиентского запроса. Функция возвращает объект буфера протокола Feature
для искомого местоположения и при необходимости error
.
В методе заполните объект Feature
соответствующей информацией для заданной Point
, а затем return
его вместе с nil
ошибкой, чтобы сообщить gRPC, что вы завершили работу с RPC и что объект Feature
можно вернуть клиенту.
Метод GetFeature
требует создания и регистрации объекта routeGuideServer
, чтобы запросы клиентов на поиск местоположения можно было направлять в эту функцию. Это делается в main()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
Вот что происходит в main()
, шаг за шагом:
- Укажите TCP-порт для прослушивания запросов удалённых клиентов с помощью
lis, err := net.Listen(...)
. По умолчанию приложение использует TCP-порт50051
, указанный переменнойport
или параметром--port
в командной строке при запуске сервера. Если TCP-порт не удаётся открыть, приложение завершает работу с фатальной ошибкой. - Создайте экземпляр сервера gRPC с помощью
grpc.NewServer(...)
, назвав этот экземплярgrpcServer
. - Создайте указатель на
routeGuideServer
, структуру, представляющую службу API приложения, назвав указательs.
- Используйте
s.loadFeatures()
для заполнения массиваs.savedFeatures
местоположениями, которые можно найти с помощьюGetFeature
. - Зарегистрируйте службу API на сервере gRPC, чтобы вызовы RPC к
GetFeature
направлялись в соответствующую функцию. - Вызовите
Serve()
на сервере с данными нашего порта, чтобы выполнить блокирующее ожидание клиентских запросов; это продолжается до тех пор, пока процесс не будет завершен или не будет вызванStop()
.
Функция loadFeatures()
получает сопоставления координат и местоположения из server/testdata.go
.
6. Создайте клиента
Теперь отредактируйте client/client.go
, в котором вы будете реализовывать клиентский код.
Для вызова методов удалённой службы сначала необходимо создать канал gRPC для взаимодействия с сервером. Мы создаём его, передавая строку целевого URI сервера (в данном случае это просто адрес и номер порта) в grpc.NewClient()
в функции main()
клиента следующим образом:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
Адрес сервера, определяемый переменной serverAddr
, по умолчанию равен localhost:50051
и может быть переопределен с помощью переключателя --addr
в командной строке при запуске клиента.
Если клиенту необходимо подключиться к службе, требующей учётных данных аутентификации, например, TLS или JWT, он может передать объект DialOptions
в качестве параметра методу grpc.NewClient
, содержащему необходимые учётные данные. Служба RouteGuide
не требует никаких учётных данных.
После настройки канала gRPC нам понадобится клиентская заглушка для выполнения RPC-вызовов через вызовы функций Go. Мы получаем эту заглушку с помощью метода NewRouteGuideClient
, предоставленного файлом route_guide_grpc.pb.go
, сгенерированным из файла .proto
приложения.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Методы обслуживания вызовов
В gRPC-Go RPC работают в блокирующем/синхронном режиме, что означает, что вызов RPC ждет ответа сервера и либо возвращает ответ, либо ошибку.
Простой RPC
Вызов простого RPC GetFeature
почти так же прост, как вызов локального метода, в данном случае client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
Клиент вызывает метод созданной ранее заглушки. Для параметров метода клиент создаёт и заполняет объект буфера протокола запроса Point
. Вы также передаёте объект context.Context
, который позволяет при необходимости изменить поведение RPC, например, установить ограничение по времени вызова или отменить RPC в процессе выполнения. Если вызов не возвращает ошибку, клиент может прочитать информацию об ответе сервера из первого возвращаемого значения:
log.Println(feature)
В целом функция main()
клиента должна выглядеть так:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. Попробуйте
Убедитесь, что сервер и клиент работают друг с другом корректно, выполнив следующие команды в рабочем каталоге приложения:
- Запустите сервер в одном терминале:
cd server go run .
- Запустите клиент с другого терминала:
cd client go run .
Вы увидите примерно такой вывод, при этом временные метки опущены для ясности:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. Что дальше?
- Узнайте, как работает gRPC, изучив раздел «Введение в gRPC» и «Основные концепции».
- Проработайте базовый учебник
- Изучите справочник API