本文相关代码:gitee
由于go语言和go-micro框架迭代迅速,我在学习go-micro框架查询资料时,经常因为过时的资料遇到莫名其妙的错误。因此我决心根据自己的实践与摸索,编写一个适合go-micro V2版本切实可用的go-micro开发指南,重点记录开发的流程和各类问题的解决方案。希望这份笔记能够帮助以后的自己,和所有看到它的朋友实现以下目标:
注意:本章主要是使用micro工具自动生成第一个微服务,并用客户端调用它,不会详细讲解其中的代码,具体代码逻辑以及如何手写项目,请看第二章。
以下内容摘抄自:http://www.topgoer.com
1.go-micro简介
Go Micro是一个插件化的基础框架,基于此可以构建微服务,Micro的设计哲学是可插拔的插件化架构
在架构之外,它默认实现了consul作为服务发现(2019年源码修改了默认使用mdns),通过http进行通信,通过protobuf和json进行编解码2.go-micro的主要功能
服务发现: 自动服务注册和名称解析。服务发现是微服务开发的核心。当服务A需要与服务B通话时,它需要该服务的位置。默认发现机制是多播DNS(mdns),一种零配置系统。您可以选择使用SWIM协议为p2p网络设置八卦,或者为弹性云原生设置设置consul
负载均衡: 基于服务发现构建的客户端负载均衡。一旦我们获得了服务的任意数量实例的地址,我们现在需要一种方法来决定要路由到哪个节点。我们使用随机散列负载均衡来提供跨服务的均匀分布,并在出现问题时重试不同的节点
消息编码: 基于内容类型的动态消息编码。客户端和服务器将使用编解码器和内容类型为您无缝编码和解码Go类型。可以编码任何种类的消息并从不同的客户端发送。客户端和服务器默认处理此问题。这包括默认的protobuf和json
请求/响应: 基于RPC的请求/响应,支持双向流。我们提供了同步通信的抽象。对服务的请求将自动解决,负载平衡,拨号和流式传输。启用tls时,默认传输为http / 1.1或http2 Async
Messaging: PubSub是异步通信和事件驱动架构的一流公民。事件通知是微服务开发的核心模式。启用tls时,默认消息传递是点对点http / 1.1或http2
可插拔接口: Go Micro为每个分布式系统抽象使用Go接口,因此,这些接口是可插拔的,并允许Go Micro与运行时无关,可以插入任何基础技术 插件地址:https://github.com/micro/go-plugins3.go-micro通信流程
Server监听客户端的调用,和Brocker推送过来的信息进行处理。并且Server端需要向Register注册自己的存在或消亡,这样Client才能知道自己的状态
Register服务的注册的发现,Client端从Register中得到Server的信息,然后每次调用都根据算法选择一个的Server进行通信,当然通信是要经过编码/解码,选择传输协议等一系列过程的
如果有需要通知所有的Server端可以使用Brocker进行信息的推送,Brocker 信息队列进行信息的接收和发布
开始第一个项目前,请先使用go version
命令查看您的语言版本,因为截至发文时(2020.9.10) go-micro 依赖的部分包尚未适配go1.15版本,因此本教程建议在go1.14+版本中运行。
同时考虑到国内的网络环境,请确保go env
中 GO111MODULE=on
且配置了正确的依赖代理服务(推荐GOPROXY=https://goproxy.io
)。
这里需要说明一下,我们微服务使用的框架叫go-micro
,他将被集成到我们的项目中。
同时,官方还为我们提供了另一个叫micro
的项目,他是一个官方工具包,主要是通过编译后的可执行文件为我们开发提供各种帮助,这里我们安装的就是micro
工具包:
go get github.com/micro/micro/v2
安装完成后可以执行micro help
看到如下信息表示已经安装成功:
NAME:
micro - A microservice runtime
Use `micro [command] --help` to see command specific help.
USAGE:
micro [global options] command [command options] [arguments...]
VERSION:
latest
COMMANDS:
server Run the micro server
以下省略...
实际上,通过阅读帮助文档不难发现,micro
本身也是一个微服务,后续我们会讲到他的更多作用,这里我们重点讲如何使用micro自动生成我们自己的微服务代码。
执行如下命令:
# 首先进入到项目目录,这里我以我自己的go项目目录为例
cd go-workspace
# 调用micro生成代码
# 默认情况下Micro生成的代码会放到GOPATH/src中,通过配置--gopath=false可以选择在当前目录下
micro new --gopath=false hello
代码生成后,micro会输出大量项目信息,我们观察这段信息。
首先是项目结构,可以看到micro为我们创建了一个hello
文件夹,并在里面写入了一个完整的go mod
项目:
Creating service go.micro.service.hello in hello
.
├── main.go #程序入口
├── generate.go
├── plugin.go
├── handler #服务的主要处理逻辑,(仅仅是)类似java spring项目的service
│ └── hello.go
├── subscriber #消息处理逻辑
│ └── hello.go
├── proto #存放proto文件,并生成相关业务代码
│ └── hello
│ └── hello.proto
├── Dockerfile #docker镜像配置文件
├── Makefile #若干make命令帮助我们自动化管理项目
├── README.md
├── .gitignore
└── go.mod
这里补充说明一下,为了照顾大多数读者,本教程尽量不会使用make命令,但还是建议 win环境的朋友自行搜索MinGW的安装和配置,因为这东西用过你就知道真香。
如果你安装完mingw-get-setup在执行install的时候发现总是下载超时,请用USB将手机4G网共享给电脑,有奇效,当然梯子效果更佳。
Protobuf是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。在go-micro中,官方虽然也支持json格式数据传输,但默认是protobuf。
这种格式相对json有两个弊端:
hello.proto
的格式学会定义接口和参数即可(熟练后强烈建议进一步深入学习)# 首先要下下载protoc协议的编解码器,下载安装后放在任意`path`路径下一边被命令行调用
# 我一般会把他放在 GOPATH/bin 目录下
download protoc zip packages (protoc-$VERSION-$PLATFORM.zip) and install:
visit https://github.com/protocolbuffers/protobuf/releases
# 接下来是下载三个go语言的proto包,用于生成对应go语言的micro代码
download protobuf for micro:
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go get github.com/micro/micro/v2/cmd/protoc-gen-micro
# 最后是进入到你新建的项目中,通过proto文件生成micro代码
compile the proto file hello.proto:
cd hello
# 没有make的win环境朋友可以直接复制执行makefile文件里面的命令,效果是一样的:
# protoc --proto_path=. --micro_out=Mproto/imports/api.proto=github.com/micro/go-micro/v2/api/proto:. --go_out=Mproto/imports/api.proto=github.com/micro/go-micro/v2/api/proto:. proto/hello/hello.proto
#这一步先不要着急执行,往下看(重要的话说三遍)
#这一步先不要着急执行,往下看(重要的话说三遍)
#这一步先不要着急执行,往下看(重要的话说三遍)
make proto
如果你不听话,直接执行了,会发现程序顺利执行,只是有一条警告信息:
2020/09/10 11:01:43 WARNING: Missing 'go_package' option in "proto/hello/hello.p
roto",
please specify it with the full Go package path as
a future release of protoc-gen-go will require this be specified.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#p
ackage for more information.
这里,我们需要预处理一下hello.proto
文件,在最上面加入一行信息:
这行信息用于指定生成代码所属的包路径,因为我们将代码生成在hello.proto
同目录下,因此包路径使用proto/hello
,如果你的项目想把生成的go文件放在其他路径,则根据实际情况填写。
完成修改后,执行make proto
或者完整的生成命令:
> protoc --proto_path=. --micro_out=Mproto/imports/api.proto=github.com/micro/go-m
icro/v2/api/proto:. --go_out=Mproto/imports/api.proto=github.com/micro/go-micro/
v2/api/proto:. proto/hello/hello.proto
这里官方写法比较冗长,如果是当前目录下可以简写为protoc --proto_path=. --micro_out=. --go_out=. proto/hello/hello.proto
注意,其中的--go_out
和--micro_out
参数指定了生成文件的输出路径,应与上一步我们修改的go_package
保持一致。
至此,所有代码都已经生成完毕,理论上go mod tidy && go run main.go
就可以愉快的跑起来了。
> go run main.go
2020-09-10 11:30:29 file=[email protected]/service.go:200 level=info Starting [service] go.micro.service.hello
2020-09-10 11:30:29 file=grpc/grpc.go:864 level=info Server [grpc] Listening on [::]:60662
2020-09-10 11:30:29 file=grpc/grpc.go:881 level=info Broker [http] Connected to 127.0.0.1:60663
2020-09-10 11:30:29 file=grpc/grpc.go:697 level=info Registry [mdns] Registering node: go.micro.service.hello-3b9e045d-6db0-47a5-9504-743b4d9175ba
2020-09-10 11:30:29 file=grpc/grpc.go:730 level=info Subscribing to topic: go.micro.service.hello
根据日志信息,会发现框架帮我们启动了
go.micro.service.hello-3b9e045d-6db0-47a5-9504-743b4d9175ba
mdns
grpc
的Server服务:60662
http
的Broker服务:60663
go.micro.service.hello
你可以在另一个命令行窗口执行micro list services
命令,可以看到我们的服务go.micro.service.hello
已经被注册:
> micro list services
go.micro.service.hello
micro.http.broker
如果你看到如下报错,是由于第三方库兼容性造成的。这个问题困扰了我很久,但是在我写文时,已经被官方最新版解决:
# github.com/coreos/etcd/clientv3/balancer/picker
F:\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\picker\err.go:37:44: undefined: balancer.PickOptions
F:\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\picker\roundrobin_balanced.go:55:54: undefined: balancer.PickOptions
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
F:\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:114:78: undefined: resolver.BuildOption
F:\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:182:31: undefined: resolver.ResolveNowOption
# github.com/micro/go-micro/transport/quic
F:\go\pkg\mod\github.com\micro\[email protected]\transport\quic\quic.go:54:12: q.s.Close undefined (type quic.Session has no field or method Close)
F:\go\pkg\mod\github.com\micro\[email protected]\transport\quic\quic.go:121:3: unknown field 'IdleTimeout' in struct literal of type quic.Config
在 go.mod 文件中添加以下两行 replace 解决编译问题
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
replace github.com/lucas-clemente/quic-go => github.com/lucas-clemente/quic-go v0.14.1
如上所述,我们已经启动了第一个go-micro微服务hello,怎么调用他呢?我们可以新建一个hello-cli.go
文件:
package main
import (
"context"
"github.com/micro/go-micro/v2"
"github.com/micro/go-micro/v2/client"
pb "hello/proto/hello"
"log"
)
func main() {
// 这里以HelloService默认提供的Call接口调用为例示范服务的调用
// 可以看到他的调用就像调用本地方法一样,go-micro为我们隐藏了背后的服务注册、发现、负载均衡以及网络操作
testCallFunc()
// 这里示范消息的发送
testSendMessage()
}
func testCallFunc(){
// 获取hello服务
// 这里第一个参数"go.micro.service.hello"必须与hello-service注册信息一致
// 一般由micro生成的项目默认服务名为:{namespace 默认[go.micro]}.{type 默认[service]}.{项目名}组成
// 如果要修改默认值,在生成项目时可以这样: micro --namespace=XXX --type=YYYY ZZZZ
// 当然也可以直接修改main.go中micro.Name("go.micro.service.hello")的内容
helloService := pb.NewHelloService("go.micro.service.hello", client.DefaultClient)
// 默认生成的hello服务中自带三个接口: Call() Stream() PingPong(),分别对应参数调用、流传输和心跳
resp, err := helloService.Call(context.Background(), &pb.Request{
Name: "xiao xie",
})
if err != nil {
log.Panic("call func", err)
}
log.Println("call func success!", resp.Msg)
}
func testSendMessage(){
// 消息主题,定义规则与服务一致
// 同样,也可以修改main.go的micro.RegisterSubscriber("go.micro.service.hello", service.Server(), new(subscriber.Hello))
const topic = "go.micro.service.hello"
// 获取消息发送接口,这里我一直使用的时micro.NewPublisher()
// 但在写文时发现NewPublisher()已经被废止,改为NewEvent(),二者参数和返回值一致
event := micro.NewEvent(topic, client.DefaultClient)
if err := event.Publish(context.Background(), &pb.Message{
Say: "hello server!",
}); err != nil {
log.Panic("send msg", err)
}
log.Println("send msg success!")
}
执行go run hello-cli.go
,即可看到运行结果:
> go run hello-cli.go
2020-09-10 12:43:25.446682 I | call func success! Hello xiao xie
2020-09-10 12:43:25.547441 I | send msg success!
至此,一个基础的微服务就部署完成并通过测试了。
本章我们介绍了借助micro
工具包快速编写go-micro微服务的基本操作流程,和微服务的两种基本调用方式gRpc
和消息
,由于篇幅关系我们并没有详解自动生成的代码的含义。
下一章我们将参考示例代码结构手动编写一个微服务,并在后续章节中逐步开发一个微服务化的todolist
项目。