• 【微服务落地】服务间通信方式: 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地址

    image.png

    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
    

    会多了两个文件

    image.png

    服务端

    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可以点赞让更多人看到它。
  • 相关阅读:
    unity3d 中文乱码解决方法——cs代码文件格式批量转化UTF8
    Unity SteamVR插件集成
    Unity3D Layer要点
    Unity利用Sapi进行windows语音开发
    Scratch入门课程(1)——把工具准备好
    【blockly教程】Blockly编程案例
    【blockly教程】第六章 Blockly的进阶
    【blockly教程】第五章 循环结构
    【blockly教程】第三章Blockly顺序程序设计
    【blockly教程】第四章 Blockly之选择结构
  • 原文地址:https://www.cnblogs.com/HappyTeemo/p/15217367.html
Copyright © 2020-2023  润新知