云之鹰 發表於 2020-8-11 23:39:00

go语言gRPC系列(一) - gRPC入门

<ul>
<li>1. 前言</li>
<li>2. gRPC与Protobuf简介</li>
<li>3. 安装</li>
<li>4. 中间文件演示
<ul>
<li>4.1 编写中间文件</li>
<li>4.2 运行protoc命令编译成go中间文件</li>
</ul>
</li>
<li>5. 创建gRPC服务端
<ul>
<li>5.1 新建Product.protoc</li>
<li>5.2 运行protoc命令</li>
<li>5.3 实现RegisterProdServiceServer接口</li>
<li>5.4 准备工作完成,创建main函数将服务端跑起来</li>
</ul>
</li>
<li>6. 创建gRPC客户端
<ul>
<li>6.1 拷贝Product.pb.go到客户端service文件夹下</li>
<li>6.2 编写client的main函数</li>
<li>6.3 运行并显示结果</li>
</ul>
</li>
</ul>
<h2 id="1-前言"><span id="head1">1. 前言</span></h2>
<p>之前学习的go的微服务之间还是通过<code>REST API</code>的方式互相调用的,但既然要学习微服务,<code>gRPC</code>肯定是一个绕不过去的需要学习的技术, 所以就开搞吧</p>
<h2 id="2-grpc与protobuf简介"><span id="head2">2. gRPC与Protobuf简介</span></h2>
<p><code>gRPC</code>是一款<strong>语言中立</strong>、<strong>平台中立</strong>、开源的远程过程调用系统</p>
<blockquote>
<p>即:<code>gRPC</code>客户端和服务端可以在多种环境中运行和交互,例如用<code>java</code>写一个服务端,可以用go语言写客户端调用</p>
</blockquote>
<p>微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此间的通信就是个大问题.</p>
<p>gRPC可以实现将大的项目拆分为多个小且独立的业务模块,也就是服务。各服务间使用高效的<code>protobuf</code>协议进行RPC调用,gRPC默认使用<code>protocol buffers</code>,这是google开源的一套成熟的结构数据序列化机制</p>
<blockquote>
<p>当然也可以使用其他数据格式如JSON</p>
</blockquote>
<p>可以用proto files创建gRPC服务,用message类型来定义方法参数和返回类型</p>
<h2 id="3-安装"><span id="head3">3. 安装</span></h2>
<ul>
<li><strong>第一步:下载grpc通用编译器</strong></li>
</ul>
<p>如下图,解压出来因平台而异会是一个<code>protoc</code>或者<code>protoc.exe</code></p>
<blockquote>
<p>https://github.com/protocolbuffers/protobuf/releases</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn71b14sxj312l0g0di1.jpg" alt="" loading="lazy"></p>
<ul>
<li><strong>第二步:把下载的二进制文件路径添加到环境变量中</strong>(为了能全局访问protoc)
<ul>
<li>这里以为mac为例子</li>
</ul>
</li>
</ul>
<pre><code class="language-shell"># 打开存放环境变量的文件
vim ~/.bash_profile

# 添加如下,后面是路径
alias protoc="/Users/emm/others/protoc-3.12.4-osx-x86_64/bin/protoc"

# 刷新环境变量
source ./.bash_profile
</code></pre>
<ul>
<li><strong>第三步: 安装go专用的protoc的生成器</strong></li>
</ul>
<blockquote>
<p>go get github.com/golang/protobuf/protoc-gen-go</p>
</blockquote>
<p>安装后会在<code>GOPATH</code>目录下生成可执行文件,protobuf的编译器插件<code>protoc-gen-go</code>,等下执行<code>protoc</code>命令会自动调用这个插件</p>
<h2 id="4-中间文件演示"><span id="head4">4. 中间文件演示</span></h2>
<h3 id="41-编写中间文件"><span id="head5">4.1 编写中间文件</span></h3>
<p>这里新建一个pbfiles文件夹用于存放<code>protoc</code>文件</p>
<pre><code class="language-protobuf">// 这个就是protobuf的中间文件

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";

// 指定等会文件生成出来的package
package service;

// 定义request
message ProductRequest{
int32 prod_id = 1; // 1代表顺序
}

// 定义response
message ProductResponse{
int32 prod_stock = 1; // 1代表顺序
}
</code></pre>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn8eityngj30me0cp0uu.jpg" alt="" loading="lazy"></p>
<h3 id="42-运行protoc命令编译成go中间文件"><span id="head6">4.2 运行protoc命令编译成go中间文件</span></h3>
<p>然后运行以下的命令来生成<code>.go</code>结尾的文件</p>
<ul>
<li>下面的命令就是我们刚刚下的<code>protoc</code>包以及<code>protoc-gen-go</code>插件的作用</li>
</ul>
<pre><code class="language-shell"># 编译Product.proto之后输出到service文件夹
protoc --go_out=../service Product.proto
</code></pre>
<p>如下就在service文件夹自动生成了一个go文件,并且它提示我们不要去修改它</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn8jpg8uzj31gk0k50wx.jpg" alt="" loading="lazy"></p>
<h2 id="5-创建grpc服务端"><span id="head7">5. 创建gRPC服务端</span></h2>
<h3 id="51-新建productprotoc"><span id="head8">5.1 新建Product.protoc</span></h3>
<p>这个protoc文件比上面的多出了一个service的定义和里面的一个方法的定义</p>
<pre><code class="language-protobuf">// 这个就是protobuf的中间文件

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";

// 指定等会文件生成出来的package
package service;

// 定义request model
message ProductRequest{
int32 prod_id = 1; // 1代表顺序
}

// 定义response model
message ProductResponse{
int32 prod_stock = 1; // 1代表顺序
}

// 定义服务主体
service ProdService{
// 定义方法
rpc GetProductStock(ProductRequest) returns(ProductResponse);
}
</code></pre>
<h3 id="52-运行protoc命令"><span id="head9">5.2 运行protoc命令</span></h3>
<p><strong>注意</strong></p>
<ul>
<li>这里的protoc命令和之前的命令相比有点不一样</li>
</ul>
<pre><code>protoc --go_out=plugins=grpc:../service Product.proto
</code></pre>
<p>然后还是会在service文件夹下生成一个<code>.go</code>的文件</p>
<p><strong>有两个比较需要注意的</strong></p>
<ol>
<li>RegisterProdServiceServer</li>
</ol>
<blockquote>
<p>后面需要在server中调用这个来注册</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9mmp3j2j30nd0d776h.jpg" alt="" loading="lazy"></p>
<ol start="2">
<li>ProdServiceServer的接口定义</li>
</ol>
<blockquote>
<p>我们需要继承这个接口,即实现它所有的方法</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9nq5pd4j30p50ccmyx.jpg" alt="" loading="lazy"></p>
<h3 id="53-实现registerprodserviceserver接口"><span id="head10">5.3 实现RegisterProdServiceServer接口</span></h3>
<p>上面我们在<code>protoc</code>文件中定义了一个<code>ProdService</code>中包含了一个<code>GetProductStock</code>的方法</p>
<p>这里我们要实现自动生成的go文件中的接口</p>
<pre><code class="language-go">package service

import "context"

type ProdService struct {
}

func (ps *ProdService) GetProductStock(ctx context.Context, request *ProductRequest) (*ProductResponse, error) {
        return &amp;ProductResponse{ProdStock: request.ProdId}, nil
}
</code></pre>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9q844nuj30zn0efjtz.jpg" alt="" loading="lazy"></p>
<h3 id="54-准备工作完成创建main函数将服务端跑起来"><span id="head11">5.4 准备工作完成,创建main函数将服务端跑起来</span></h3>
<p>前面的都是准备工作,这里是真正把服务端跑起来的操作</p>
<p><strong>下面是服务端代码:</strong></p>
<pre><code class="language-go">package main

import (
        "gomicro-quickstart/grpc_demo/service"
        "google.golang.org/grpc"
        "log"
        "net"
)

func main() {
        // 1. new一个grpc的server
        rpcServer := grpc.NewServer()

        // 2. 将刚刚我们新建的ProdService注册进去
        service.RegisterProdServiceServer(rpcServer, new(service.ProdService))

        // 3. 新建一个listener,以tcp方式监听8082端口
        listener, err := net.Listen("tcp", ":8082")
        if err != nil {
                log.Fatal("服务监听端口失败", err)
        }

        // 4. 运行rpcServer,传入listener
        _ = rpcServer.Serve(listener)
}
</code></pre>
<p><strong>排坑</strong>:</p>
<ul>
<li>如果遇见类似<code>undefined: grpc.SupportPackageIsVersion6</code>和<code>undefined: grpc.ClientConnInterface</code>的错误,可以修改go.mod将grpc版本改到1.27.0</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9v5g03mj30ni0aw0ua.jpg" alt="" loading="lazy"></p>
<hr>
<h2 id="6-创建grpc客户端"><span id="head12">6. 创建gRPC客户端</span></h2>
<ul>
<li>新建一个<code>grpc_client</code>文件夹存放客户端相关的</li>
<li>并在<code>grpc_client</code>文件夹下再新建一个<code>service</code>文件夹</li>
</ul>
<h3 id="61-拷贝productpbgo到客户端service文件夹下"><span id="head13">6.1 拷贝Product.pb.go到客户端service文件夹下</span></h3>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghna79yhtbj308g02owef.jpg" alt="" loading="lazy"></p>
<h3 id="62-编写client的main函数"><span id="head14">6.2 编写client的main函数</span></h3>
<pre><code class="language-go">package main

import (
        "context"
        "fmt"
        "gomicro-quickstart/grpc_client/service"
        "google.golang.org/grpc"
        "log"
)

func main() {
        // 1. 新建连接,端口是服务端开放的8082端口
        // 并且添加grpc.WithInsecure(),不然没有证书会报错
        conn, err := grpc.Dial(":8082", grpc.WithInsecure())
        if err != nil {
                log.Fatal(err)
        }

        // 退出时关闭链接
        defer conn.Close()

        // 2. 调用Product.pb.go中的NewProdServiceClient方法
        productServiceClient := service.NewProdServiceClient(conn)

        // 3. 直接像调用本地方法一样调用GetProductStock方法
        resp, err := productServiceClient.GetProductStock(context.Background(), &amp;service.ProductRequest{ProdId: 233})
        if err != nil {
                log.Fatal("调用gRPC方法错误: ", err)
        }

        fmt.Println("调用gRPC方法成功,ProdStock = ", resp.ProdStock)
}

</code></pre>
<h3 id="63-运行并显示结果"><span id="head15">6.3 运行并显示结果</span></h3>
<ul>
<li>先把服务端运行起来</li>
<li>再把客户端运行起来</li>
</ul>
<p>然后客户端输出正确的结果,第一个go的gRPC调用运行成功</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ghnalvrgw4j31c10pzgqu.jpg" alt="" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/baoshu/p/13488106.html
頁: [1]
查看完整版本: go语言gRPC系列(一) - gRPC入门