基于 Go Module 管理依赖并将注册中心调整为 Etcd

前言

由于 Go Micro 框架去年年底将 Consul 从默认支持的注册中心调整为通过插件机制引入,导致很多同学反映按照基于 Go Micro 框架构建一个简单的微服务接口这篇教程遇到很多坑。另外,之前这篇教程是按照 GOPATH 方式管理项目依赖,很多人也会因为网络问题导致无法下载依赖包。尤其是对新手而言,有点无所适从,心态已然变成了从入门到想放弃。

所以学院君为此更新一篇教程,基于全新的 Go Module 方式管理依赖,使用 Go Module 的好处是可以配置代理来加速国内 Go 依赖包的下载,此外,还将使用最新版本的 Go Micro 并基于 Etcd 作为注册中心来演示如何构建第一个微服务接口。

注:本教程适用于 Go 1.11+ 版本,因为 Go 1.11 才正式引入了 Go Module 作为包管理器。

创建新项目

首先我们打开 GoLand,基于 Go Modules 创建一个新项目 hello,这里我将项目保存到了 ~/Development/go/src/hello 这个路径,并且设置环境变量中的代理地址为 https://goproxy.io,以便可以通过该地址加速依赖包下载:

新建微服务项目

设置GOPROXY

按照上图所示设置完成后,点击「Create」按钮创建该项目,然后就可以在项目根目录下自动生成一个 go.mod 文件,你可以将其类比做 PHP 中的 composer.json,或者 NPM 中的 package.json,Go Module 基于该文件对项目依赖进行管理:

go.mod

然后你可以在 GoLand 底部工具栏中点开「Terminal」,运行 go env 查看 Go Module 是否已启用以及代理地址设置是否成功:

go env

至此,我们就基于 GoLand 提供的工具快速完成了微服务项目的初始化。

安装 Protobuf 相关工具

接下来,我们来安装基于 Protobuf 格式服务声明文件自动生成微服务原型代码所需要的一些工具。

安装 protoc-gen-micro

首先是 protoc-gen-micro,该工具适用于在 Micro 框架中基于 Protobuf 文件生成服务代码,在项目根目录下运行如下 go get 命令安装:

go get -u github.com/micro/protoc-gen-micro 

下载protoc-gen-micro

安装完成后,即可在 go.modrequire 中看到依赖声明:

module hello

go 1.13

require (
	github.com/golang/protobuf v1.4.0 // indirect
	github.com/micro/protoc-gen-micro v1.0.0 // indirect
)

系统默认会将 protoc-gen-micro 可执行文件安装到 GOPATH 路径的 bin 目录下,如果你不知道默认 GOPATH 路径在哪里,可以在 GoLand 的 Preferences 中看到:

GOPATH路径

安装 protoc

protoc-gen-micro 依赖 protocprotoc-gen-go,所以要使用 protoc-gen-micro 还要安装它们。

可以从这里 https://github.com/protocolbuffers/protobuf/releases 下载最新版的 protoc

下载protoc

选择与自己系统相匹配的压缩包,比如我的是 Mac 系统,则选择 osx 64 位下载,解压,然后将其移动到指定位置,并将 protoc 二进制可执行文件所在的 bin 目录放到系统路径中以便可以全局调用(以下是 Mac 系统指令,Windows 下请自行通过图形化界面设置系统路径):

mv ~/Downloads/protoc-3.11.4-osx-x86_64 ~/Development/tools
vi ~/.zshrc
export PATH="/Users/sunqiang/go/tools/protoc-3.8.0-osx-x86_64/bin:$PATH"
source ~/.zshrc

你可以运行 protoc --version 检测是否可以在任意位置调用 protoc 命令。

安装 protoc-gen-go

最后通过如下命令安装 protoc-gen-go,该依赖包是 Protobuf 的 Go 语言实现:

go get -u github.com/golang/protobuf/protoc-gen-go

安装完成后,同样应该可以在 GOPATH 路径下的 bin 目录中看到 protoc-gen-go。你需要将 $GOPATH/bin 目录也放到系统路径中,参照上面 protoc 的设置即可,以便可以全局调用 protoc-gen-goprotoc-gen-micro 可执行文件。

使用 Etcd 作为注册中心

由于 Go Micro 框架默认已经不能开箱支持 Consul,所以我们选择 Etcd 作为注册中心进行服务发现。你可以在这个页面 https://github.com/etcd-io/etcd/releases 下载最新版本的 Etcd:

下载Etcd

选择自己系统对应的 zip 或者 tar.gz 包下载即可,以 Mac 为例,选择 etcd-v3.4.7-darwin-amd64.zip 下载,然后解压到本地指定目录:

mv ~/Downloads/etcd-v3.4.7-darwin-amd64.zip ~/Development/tools
cd ~/Development/tools
unzip etcd-v3.4.7-darwin-amd64.zip

进入该目录并启动 Etcd 服务器,然后不要关掉这个窗口,否则服务器会中止:

cd etcd-v3.4.7-darwin-amd64
./etcd

然后可以新开一个窗口,进入 etcd 所在目录,通过 etcdctl 指令进行客户端测试:

测试Etcd键值存取

编写服务

至此,我们已经做好了所有外围的准备工作,接下来,可以正式通过 Go Micro 框架编写第一个微服务接口了。

服务接口声明

hello 目录下新建一个 proto 子目录,然后在 proto 目录下创建一个 Protobuf 格式的服务接口声明文件 greeter.proto

syntax = "proto3";
    
service Greeter {
	rpc Hello(HelloRequest) returns (HelloResponse) {}
}
    
message HelloRequest {
	string name = 1;
}
    
message HelloResponse {
	string greeting = 1;
}

如上述代码所示,我们定义了一个名为 Greeter 的服务,该服务中包含一个 Hello 方法,该方法接收一个 HelloRequest 对象,然后返回一个 HelloResponse 对象,这两个对象都只包含一个参数。

通过服务声明生成原型代码

接下来,我们就可以借助前面安装的 protoc 工具通过服务声明文件生成相应的服务原型代码(在 hello 项目根目录下运行):

protoc -I. --go_out=plugins=micro:. proto/greeter.proto

为了避免后续修改 greeter.proto 文件后需要频繁执行该指令,可以在项目根目录下创建一个 Makefile 文件,然后将该指令放到 build 构建步骤中:

build:
	protoc -I. --go_out=plugins=micro:. proto/greeter.proto

这样,就可以通过运行 make build 执行该指令了,执行成功后,会在 hello/proto 目录下生成一个包含服务原型代码的 greeter.pb.go 文件:

自动生成服务原型文件

如果在执行上述 protoc 命令出错,提示 micro 插件不存在:

protoc命令出错

可以将指令调整为下面这个重新执行(项目根目录下执行):

protoc --proto_path=. --micro_out=. --go_out=. proto/greeter.proto

编写服务实现代码

接下来,我们就可以在项目根目录下创建一个 main.go,然后基于上述服务原型代码来编写第一个微服务服务端接口了:

package main

import (
    "context"
    "fmt"
    "github.com/micro/go-micro"
    proto "hello/proto"
)

type GreeterServiceHandler struct{}

func (g *GreeterServiceHandler) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
    rsp.Greeting = " 你好, " + req.Name
    return nil
}

func main()  {
    // 创建新的服务
    service := micro.NewService(
        micro.Name("Greeter"),
    )

    // 初始化,会解析命令行参数
    service.Init()

    // 注册处理器,调用 Greeter 服务接口处理请求
    proto.RegisterGreeterHandler(service.Server(), new(GreeterServiceHandler))

    // 启动服务
    if err := service.Run(); err != nil {
        fmt.Println(err)
    }
}

这里的服务处理器类 GreeterServiceHandler 实现了服务原型代码中的 GreeterHandler 处理器接口,然后我们在入口函数 main 中初始化名为 Greeter 的微服务,并通过 proto.RegisterGreeterHandler 方法将 GreeterServiceHandler 处理器实现绑定到微服务的服务器中(默认是 rpc 服务器),最后通过 service.Run 启动服务,这样,就可以通过 GreeterServiceHandler 处理器方法处理客户端请求了。

在项目根目录下运行如下命令自动下载服务实现代码中的依赖:

go mod tidy

然后启动服务:

go run main.go --registry=etcd

启动服务端

注意,这里我们手动通过 --registry=etcd 指定注册中心为 etcd,否则默认的注册中心是 mdns(在 Windows 系统中默认不可用)。

当然,你也可以通过设置 GoLand 的 Go Modules 环境变量 MICRO_REGISTRY=etcd 来统一设置,这样,就不需要在启动服务时额外传入这个选项了(打开 GoLand 的 Preferences 界面即可完成设置):

设置 MICRO_REGISTRY 环境变量

后面我们都是基于这个系统环境设置进行演示,不再额外传入 --registry 指定注册中心,望知晓。

接下来,就可以通过客户端调用这个远程服务了。

客户端调用

我们来编写一段简单的客户端测试代码,在项目根目录下创建 client.go 并编写代码如下:

package main

import (
    "context"
    "fmt"
    "github.com/micro/go-micro"
    proto "hello/proto"
)

func main() {
    // 创建一个新的服务
    service := micro.NewService(micro.Name("Greeter.Client"))
    // 初始化
    service.Init()

    // 创建 Greeter 客户端
    greeter := proto.NewGreeterService("Greeter", service.Client())

    // 远程调用 Greeter 服务的 Hello 方法
    rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "学院君"})
    if err != nil {
        fmt.Println(err)
    }

    // Print response
    fmt.Println(rsp.Greeting)
}

我们通过原型代码中的 proto.NewGreeterClient 创建客户端,然后通过 RPC 远程调用 Hello 方法,并打印响应结果:

客户端测试

返回值符合预期,表明我们的微服务接口可以正常工作。至此,我们就基于 Go Micro 框架成功创建了第一个微服务接口,非常简单吧?

提供 HTTP 服务接口

上述客户端代码是以 RPC 方式请求服务接口的,能否通过 HTTP 请求的方式获取响应结果呢?

当然可以,Go Micro 框架为我们提供了一个 API 网关实现 —— Micro API,我们可以基于该组件快速对外提供 HTTP + JSON 格式的服务接口。

使用之前,需要先安装 micro 包:

go get github.com/micro/micro/v2

安装完成后,会在 $GOPATH/bin 目录下创建一个 micro 可执行文件,由于 $GOPATH/bin 已经包含在系统路径中,所以可以直接通过下面的指令启动:

启动 micro api

该 API 网关通过 8080 端口对外提供服务,我们可以通过 http://localhost:8080/service/endponit 的方式发起对后端微服务接口的请求,API 网关会通过解析 URL 路径自动将请求路由给后端对应的微服务处理器方法。

注:这里注册中心通过 MICRO_REGISTRY 环境变量读取,如果没有设置需要手动指定 --registry=etcd

这个时候通过 curl 测试服务接口的访问:

通过 curl 测试服务接口

发现报错了,这是因为 Micro API 默认服务所在的命名空间是 go.micro.api,而这里我们并没有设置任何命名空间,所以需要将命名空间指定为空字符串,然后设置处理器为 rpc(尚未实现专门的 api 处理器方法):

重启 micro api

这样一来,再次测试的话就可以正常访问了:

通过 curl 测试服务接口

更多关于 Micro 的使用示例和底层实现,参考后续 API 网关相关教程。

友情提示:后续框架篇教程默认依然基于 GOPATH + Consul 进行介绍,你可以按照本篇教程进行代入和调整。

上一篇: 基于 Go Micro 框架构建一个简单的微服务接口

下一篇: 通过 HTTP 请求调用 Go Micro 提供的微服务接口