基于 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
,以便可以通过该地址加速依赖包下载:
按照上图所示设置完成后,点击「Create」按钮创建该项目,然后就可以在项目根目录下自动生成一个 go.mod
文件,你可以将其类比做 PHP 中的 composer.json
,或者 NPM 中的 package.json
,Go Module 基于该文件对项目依赖进行管理:
然后你可以在 GoLand 底部工具栏中点开「Terminal」,运行 go env
查看 Go Module 是否已启用以及代理地址设置是否成功:
至此,我们就基于 GoLand 提供的工具快速完成了微服务项目的初始化。
安装 Protobuf 相关工具
接下来,我们来安装基于 Protobuf 格式服务声明文件自动生成微服务原型代码所需要的一些工具。
安装 protoc-gen-micro
首先是 protoc-gen-micro
,该工具适用于在 Micro 框架中基于 Protobuf 文件生成服务代码,在项目根目录下运行如下 go get
命令安装:
go get -u github.com/micro/protoc-gen-micro
安装完成后,即可在 go.mod
的 require
中看到依赖声明:
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 中看到:
安装 protoc
protoc-gen-micro
依赖 protoc
和 protoc-gen-go
,所以要使用 protoc-gen-micro
还要安装它们。
可以从这里 https://github.com/protocolbuffers/protobuf/releases 下载最新版的 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-go
和 protoc-gen-micro
可执行文件。
使用 Etcd 作为注册中心
由于 Go Micro 框架默认已经不能开箱支持 Consul,所以我们选择 Etcd 作为注册中心进行服务发现。你可以在这个页面 https://github.com/etcd-io/etcd/releases 下载最新版本的 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
指令进行客户端测试:
编写服务
至此,我们已经做好了所有外围的准备工作,接下来,可以正式通过 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 --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 界面即可完成设置):
后面我们都是基于这个系统环境设置进行演示,不再额外传入 --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
已经包含在系统路径中,所以可以直接通过下面的指令启动:
该 API 网关通过 8080
端口对外提供服务,我们可以通过 http://localhost:8080/service/endponit
的方式发起对后端微服务接口的请求,API 网关会通过解析 URL 路径自动将请求路由给后端对应的微服务处理器方法。
注:这里注册中心通过
MICRO_REGISTRY
环境变量读取,如果没有设置需要手动指定--registry=etcd
。
这个时候通过 curl 测试服务接口的访问:
发现报错了,这是因为 Micro API 默认服务所在的命名空间是 go.micro.api
,而这里我们并没有设置任何命名空间,所以需要将命名空间指定为空字符串,然后设置处理器为 rpc
(尚未实现专门的 api
处理器方法):
这样一来,再次测试的话就可以正常访问了:
更多关于 Micro 的使用示例和底层实现,参考后续 API 网关相关教程。
友情提示:后续框架篇教程默认依然基于 GOPATH + Consul 进行介绍,你可以按照本篇教程进行代入和调整。
70 Comments
go 升级 1.15 之后,无法启动运行,报错如下:
目前采用的解决方案是降级到 go 1.13,请问 go 1.15 是否有解决方案?
解决方案见:#54
通过该命令指定 namespace 之后,curl 仍然500错误
以下是服务信息
该问题已解决,升级 go-micro 以及 protoc-gen-micro 版本即可,升级方式如下: protoc-gen-micro
go-micro
注:该解决方案支持 go 1.15 https://xueyuanjun.com/post/21585#comment52
$ go run main.go
command-line-arguments
./main.go:27:45: cannot use service.Server() (type "github.com/micro/go-micro/v2/server".Server) as type "github.com/micro/go-micro/server".Server in argument to greeter.RegisterGreeterHandler: "github.com/micro/go-micro/v2/server".Server does not implement "github.com/micro/go-micro/server".Server (wrong type for Handle method) have Handle("github.com/micro/go-micro/v2/server".Handler) error want Handle("github.com/micro/go-micro/server".Handler) error
把v2去掉可以跑起来
使用v2版本后,proto.RegisterGreeterHandler(service.Server(), new(GreeterServiceHandler)) 该行报#55的错
我也遇到这样,但把命名空间加到代码里就可以里。main.go => service := micro.NewService(micro.Name("go.micro.api.Greeter"),) 在client.go 里也如此:client.go => service := micro.NewService(micro.Name("go.micro.api.Greeter.Client")); greeter := proto.NewGreeterService("go.micro.api.Greeter", service.Client()) 这样运行代码 micro api --handler=rpc 就可以里,不知道为什么文章demo里到--namespace 没起作用
有没有人跟我一样遇到这种情况嘛
在go.mod 文件中加上 replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
重新编译proto文件