1. Введение
В этом практическом занятии вы будете использовать gRPC-Go для создания клиента и сервера, которые станут основой приложения для построения маршрутов, написанного на Go.
К концу этого урока у вас будет клиент, который подключается к удалённому серверу с помощью gRPC , чтобы получить имя или почтовый адрес объекта, расположенного по определённым координатам на карте. Полноценное приложение может использовать эту клиент-серверную архитектуру для перечисления или обобщения точек интереса вдоль маршрута.
Сервис определяется в файле Protocol Buffers, который будет использоваться для генерации шаблонного кода для клиента и сервера, чтобы они могли взаимодействовать друг с другом, экономя ваше время и усилия на реализации этой функциональности.
Сгенерированный код учитывает не только сложности взаимодействия между сервером и клиентом, но и сериализацию и десериализацию данных.
Что вы узнаете
- Как использовать Protocol Buffers для определения API сервиса.
- Как создать клиент и сервер на основе gRPC из определения Protocol Buffers с помощью автоматической генерации кода.
- Понимание взаимодействия клиент-сервер с использованием gRPC.
Данный практический семинар предназначен для разработчиков на Go, которые только начинают работать с gRPC или хотят освежить свои знания gRPC, а также для всех, кто заинтересован в создании распределенных систем. Предварительный опыт работы с gRPC не требуется.
2. Прежде чем начать
Предварительные требования
Убедитесь, что у вас установлены следующие компоненты:
- Требуется инструментарий Go версии 1.24.5 или более поздней. Инструкции по установке см. в разделе « Начало работы с Go».
- Компилятор протокола Protocol Buffer,
protoc, версии 3.27.1 или более поздней. Инструкции по установке см. в руководстве по установке компилятора. - Плагины компилятора протокола Protocol Buffer для 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 , чтобы компилятор протокола Protocol Buffer мог найти плагины:
export PATH="$PATH:$(go env GOPATH)/bin"
Получите код
Чтобы вам не пришлось начинать с нуля, в этом практическом руководстве представлен шаблон исходного кода приложения, который вы сможете доработать. Следующие шаги покажут вам, как завершить приложение, включая использование плагинов компилятора Protocol Buffer для генерации шаблонного кода gRPC.
Загрузите исходный код в виде ZIP-архива с GitHub и распакуйте его содержимое.
В качестве альтернативы, полный исходный код доступен на GitHub, если вы хотите избежать необходимости вводить код реализации вручную.
3. Определите услугу.
Первым шагом является определение gRPC-сервиса приложения, его RPC-метода, а также типов сообщений запроса и ответа с помощью Protocol Buffers . Ваш сервис будет предоставлять:
- Метод RPC под названием
GetFeature, который реализует сервер, а клиент вызывает. - Типы сообщений
PointиFeatureпредставляют собой структуры данных, которыми обмениваются клиент и сервер при использовании методаGetFeature. Клиент предоставляет координаты карты в видеPointв своем запросеGetFeatureк серверу, а сервер отвечает соответствующимFeature, описывающим то, что находится по этим координатам.
Данный RPC-метод и типы сообщений для него будут определены в файле routeguide/route_guide.proto предоставленного исходного кода.
Протоколы Protocol Buffers обычно называются protobufs. Для получения дополнительной информации о терминологии 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 protocol buffer из этого запроса клиента. Функция возвращает объект Feature protocol buffer для искомого местоположения и, при необходимости, 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 request protocol buffer. Также передается объект 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.