从零单排之gRPC初体验
初衷:想用go开发一个类Ansible运维工具
Ansible性能较差并且太老派了不想用
调研了一下agent端基本要从json-rpc和grpc中选型
学都学了,就学个主流新潮的
八股文
什么是gRPC?
想象一下,你和基友在不同的密室里,你们想要聊天并交换小秘密。这时候,你们需要一个高效、可靠的通信方式
摩斯密码。gRPC就是这样一种让服务之间像朋友一样轻松交流的工具。gRPC(Google Remote Procedure Call)是谷歌推出的高性能通信框架。它使用HTTP/2作为传输协议,Protobuf作为数据序列化方式。换句话说,gRPC让你的服务能够快速、可靠地互相打电话,还能彼此理解复杂的“悄悄话”。
gRPC的QWER技能
极速传输:gRPC搭载HTTP/2这辆跑车,数据传输快如闪电,让你几乎感觉不到延迟。
多语言支持:无论你是用Java、Python、Go,还是其他语言编写的服务,gRPC都能让它们无障碍交流。
代码自动生成:只需定义一次接口,gRPC就能帮你自动生成客户端和服务端代码,减少重复劳动。
流式传输:像抖音直播一样,支持双向数据流,让通信变得更加实时、顺畅。
什么是gRPC-Go?
gRPC-Go是gRPC的Go语言实现版本。它是Go开发者的秘密武器,让你在使用Go语言时,也能享受到gRPC带来的各种超能力。通过gRPC-Go,你可以快速构建高性能、可扩展的服务,让你的分布式系统像精密的机器一样高效运转。
gRPC-Go的魅力
简单上手:通过Protobuf定义接口,gRPC-Go帮你生成所有需要的代码,让你省去很多麻烦。
高效可靠:基于HTTP/2和Protobuf,gRPC-Go让你的服务通信又快又稳。
生态系统强大:与Go的标准库和社区库完美融合,让你可以轻松扩展和集成各种功能。
上手
参考一下官方示例
https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto
关键字
syntax:是必须写的,而且要定义在第一行;目前proto3是主流(引入了许多改进和简化),不写默认使用proto2
option go_package:定义生成的pb.go的包名,我们通常在proto文件中定义。如果不在proto文件中定义,也可以在使用protoc生成代码时指定pb.go文件的包名
package:定义我们proto文件的包名
service: 服务是一组方法的集合。定义了客户端可以调用的远程过程。
message:非常重要,用于定义消息结构体
分配标识号
string name =1;
这个1
是什么?
这些数字是“分配表示号”:在消息定义中,每个字段后面都有一个唯一的数字,序号用于标识字段在序列化和反序列化中的顺序和唯一性。这个就是标识号。
小技巧:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。
所以这里也可以保留一些标识号,留给以后用。举个🌰
message HelloRequest {
reserved 2, 5, 7 to 10; // 保留2,5,7到10这些标识号
}
如果使用了这些保留的标识号,protocol buffer编译器无法编译通过,将会输出警告信息。
创建grpc demo项目
配置环境
# Mac的话这里直接用homebrew安装
brew install protobuf
# 初始化项目
go mod init grpc
# protoc-gen-go 主要用来生成 go代码
# protoc-gen-go-grpc 生成grpc代码
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 配置一下环境变量到zshrc或bashrc,看你终端用的啥
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.zshrc
source ~/.zshrc
编写hello.proto
syntax = "proto3";
option go_package = "./;hello";
package hello;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
生成go代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
tree
.
├── go.mod
├── go.sum
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
hello.pb.go:
处理消息类型的定义和序列化/反序列化,主要用于消息的创建、操作和传输。
这个文件主要负责 Protocol Buffers(protobuf)的消息类型定义和序列化/反序列化逻辑。
当你需要创建、读取、更新、或删除消息类型的实例时,你会使用到这个文件。
任何涉及到消息类型的序列化(发送数据)或反序列化(接收数据)时,也会使用到这个文件。
hello_grpc.pb.go:
处理 gRPC 服务和客户端的接口和基础实现,主要用于定义服务端和客户端的行为以及服务注册。
实现gRPC服务端喝客户端远程调用,以及服务注册到gRPC时使用
编写一下服务端与客户端
服务端
项目内创建server/main.go
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "grpc"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端
项目内创建client/main.go
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package main implements a client for Greeter service.
package main
import (
"context"
"flag"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
通信一下
go run server/main.go
2024/06/13 15:47:08 server listening at [::]:50051
2024/06/13 15:48:34 Received: world
go run client/main.go
2024/06/13 15:48:34 Greeting: Hello world