• 花5分钟写个 grpc 微服务架构吧


    背景:当前微服务架构在开发中越来越常见,其目的在于将各个模块进行解耦,实现各个模块之间快速迭代。在 golang 项目中,最流行的微服务框架当属谷歌旗下的 grpc 框架。回想起我学 grpc的 时候, 虽说不难,代码量不大, 但还是遇到了很多坑的, 如果照着网上的教程来写代码大概率是跑不通的。 特此写一篇小白也能看懂的,最简单的,带你手把手写的基于 grpc 微服务架构项目。

    安装 grpc , protoc 工具 和 protobuf

    在命令行中输入以下三行命令:

    1. go get google.golang.org/grpc
    2. go get -u github.com/golang/protobuf/proto
    3. go get -u github.com/golang/protobuf/protoc-gen-go
    4. 复制代码

    光有这几个还是不够的,还得要去下载 protobuf 安装包 下载完成后解压,将 bin 目录下的 protoc 可执行文件复制到 go安装目录下的 bin 目录,比如我的是 C:\Users\Chester_Zhang\go\bin 。要不然等等命令行无法识别 protoc 命令。

    接下来确保环境变量配置正确, 如果环境变量没配置好, 可能后续的 protoc 命令无法识别。 确保go安装目录下的 bin 目录位于环境变量 中。比如我是默认安装go的,那么C:\Users\Chester_Zhang\go\bin 应该位于环境变量中。

    如果 go get 过程中遇到了网络问题,可以更改 go proxy 为goproxy.cn,direct 。

    正式开始吧

    创建目录和工程

    首先来看一看目录结构,目录结构也有很多坑。新建一个 grpc 目录,grpc 下面创建client, proto, server 三个目录

    1. --grpc
    2. --client
    3. --proto
    4. --server
    5. 复制代码

    然后进入 grpc 目录 命令行输入

    1. go mod init grpc
    2. go mod tidy
    3. 复制代码

    这样就在 grpc 下面创建了一个 go 工程。

    写写 protobuf

    protobuf 是一种数据格式,和 json 类似,但是传输效率更高。在rpc中,一般使用protobuf格式的数据,就好比restful中使用json。

    在 proto/chat 目录下创建chat.proto文件

    1. syntax = "proto3";
    2. package proto;
    3. option go_package="/";
    4. // 定义 message
    5. message ChatMessage {
    6. string body = 1;
    7. }
    8. // 定义 service
    9. service ChatService {
    10. rpc SayHello(ChatMessage ) returns (ChatMessage ) {}
    11. }
    12. 复制代码

    当然你也可以取别的名字,但一定要以 .proto 后缀结尾。 这里有一个坑, 必须要写 go_package, 否则 等等生成 .go 文件时会报错。 go_package 这里我只写了一个斜杠, 大家也可以试试写其他的看看会发生什么。

    写完 protobuf 之后就可以编译了。还是在 grpc 目录下进入命令行输入:

    1. protoc --go_out=plugins=grpc:proto --proto_path= proto/chat.proto
    2. 复制代码

    这样就会在 proto 目录下生成一个 chat.pb.go 文件。先不看这个 chat.pb.go 文件里面有什么, 先来看看 上面这条命令的含义。

    protoc 是命令, 最重要的有两个参数: go_out 和 proto_path。 proto_path 指定了 .proto 文件在哪里。 go_out 指定了 生成的 .pb.go 文件在哪里以及用什么插件生成, 这里我们用了 grpc 插件生成,生成目录还是在 proto 目录下。 至于为什么 要用 grpc 插件生成, 我也不知道哈,可能这个比较好?hhh。

    回头来看 那个 chat.pb.go 文件,看看里面都有些什么,我只截取部分精华部分, 因为别的部分我也看不太懂了hhh

    1. // 定义 message
    2. type ChatMessage struct {
    3. state protoimpl.MessageState
    4. sizeCache protoimpl.SizeCache
    5. unknownFields protoimpl.UnknownFields
    6. Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
    7. }
    8. func NewChatServiceClient(cc grpc.ClientConnInterface) ChatServiceClient {
    9. return &chatServiceClient{cc}
    10. }
    11. // ChatServiceServer is the server API for ChatService service.
    12. type ChatServiceServer interface {
    13. SayHello(context.Context, *ChatMessage) (*ChatMessage, error)
    14. }
    15. func (*UnimplementedChatServiceServer) SayHello(context.Context, *ChatMessage) (*ChatMessage, error) {
    16. return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
    17. }
    18. func RegisterChatServiceServer(s *grpc.Server, srv ChatServiceServer) {
    19. s.RegisterService(&_ChatService_serviceDesc, srv)
    20. }
    21. 复制代码

    来看看这里面都有啥

    • ChatMessage 这个结构体,刚刚在 chat.proto 文件中的 message 类型会被转换成 一个 go 的结构体。

    • NewChatServiceClient 返回一个 ChatService 的 client。

    • ChatServiceServer 这个接口,刚刚 chat.proto 文件中定义了一个 service叫 ChatService, 里面还有个 SayHello 方法,也被翻译成了这个接口。

    • 我们等等还要取实现一下这个 SayHello 方法,如果不实现的话,就会调用 UnimplementedChatServiceServer 里面的 SayHello 方法。

    1. func (*UnimplementedChatServiceServer) SayHello(context.Context, *ChatMessage) (*ChatMessage, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") }
    2. 复制代码

    最后的 RegisterChatServiceServer 是用了注册一个service的,只有被注册以后才能使用这个service。

    说了半天,还没有正式进入主菜。我们的 service 里面的SayHello 方法还没有实现呀。

    接着在 proto 目录下新建 chat.go 文件

    1. package __
    2. import (
    3. "fmt"
    4. "golang.org/x/net/context"
    5. )
    6. type Server struct {
    7. }
    8. func (s *Server) SayHello(ctx context.Context, in *ChatMessage ) (*ChatMessage, error) {
    9. log.Printf("Receive message body from client: %s", in.Body)
    10. return &ChatMessage{Body: "Hello From the Server!"}, nil
    11. }
    12. 复制代码

    这里写了个 结构体,包含了 SayHello 方法, 结构体的名字你可以取其他的,但是这个 SayHello 方法可就不能改了,因为在 .proto 文件里面已经写好了,等等要关联上。

    写个 server 并注册

    在 server 目录下创建 server.go

    1. package main
    2. import (
    3. proto "grpc/proto"
    4. "google.golang.org/grpc"
    5. "log"
    6. "net"
    7. )
    8. func main() {
    9. // 监听8000 端口, 返回一个 listener 和 error
    10. lis, err := net.Listen("tcp", ":8000")
    11. if err != nil {
    12. log.Fatalf("Fail to listen: %v", err)
    13. }
    14. s:= proto.Server{}
    15. grpcServer := grpc.NewServer()
    16. //注册一个server
    17. proto.RegisterChatServiceServer(grpcServer,&s)
    18. // server 开始监听
    19. if err := grpcServer.Serve(lis); err != nil {
    20. log.Fatalf("Fail to serve: %v", err)
    21. }
    22. }
    23. 复制代码

    写个 Client 客户端

    服务端写完了,写个 Client 客户端 来调用 远程的 SayHello 方法吧。 在 client 目录下创建 client.go 文件。

    1. package main
    2. import (
    3. proto "grpc/proto"
    4. "context"
    5. "google.golang.org/grpc"
    6. "log"
    7. )
    8. func main() {
    9. //获得一个 Client 连接
    10. var conn *grpc.ClientConn
    11. conn, err := grpc.Dial(":8000", grpc.WithInsecure())
    12. if err != nil {
    13. log.Fatalf("did not connect: %s", err)
    14. }
    15. defer conn.Close()
    16. // 获得一个 ChatService 的 client
    17. c := proto.NewChatServiceClient(conn)
    18. // grpc 调用远程的 SayHello
    19. response, err := c.SayHello(context.Background(), &proto.ChatMessage{Body: "Hello From Client!"})
    20. if err != nil {
    21. log.Fatalf("Error when calling SayHello: %s", err)
    22. }
    23. log.Printf("Response from server: %s", response.Body)
    24. }
    25. 复制代码

    运行起来

    在grpc目录下开两个命令行,依次输入下面两条命令

    1. go run server/server.go
    2. go run client/client.go
    3. 复制代码

    在 server 端 命令行会输出

    1. 2022/08/23 16:43:05 Receive message body from client: Hello From Client!
    2. 复制代码

    在 client 端 命令行会输出

    1. 2022/08/23 16:43:05 Receive message body from client: Hello From Client!
    2. 复制代码

    闲聊一下 分布式,微服务, rpc 与 restful

    如果你去面试,面试官可能会问,为什么要用微服务呢? 微服务和分布式架构有啥不一样呢。首先从目的上来说,分布式和微服务都是一个目的,将各个模块分开来开发,分开来部署,因为单独的机器内存有限,CPU处理资源有限,总不能一直加大内存,加大算力的。而微服务的精髓在于一个 "微" 字, 所谓 "微" 就是各个模块之间的粒度足够的小。 就好比 我们上面写的 一个最简单 微服务项目,单独一个 SayHello 函数也可以领出来做一个 服务端。

    看到这里你可能会问 明明有 resful了,为什么还要 rpc 呢? 这个问题问得非常好。 虽然 restful 和 rpc 都是基于 请求-响应的模型,但 restful 和 rpc 的使用场景和意义还是有很大不同的。 restful 一般用于自己这个项目去访问外部的资源, 你发一个请求, 外部资源返回一个响应。而 rpc 则一般用于一个项目之间内部模块的相互调用,强调的是像在本地调用一个函数一样访问另一个模块上的函数。比如来看刚刚我们上面写的 client 端代码有这么一行:

    1. response, err := c.SayHello(context.Background(), &proto.ChatMessage{Body: "Hello From Client!"})
    2. 复制代码

    看到没有, c.SayHello 这是不是相当于之间调用服务端的 SayHello 方法,这和 restful是不一样的, restful 只关注我往接口发请求,强调的是 发送 , 而rpc 强调的是好比服务端的函数在我手里一样,我直接拿来用, 这就是核心的区别。

    看一看 rpc 原理

    回头来看一下 rpc 的原理,我画了一幅图

    函数A调用函数B的过程,可以用从1到10这个过程来表示。其中的序列化和反序列化就是 protobuf 格式的数据与 客户端服务端语言的数据类型转换的过程。而函数映射指的是 functionB 在在 rpc-server 中注册好,就好比 server端写过的这3行代码:

    1. s:= proto.Server{}
    2. grpcServer := grpc.NewServer()
    3. //注册一个server
    4. proto.RegisterChatServiceServer(grpcServer,&s)
    5. 复制代码

    同样的在 客户端也有这么1行代码,其目的在于从 rpc client 中获得服务端的 clint。

    1. c := proto.NewChatServiceClient(conn)
    2. 复制代码

    通过 rpc-clinet 调用 functionA, 从直观来看,就好比在本地调用一样。

     

  • 相关阅读:
    梳理下我自已对Reactor与及IO多路复用的select\poll\epoll的理解
    Linux服务器自定义登陆提示信息
    E. Red-Black Pepper
    vue当中的收集表单数据以及过滤器
    面试突击79:Bean 作用域是啥?它有几种类型?
    神经网络权重是什么意思,神经网络权重调整方法
    Win7怎么把控制面板添加到右键菜单
    RIP光栅图像处理器
    JS严格模式(精简分析,快速掌握)
    Ai作图可控性演进——从SD到MJ
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126502741