美文网首页
go微服务学习(二)-从rpc到grpc

go微服务学习(二)-从rpc到grpc

作者: 温岭夹糕 | 来源:发表于2023-11-27 17:01 被阅读0次

    写在开头

    仅用于自己学习

    回顾上节代码

    client/client.go

    func main() {
        con, err := rpc.Dial("tcp", ":8080")
        if err != nil {
            log.Fatal(err)
            return
        }
        defer con.Close()
    
        var str string
    
        err = con.Call("hello.Hello", "zjb", &str)
        if err != nil {
            log.Fatal(err)
            return
        }
        fmt.Println(str)
    }
    

    server/server.go

    type HelloServer struct {
    }
    
    func (s HelloServer) Hello(str string, res *string) error {
        *res = "hello" + str
        return nil
    }
    
    func main() {
        ln, err := net.Listen("tcp", ":8080")
        if err != nil {
            log.Fatal(err)
            return
        }
        _ = rpc.RegisterName("hello", HelloServer{})
        for {
            con, err := ln.Accept()
            if err != nil {
                log.Fatal(err)
                return
            }
            go rpc.ServeConn(con)
        }
    }
    

    1.原生rpc的弊端

    无非就是如何解耦

    1.约定问题如何解决

    最大的弊端不用想就知道,通常客户端和服务端都不在一台服务器上,那么客户端如何知道服务端结构体的结构?
    直接把服务端代码也拷贝一份下来?看安全性显然不行,那么我们只需要把客户端需要的部分给拷贝下来不就行了?
    抽象出handler逻辑
    handler/handler.go

    const (
            //这里包名+结构体名保证唯一性
        HelloServiceName string = "handler/HelloServer"
    )
    

    那么在服务端修改后客户端只需要引用改变了即可

    //server.go
    _ = rpc.RegisterName(handler.HelloServiceName, HelloServer{})
    //client.go
    err = con.Call(handler.HelloServiceName+".Hello", "zjb", &str)
    

    还是不够爽,我连这个方法名我都想让handler告诉我,那就使用interface进行约定
    handler.go

    type HelloService interface {
        Hello(str string, res *string) error
    }
    

    那我们看一眼就知道有哪些方法了,服务器具体的实现类随便它

    1.2独立业务逻辑

    此时的client.go我们只想写自己的业务逻辑,什么连接啊,访问哪个啊我都不关心,那么我们就把它抛给一个代理或管理器来执行
    clent_proxy/manger.go

    type Client struct {
        *rpc.Client
    }
    
    var _ handler.HelloService = (*Client)(nil)
    
    func (c *Client) Hello(str string, res *string) error {
        return c.Call(handler.HelloServiceName+".Hello", str, res)
    }
    
    func (c *Client) Close() {
        c.Close()
    }
    
    func MakeClient(addr string) (*Client, error) {
        con, err := rpc.Dial("tcp", ":8080")
        if err != nil {
            return nil, err
        }
        return &Client{con}, nil
    }
    

    那么客户端只需要调用代理的Hello方法即可,同理服务端
    server_proxy/manger.go

    type HelloServer struct {
    }
    
    var _ handler.HelloService = (*HelloServer)(nil)
    
    func (s HelloServer) Hello(str string, res *string) error {
        *res = "hello" + str
        return nil
    }
    
    func RegisterName(name string, srv any) {
        rpc.RegisterName(name, srv)
    }
    

    综上,我们看到核心是通过handler文件来进行双方的约束,并利用代理类让横向业务不入侵到主业务当中
    问题又来了,这些玩意手写维护都很麻烦,有没有好用的工具帮我们自动生成server_proxy、client_proxy和handler?
    就算go能生成,那能为其他语言也生成一份吗(这样不就其他语言能互相调用函数了吗)?
    这就引入了下文protobuf 和grpc

    2.Proto 和 grpc

    2.1环境安装

    proto工具安装比较简单,网上都能百度到就不再过多赘述
    执行命令查看是否安装成功

    protoc --version
    

    安装grpc比较麻烦
    我自己的安装环境是windows
    官方给的命令不起作用

    go get google.golang.org/grpc
    

    原因是这个代码已经转移到github上了,但代码里包的依赖还是没有修改还是google.golang.org,所以我更推荐下面这种办法。
    这里提供一种像我一样无妨访问goolg官网安装grpc办法
    需要注意对应的目录

    git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
    git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
    git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
    git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
    

    最后进入到gopath目录的src下执行以下命令即可

    go install google.golang.org/grpc
    

    如果你像我一样克隆仓库都办不到,那直接去github仓库下载zip包来解压到$GOPATH的对应文件夹即可

    2.2 proto 3语法

    官方文档地址

    1. 第一行必须声明是3的语法
    syntax = "proto3"; 
    
    1. option这里用的最多是go_package 来声明包名,如下会自己创建pb/proto_demo文件夹下的go文件,文件的包名为“proto_demo”
    option go_package = "pb/proto_demo";
    
    1. 定义消息数据结构message,各个类型对应什么字段翻阅文档就行
    message Person {
        string name = 1;
        int32 id = 2;
        string email = 3;
    }
    

    数据结构的字段后面跟的编号需要保证唯一性,这些编号不是默认值,而是用于二进制格式标识字段,就好比数组哪个内存单元占什么数据,这也意味着一旦消息类型被使用,编号就不应被更改

    1. 保留字段。即使我们要更新消息的数据结构,如删除某个字段,也不能重用该数字编号。如果以后加载旧版本的相同.proto文件,会导致数据损坏、隐私泄露等问题。用保留字段reserved标注即可,当这个变量名被重新使用时编译器报错
    message Foo {
        // 注意,同一个 reserved 语句不能同时包含变量名和 Tag 
        reserved 2, 15, 9 to 11;
        reserved "foo", "bar";
    }
    
    1. 定义服务。service会使proto生成对应的客户端和服务端代码,对应第一节的两个代理代码
    service UserServiceRpc
    {
        rpc Login(LoginRequest) returns(LoginResponse);
    }
    

    2.3proto文件编译

    protoc  \
       -I=xxx  //  .proto文件所在的目录
      --go_out=.  //文件在哪里生成
      [--go_out=plugins=grpc:.]  //涉及到grpc代码的生成需要使用插件
    

    2.4快速体验grpc

    编写hello.proto文件,提供名为My的服务,该服务具体提供Hello服务

    syntax = "proto3";
    
    option go_package = "pb/proto_demo";
    
    message HelloRequest {
      string name = 1; //1是编号
    }
    
    message Response {
      string reply = 1;
    }
    
    service My {
      rpc Hello(HelloRequest) returns (Response); //hello接口
    }
    

    编译文件生成约束规则

    protc -I . --go_out=plugin=grpc:.
    

    我么可以观察生成的go文件多出了myclient结构体和myserver结构体,这不就类似上文的两个代理

    服务端server/main.go编写服务的具体内容

    type Server struct {
    }
    
    var _ proto_demo.MyServer = (*Server)(nil)
    
    // 逻辑就是返回 hello+name
    func (s *Server) Hello(ctx context.Context, r *proto_demo.HelloRequest) (*proto_demo.Response, error) {
        return &proto_demo.Response{
            Reply: "hello" + r.Name,
        }, nil
    }
    
    func main() {
        g := grpc.NewServer()
        proto_demo.RegisterMyServer(g, &Server{})
        ln, err := net.Listen("tcp", ":8080")
        if err != nil {
            panic("you die!")
        }
        log.Fatal(g.Serve(ln))
    }
    

    client/main.go客户端发起请求

    func main() {
        con, err := grpc.Dial(":8080", grpc.WithInsecure())
        if err != nil {
            panic("you die!")
        }
        defer con.Close()
        var req = &proto_demo.HelloRequest{
            Name: "zjb",
        }
        client := proto_demo.NewMyClient(con)
        res, err := client.Hello(context.Background(), req)
        if err != nil {
            panic("you die!")
        }
        fmt.Println(res.Reply)
    }
    

    结果

    hellozjb
    

    2.5扩展阅读grpc流模式

    流模式

    参考

    proto避坑指南
    一文读懂protobuf

    相关文章

      网友评论

          本文标题:go微服务学习(二)-从rpc到grpc

          本文链接:https://www.haomeiwen.com/subject/qcpzwdtx.html