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

2023-11-27  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读