乐趣区

关于思否技术征文:Go微服务开发gRPC总共分三步

前言

上一篇文章咱们介绍了 ProtoBuf 的应用,不理解 ProtoBuf 的同学倡议先读这篇文章:# 一文带你玩转 ProtoBuf,会用 protobuf 是学习 gRPC 的根底。

之前我也有写过 RPC 相干的文章:《Go RPC 入门指南:RPC 的应用边界在哪里?如何实现跨语言调用?》,具体介绍了 RPC 是什么,应用边界在哪里?并且用 Go 和 php 举例,实现了跨语言调用。不理解 RPC 的同学倡议先读这篇文章补补课。

下面提到的这些基础知识,不作为本文的重点。

这篇文章将重点介绍在微服务中 gRPC 的应用:

开发流程

在微服务分布式架构中开发 gRPC 其实非常简单,不要畏难畏烦,没有什么心智累赘的。

开发 gRPC 的流程和宋丹丹把大象装冰箱是一样的:

  1. 把冰箱门关上
  2. 把大象装进去
  3. 把冰箱门关上

开发 gRPC 的流程;

  1. 写 proto 文件定义服务和音讯
  2. 应用 protoc 工具生成代码
  3. 编写业务逻辑代码提供服务

就是这么简略。

上面我依然以 Go 语言举例,其余语言的实现思路也是一样的。

入门实际

为了让大家更好了解,我参考 gRPC 官网文档,写了一个 helloword 示例。

下图是应用 Go 实现 gRPC 开发的目录结构图,先让大家有个整体的意识:

欢送大家依照我的步骤进行复刻实际:

看文章是学不会编程的,然而一边看文章一边敲代码能够!

1. 写 proto 文件定义服务和音讯

service Greeter {} 是咱们定义的服务

rpc SayHello (HelloRequest) returns (HelloReply) {} 是在服务中定义的办法

protoc 工具集,会依据咱们定义的服务、办法、和音讯生成指定语言的代码。

syntax = "proto3";

option go_package = "./;hello";

package hello;

service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}}

message HelloRequest {string name = 1;}

message HelloReply {string message = 1;}

如果小伙伴们看下面代码有不懂的中央,那就是 protobuf 根底不牢了,请看这篇:一文带你玩转 protobuf,回顾一下知识点。

2. 应用 protoc 工具生成代码

切换到 proto 文件所在目录下

cd protos/helloword/

生成 Go 代码

protoc --go_out=. helloworld.proto

小技巧之 同步依赖:当你生成 Go 代码后,发现生成的文件飘红报错,不要缓和,少数状况是因为依赖不存在导致的。

执行上面的命令,同步依赖就能够了:

go mod tidy

3. 编写业务逻辑代码 提供服务

上面是明天的重点,咱们用 Go 实现业务逻辑的编写,留神看:

在微服务架构开发 gRPC 时,肯定有两个端:服务端和客户端。

咱们的习惯是,在搞定 protobuf 之后,先写服务端逻辑,裸露端口,提供服务;再写客户端逻辑,连贯服务,发送申请,解决响应。

小提示:PHP 和 Objective- C 只能实现 gRPC 中的客户端,不能实现服务端。

3.1 编写服务端业务逻辑

编写服务端非常简单,咱们只须要实现在 proto 中定义的 rpc 办法。

小技巧:在咱们理论开发中,咱们导入 protos 服务的时候,默认是一个比拟长的名字,倡议联合本人我的项目,改成比拟短又容易了解的名字。

package greeter_server

import "context"

// 导入咱们在 protos 文件中定义的服务
import pb "sifou/rpc/protos/helloworld"

// 定义一个构造体,作用是实现 helloworld 中的 GreeterServer
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {return &pb.HelloReply{Message: "Hello" + in.Name}, nil
}

以上就实现了服务端的业务逻辑编写:

  1. 用咱们在 proto 中定义的音讯,构建并填充了一个咱们在接口定义的 HelloReply 应答对象。
  2. HelloReply 对象返回给客户端。

到这里业务性能是实现了,然而服务端的业务如何让客户端调用呢?

上面咱们持续编写:裸露端口,提供服务

3.2 裸露端口,提供服务

踩坑分享:我在编码的过程中应用了谬误的 gRPC 依赖,节约了不少工夫。应该用上面这个依赖包:

go get google.golang.org/grpc

留神:上面的代码是在 3.1 的根底上增加的,并不是另外创立一个新的 Go 文件。

要害代码正文曾经在代码段中写分明了,倡议大家参考步骤,手敲一遍。

package main

import (
   "context"
   "flag"
   "fmt"
   "google.golang.org/gRPC"
   "log"
   "net"
)

// 导入咱们在 protos 文件中定义的服务
import pb "sifou/rpc/protos/helloworld"

// 定义一个构造体,作用是实现 helloworld 中的 GreeterServer
type server struct {pb.UnimplementedGreeterServer}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {return &pb.HelloReply{Message: "Hello" + in.Name}, nil
}

// 定义端口号 反对启动的时候输出端口号
var (port = flag.Int("port", 50051, "The server port")
)

func main() {
   // 解析输出的端口号 默认 50051
   flag.Parse()
   //tcp 协定监听指定端口号
   lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
   if err != nil {log.Fatalf("failed to listen: %v", err)
   }
   // 实例化 gRPC 服务
   s := gRPC.NewServer()
   // 服务注册
   pb.RegisterGreeterServer(s, &server{})
   log.Printf("server listening at %v", lis.Addr())
   // 启动服务
   if err := s.Serve(lis); err != nil {log.Fatalf("failed to serve: %v", err)
   }
}

启动胜利,普天同庆:

到这里咱们就实现了 gRPC 服务端的编写:咱们实现了将 Greeter 服务绑定到一个端口,咱们启动这个服务时,服务端已筹备好从 Greeter 服务的客户端接管申请了。

咱们接下来再编写客户端:

3.3 编写客户端逻辑代码

客户端的 gRPC 更简略!

咱们将用 protoc 生成的代码写一个简略的客户端程序,来拜访咱们在创立的 Greeter 服务端。

小技巧:在 gRPC Go 咱们应用一个非凡的 Dial() 办法来创立频道,实现和服务端的连贯。

要害代码已增加正文,编写客户端逻辑代码,强烈建议大家和我一起手敲一遍。

“编程要有工匠精力,做的多了手感就进去了。”

package main

import (
   "context"
   "flag"
   "google.golang.org/gRPC" // 这个依赖不要搞错
   "google.golang.org/gRPC/credentials/insecure"
   pb "sifou/rpc/protos/helloworld"
   "log"
   "time"
)

// 默认数据 也反对在控制台自定义
const (defaultName = "world")

// 监听地址和传入的 name
var (addr = flag.String("addr", "localhost:50051", "the address to connect to")
   name = flag.String("name", defaultName, "Name to greet")
)

func main() {flag.Parse()
   // 通过 gRPC.Dial()办法建设服务连贯
   conn, err := gRPC.Dial(*addr, gRPC.WithTransportCredentials(insecure.NewCredentials()))
   if err != nil {log.Fatalf("did not connect: %v", err)
   }
   // 连贯要记得敞开
   defer func(conn *gRPC.ClientConn) {err := conn.Close()
      if err != nil {}}(conn)
   // 实例化客户端连贯
   c := pb.NewGreeterClient(conn)

   // 设置申请上下文,因为是网络申请,咱们须要设置超时工夫
   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   defer cancel()
   // 客户端调用在 proto 中定义的 SayHello()rpc 办法,发动申请,接管服务端响应
   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())
}

到这里咱们就曾经实现了服务端和客户端业务逻辑的编写,上面就是见证奇观的时刻了:

3.4 调用 gRPC 两端互通

如何实现两端的音讯互通?

  1. 咱们之前曾经关上了一个终端,启动了服务端的服务。
  2. 咱们再关上一个新的终端,运行客户端,看下服务端是否给咱们返回了数据:

和咱们料想中的后果一样:

服务端给咱们返回了“Hello world”,其中 Hello 是服务端设置的,world 是客户端传给服务端的参数,服务端进行拼接之后给客户端返回了。

至此,一个经典的 gRPC 通信示例就搞定了!

扩大:自定义输出

没用过 go flag 自定义输出的小伙伴重点看一下,这部分是为你写的:

客户端和服务端代码中的 flag.Parse 的作用是:反对咱们在终端控制台自定义输出参数,如果没有输出的话,应用程序中设置的默认参数,比方客户端的 name,在代码中是这么定义的:

name = flag.String("name", "world", "Name to greet")

咱们在终端输出如下命令:

go run main.go --name 王中阳

成果是这样的:

好了,咱们再接着聊进阶的内容:

gRPC 另外一个特点就是和语言无关,咱们能够应用不同的语言定义客户端和服务端。

上面咱们再进阶实战一下,用 gRPC 实现跨语言的调用。

进阶实战:跨语言调用

入门实战我给出了具体的示例代码,甚至连目录构造都分享给大家了,置信大家只有依照步骤复刻,肯定也能运行胜利。

对于进阶实战的跨语言调用:服务端不反复编写了,咱们依然应用下面用 Go 编写的服务端。

客户端我将用我相熟的 PHP 语言来编写,实现两端的 rpc 通信。

倡议大家回顾一下“大象装冰箱”的步骤,用本人善于的语言开发客户端,像我一样实现 gRPC 的跨语言调用。

1. 编写 proto 文件

和入门实战是一样的

2. 依据 proto 文件生成代码

和入门实战思路一样,区别指定生成代码语言不一样:

protoc-gen-php -i . -o . ./helloworld.proto

3. 编写业务逻辑代码

3.1 先写服务端

服务端应用 Go 实现的服务端,不进行编写。

确定服务端是开启状态:

再次揭示一下:

PHP 和 Objective- C 只能实现 gRPC 中的客户端,不能实现服务端。

3.2 再写客户端

我用 PHP 实现客户端的编写,你善于什么语言呢?有没有踩到坑,欢送大家在评论区探讨。

<?php
// 命名空间
namespace Helloworld;

// 定义 PHP 客户端
class GreeterClient extends \gRPC\BaseStub
{

  // 定义构造方法
  public function __construct($hostname, $opts, $channel = null)
  {parent::__construct($hostname, $opts, $channel);
  }

  /**
   * 实现 proto 文件中定义的 SayHello()办法
   * Sends a greeting
   * @param \Helloworld\HelloRequest $argument input argument
   * @param array $metadata metadata
   * @param array $options call options
   * @return \gRPC\UnaryCall
   */
  public function SayHello(\Helloworld\HelloRequest $argument,
                           $metadata = [], $options = [])
  {
    return $this->_simpleRequest('/helloworld.Greeter/SayHello',
      $argument,
      ['\Helloworld\HelloReply', 'decode'],
      $metadata, $options);
  }

}

3.3 启动服务,进行调用

编写 PHP 脚本文件:

连贯 50051 端口(Go 实现的 gRPC 服务端对外裸露的端口)

<?php
require dirname(__FILE__).'/vendor/autoload.php';

function greet($hostname, $name)
{
    $client = new Helloworld\GreeterClient($hostname, ['credentials' => gRPC\ChannelCredentials::createInsecure(),
    ]);
    $request = new Helloworld\HelloRequest();
    $request->setName($name);
    list($response, $status) = $client->SayHello($request)->wait();
    if ($status->code !== gRPC\STATUS_OK) {
        echo "ERROR:" . $status->code . "," . $status->details . PHP_EOL;
        exit(1);
    }
    echo $response->getMessage() . PHP_EOL;}

$name = !empty($argv[1]) ? $argv[1] : 'world';
$hostname = !empty($argv[2]) ? $argv[2] : 'localhost:50051';
greet($hostname, $name);

通过终端,启动 PHP 客户端:

咱们发现,PHP 的客户端通过 gRPC 胜利的连贯了 Go 服务端提供的 50051 服务,并胜利调用了 SayHello()办法,取得了返回值:Hello world

实操技巧

纸上得来终觉浅,绝知此事要躬行。

强烈建议大家入手实操,应用本人相熟的语言实现 gRPC 跨语言调用,能够参考:gRPC 各种语言教程详解。这篇技术博客更适宜小白入门 gRPC 的开发,有个整体的了解和概念。

进阶知识点安利大家看官网文档进行实际:

gRPC 官网文档中文版

gRPC 官网示例 GitHub

本文总结

通过这篇文章咱们曾经把握了 gRPC 相干的知识点,能够独立用 Go 实现客户端和服务端的编写,并且通过服务注册对外提供服务,实现可客户端和服务端的 gRPC 通信。

为了验证 gRPC 反对跨语言调用的个性,在进阶实战中又应用 PHP 开发了客户端,实现了 PHP 客户端和 Go 服务端的近程跨语言调用。

养成良好的编程习惯有助于缩小奇奇怪怪的问题,强烈建议大家严格依照“大象装冰箱”的程序进行 gRPC 的开发:

1. 写 proto 文件定义服务和音讯

2. 应用 protoc 工具生成代码

3. 编写业务逻辑代码提供服务

关注我,下一篇带大家玩转 Go 微服务。

最初:万事起于忽微,质变引起量变,置信保持的力量。

对于专栏

近期会更新一系列 Go 实战进阶的文章,欢送大家关注我。

这是近期会更新文章的常识脉络图,感兴趣的小伙伴能够关注一波,欢送日常催更。

已实现

《一文玩转 ProtoBuf》

欢送催更

小伙伴们还想看哪些内容,欢送在评论区留言。

本文参加了思否技术征文,欢送正在浏览的你也退出。

退出移动版