# GO版gRPC開發方法是什么
## 前言
在當今微服務架構盛行的時代,gRPC作為高性能、跨語言的RPC框架已成為服務間通信的重要選擇。Go語言憑借其簡潔的語法、出色的并發模型和卓越的性能,成為gRPC開發的首選語言之一。本文將深入探討如何使用Go語言進行gRPC開發,從基礎概念到高級實踐,全面解析GO版gRPC的開發方法。
## 一、gRPC基礎概念
### 1.1 什么是gRPC
gRPC是Google開發的高性能、開源、通用的RPC框架,基于HTTP/2協議和Protocol Buffers(protobuf)序列化協議。其主要特點包括:
- **跨語言支持**:支持多種編程語言
- **高效的二進制編碼**:使用protobuf進行數據序列化
- **基于HTTP/2**:支持雙向流、頭部壓縮等特性
- **強類型服務定義**:通過.proto文件明確定義服務接口
### 1.2 gRPC vs REST
| 特性 | gRPC | REST |
|---------------|-----------------------|--------------------|
| 協議 | HTTP/2 | HTTP/1.1 |
| 數據格式 | Protocol Buffers | JSON/XML |
| 性能 | 高 | 中等 |
| 流式支持 | 支持 | 有限支持 |
| 瀏覽器支持 | 有限(需要gRPC-Web) | 完全支持 |
## 二、GO版gRPC開發環境搭建
### 2.1 安裝必要工具
```bash
# 安裝Protocol Buffer編譯器
brew install protobuf # macOS
apt-get install protobuf-compiler # Ubuntu
# 安裝Go插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 驗證安裝
protoc --version
/mygrpcproject
├── proto/ # .proto文件目錄
│ └── helloworld.proto
├── client/ # 客戶端代碼
│ └── main.go
├── server/ # 服務端代碼
│ └── main.go
├── go.mod # Go模塊文件
└── go.sum
syntax = "proto3";
option go_package = ".;helloworld";
package helloworld;
// 定義服務
service Greeter {
// 一元RPC
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 服務端流式RPC
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply) {}
// 客戶端流式RPC
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply) {}
// 雙向流式RPC
rpc BidiHello (stream HelloRequest) returns (stream HelloReply) {}
}
// 請求消息
message HelloRequest {
string name = 1;
}
// 響應消息
message HelloReply {
string message = 1;
}
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/helloworld.proto
執行后將生成:
- helloworld.pb.go
:包含消息結構的序列化代碼
- helloworld_grpc.pb.go
:包含服務端和客戶端代碼
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/your/package/proto/helloworld"
)
type server struct {
pb.UnimplementedGreeterServer
}
// 實現SayHello方法
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() {
lis, err := net.Listen("tcp", ":50051")
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)
}
}
// 服務端流式RPC實現
func (s *server) LotsOfReplies(in *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {
for i := 0; i < 5; i++ {
if err := stream.Send(&pb.HelloReply{
Message: fmt.Sprintf("Hello %s %d", in.GetName(), i),
}); err != nil {
return err
}
}
return nil
}
// 雙向流式RPC實現
func (s *server) BidiHello(stream pb.Greeter_BidiHelloServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if err := stream.Send(&pb.HelloReply{
Message: "Hello " + in.GetName(),
}); err != nil {
return err
}
}
}
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "path/to/your/package/proto/helloworld"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
// 服務端流式調用
func callLotsOfReplies(c pb.GreeterClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatalf("error calling LotsOfReplies: %v", err)
}
for {
reply, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("error receiving: %v", err)
}
log.Printf("Reply: %s", reply.GetMessage())
}
}
// 雙向流式調用
func callBidiHello(c pb.GreeterClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := c.BidiHello(ctx)
if err != nil {
log.Fatalf("error calling BidiHello: %v", err)
}
waitc := make(chan struct{})
// 接收協程
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
close(waitc)
return
}
if err != nil {
log.Fatalf("Failed to receive: %v", err)
}
log.Printf("Received: %s", in.GetMessage())
}
}()
// 發送協程
names := []string{"Alice", "Bob", "Charlie"}
for _, name := range names {
if err := stream.Send(&pb.HelloRequest{Name: name}); err != nil {
log.Fatalf("Failed to send: %v", err)
}
}
stream.CloseSend()
<-waitc
}
// 客戶端攔截器
func unaryClientInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 前置處理
start := time.Now()
// 調用RPC方法
err := invoker(ctx, method, req, reply, cc, opts...)
// 后置處理
log.Printf("Invoked RPC method=%s; Duration=%s; Error=%v", method, time.Since(start), err)
return err
}
// 服務端攔截器
func unaryServerInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 前置處理
start := time.Now()
// 調用處理器
resp, err := handler(ctx, req)
// 后置處理
log.Printf("Invoked RPC method=%s; Duration=%s; Error=%v",
info.FullMethod, time.Since(start), err)
return resp, err
}
// 使用攔截器
func main() {
// 客戶端使用
conn, err := grpc.Dial(address,
grpc.WithUnaryInterceptor(unaryClientInterceptor),
grpc.WithStreamInterceptor(streamClientInterceptor))
// 服務端使用
s := grpc.NewServer(
grpc.UnaryInterceptor(unaryServerInterceptor),
grpc.StreamInterceptor(streamServerInterceptor))
}
// 服務端返回錯誤
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
if req.Value < 0 {
return nil, status.Errorf(
codes.InvalidArgument,
"Value cannot be negative: %d", req.Value)
}
// ...正常處理...
}
// 客戶端處理錯誤
resp, err := client.SomeMethod(ctx, &pb.Request{Value: -1})
if err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.InvalidArgument:
log.Printf("Invalid argument: %v", st.Message())
case codes.DeadlineExceeded:
log.Printf("Timeout!")
default:
log.Printf("RPC failed: %v", err)
}
} else {
log.Printf("Non-gRPC error: %v", err)
}
}
// 客戶端發送元數據
md := metadata.Pairs(
"authorization", "Bearer some-token",
"client-version", "1.0.0",
)
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服務端接收元數據
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "metadata missing")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "authorization token missing")
}
// ...驗證token...
}
// 客戶端負載均衡
conn, err := grpc.Dial(
"dns:///my-service.example.com",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
)
// 服務發現集成
resolverBuilder := mycustomresolver.NewBuilder()
conn, err := grpc.Dial(
resolverBuilder.Scheme()+"://authority/my-service",
grpc.WithResolvers(resolverBuilder),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
// 創建帶連接池的客戶端
var (
connPool *grpc.ClientConn
once sync.Once
)
func getClient() pb.GreeterClient {
once.Do(func() {
var err error
connPool, err = grpc.Dial("localhost:50051",
grpc.WithInsecure(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
Timeout: 10 * time.Second,
PermitWithoutStream: true,
}))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
})
return pb.NewGreeterClient(connPool)
}
// 服務端配置
s := grpc.NewServer(
grpc.MaxRecvMsgSize(1024*1024*10), // 10MB
grpc.MaxSendMsgSize(1024*1024*10),
)
// 客戶端配置
conn, err := grpc.Dial(address,
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1024*1024*10),
grpc.MaxCallSendMsgSize(1024*1024*10),
))
// 服務端啟用壓縮
s := grpc.NewServer(
grpc.RPCCompressor(grpc.NewGZIPCompressor()),
grpc.RPCDecompressor(grpc.NewGZIPDecompressor()),
)
// 客戶端調用時壓縮
resp, err := client.SomeMethod(ctx, req,
grpc.UseCompressor("gzip"))
import (
"testing"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024 * 1024
func TestSayHello(t *testing.T) {
// 創建內存監聽器
lis := bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
go func() {
if err := s.Serve(lis); err != nil {
t.Fatalf("Server exited with error: %v", err)
}
}()
defer s.Stop()
// 創建客戶端連接
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := pb.NewGreeterClient(conn)
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "test"})
if err != nil {
t.Fatalf("SayHello failed: %v", err)
}
expected := "Hello test"
if resp.GetMessage() != expected {
t.Errorf("Expected %q, got %q", expected, resp.GetMessage())
}
}
# 安裝grpcurl
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
# 列出服務
grpcurl -plaintext localhost:50051 list
# 調用方法
grpcurl -plaintext -d '{"name": "World"}' localhost:50051 helloworld.Greeter/SayHello
BloomRPC是一個類似Postman的gRPC GUI客戶端,支持: - 導入.proto文件 - 可視化調用方法 - 查看請求/響應 - 保存歷史記錄
// health.proto
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
// 服務端實現
import "google.golang.org/grpc/health/grpc_health_v1"
healthServer := health.NewServer()
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
grpc_health_v1.RegisterHealthServer(s, healthServer)
import "github.com/grpc-ecosystem/go-grpc-prometheus"
// 注冊指標
grpc_prometheus.EnableHandlingTimeHistogram()
grpc_prometheus.Register(s)
// 暴露/metrics端點
http.Handle("/metrics", promhttp.Handler())
go http.ListenAndServe(":9090", nil)
”`yaml
apiVersion: apps/v1 kind: Deployment metadata: name: grpc-server spec: replicas: 3 selector: matchLabels: app: grpc-server template: metadata: labels:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。