天天看點

【微服務落地】服務間通信方式: gRPC的入門

gRPC是什麼

官方介紹:

https://grpc.io/docs/what-is-grpc/introduction/

“A high-performance, open-source universal RPC framework”
  • 多語言:語言中立,支援多種語言。
  • 輕量級、高性能:序列化支援 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高性能序列化架構。
  • IDL:基于檔案定義服務,通過 proto3 工具生成指定語言的資料結構、服務端接口以及用戶端 Stub。
  • 設計理念
  • 移動端:基于标準的 HTTP2 設計,支援雙向流、消息頭壓縮、單 TCP 的多路複用、服務端推送等特性,這些特性使得 gRPC 在移動端裝置上更加省電和節省網絡流量。
  • 服務而非對象、消息而非引用:促進微服務的系統間粗粒度消息互動設計理念。
  • 負載無關的:不同的服務需要使用不同的消息類型和編碼,例如 protocol buffers、JSON、XML 和 Thrift。
  • 流:Streaming API。
  • 阻塞式和非阻塞式:支援異步和同步處理在用戶端和服務端間互動的消息序列。
  • 中繼資料交換:常見的橫切關注點,如認證或跟蹤,依賴資料交換。
  • 标準化狀态碼:用戶端通常以有限的方式響應 API 調用傳回的錯誤。

小結

  • grpc是個協定,對應的是proto檔案
  • protobuf 是将jrpc轉化為代碼的工具

安裝

grpc包

go get -u google.golang.org/grpc
           

protobuf

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
           

protoc文法

  • -I 參數:指定import路徑,可以指定多個-I參數,編譯時按順序查找,不指定時預設查找目前目錄
  • --go_out :golang編譯支援,支援以下參數
    • plugins=plugin1+plugin2 - 指定插件,目前隻支援grpc,即:plugins=grpc
    • M 參數 - 指定導入的.proto檔案路徑編譯後對應的golang包名(不指定本參數預設就是.proto檔案中import語句的路徑)
    • import_prefix=xxx - 為所有import路徑添加字首,主要用于編譯子目錄内的多個proto檔案,這個參數按理說很有用,尤其适用替代一些情況時的M參數,但是實際使用時有個蛋疼的問題導緻并不能達到我們預想的效果,自己嘗試看看吧
    • import_path=foo/bar - 用于指定未聲明package或go_package的檔案的包名,最右面的斜線前的字元會被忽略
    • 末尾 :編譯檔案路徑 .proto檔案路徑(支援通配符
protoc --go_out=. example.proto 
protoc --go-grpc_out=. example.proto
           

demo

demo git位址

【微服務落地】服務間通信方式: gRPC的入門

proto檔案

syntax = "proto3";

# 定義了包名
package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
           

生成

protoc --go_out=. helloworld.proto 
protoc --go-grpc_out=. helloworld.proto
           

會多了兩個檔案

【微服務落地】服務間通信方式: gRPC的入門

服務端

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	port = ":50051"
)

// 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() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
           

注意

服務端其實是實作協定中的接口,即實作所有方法

type HelloServer interface {
	// 定義SayHello方法
	SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedHelloServer()
}
           

很多教程實作了mustEmbedUnimplementedHelloServer 這個方法,但是由于是小寫, 同目錄下是好的,跨了目錄就會有問題。

應該直接:

type server struct {
	pb.UnimplementedGreeterServer
}
           

用戶端

package main

import (
	"context"
	"log"
	"os"
	"time"

	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	address     = "localhost:50051"
	defaultName = "world"
)

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
	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.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	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())
}
           

常見的坑

  • grpc版本和protoc的版本不一緻,如果是第一次用,就都用最新的就好了,後面就一直用這個版本。或者換最新版本重新生成代碼檔案。

結語

  • 如果有不對的地方歡迎指正。
  • 如果有不了解的地方歡迎指出我來加栗子。
  • 如果感覺OK可以點贊讓更多人看到它。

繼續閱讀