本文相关代码:gitee


前言

由于go语言和go-micro框架迭代迅速,我在学习go-micro框架查询资料时,经常因为过时的资料遇到莫名其妙的错误。因此我决心根据自己的实践与摸索,编写一个适合go-micro V2版本切实可用的go-micro开发指南,重点记录开发的流程各类问题的解决方案。希望这份笔记能够帮助以后的自己,和所有看到它的朋友实现以下目标:

  1. 顺利搭建一个当前版本下被反复验证可行的go-micro系统
  2. 通过一系列插件引入对go-micro框架全貌有大致理解
  3. 在笔记中快速检索到go-micro框架的各种搭建问题解决方案

注意:本章主要是使用micro工具自动生成第一个微服务,并用客户端调用它,不会详细讲解其中的代码,具体代码逻辑以及如何手写项目,请看第二章。


一、Go-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-plugins

3.go-micro通信流程

Server监听客户端的调用,和Brocker推送过来的信息进行处理。并且Server端需要向Register注册自己的存在或消亡,这样Client才能知道自己的状态
Register服务的注册的发现,Client端从Register中得到Server的信息,然后每次调用都根据算法选择一个的Server进行通信,当然通信是要经过编码/解码,选择传输协议等一系列过程的
如果有需要通知所有的Server端可以使用Brocker进行信息的推送,Brocker 信息队列进行信息的接收和发布


二、第一个微服务 hello-service

开始第一个项目前,请先使用go version命令查看您的语言版本,因为截至发文时(2020.9.10) go-micro 依赖的部分包尚未适配go1.15版本,因此本教程建议在go1.14+版本中运行。

同时考虑到国内的网络环境,请确保go envGO111MODULE=on且配置了正确的依赖代理服务(推荐GOPROXY=https://goproxy.io)。


1.安装micro

这里需要说明一下,我们微服务使用的框架叫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
   以下省略...


2.自动生成代码

实际上,通过阅读帮助文档不难发现,micro本身也是一个微服务,后续我们会讲到他的更多作用,这里我们重点讲如何使用micro自动生成我们自己的微服务代码。

执行如下命令:

# 首先进入到项目目录,这里我以我自己的go项目目录为例
cd go-workspace

# 调用micro生成代码
# 默认情况下Micro生成的代码会放到GOPATH/src中,通过配置--gopath=false可以选择在当前目录下
micro new --gopath=false hello

3.项目结构

代码生成后,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网共享给电脑,有奇效,当然梯子效果更佳。


4.protobuf

Protobuf是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。在go-micro中,官方虽然也支持json格式数据传输,但默认是protobuf。

这种格式相对json有两个弊端:

  • 首先,protobuf有一定学习成本,好在日常使用只需要参考自动代码中hello.proto的格式学会定义接口和参数即可(熟练后强烈建议进一步深入学习)
  • 其次,需要安装不少的工具,go-micro官方也是考虑到这一点,因此在代码生成文档的最后详细列出了工具安装步骤(考虑到不同版本问题,请务必使用你的控制台中看到的命令,一下内容仅供参考)
# 首先要下下载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文件,在最上面加入一行信息:
插入go_package
这行信息用于指定生成代码所属的包路径,因为我们将代码生成在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保持一致。


5.启动服务

至此,所有代码都已经生成完毕,理论上go mod tidy && go run main.go就可以愉快的跑起来了。

> go run main.go
2020-09-10 11:30:29  file=v2@v2.9.1/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\etcd@v3.3.22+incompatible\clientv3\balancer\picker\err.go:37:44: undefined: balancer.PickOptions
F:\go\pkg\mod\github.com\coreos\etcd@v3.3.22+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\etcd@v3.3.22+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:114:78: undefined: resolver.BuildOption
F:\go\pkg\mod\github.com\coreos\etcd@v3.3.22+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\go-micro@v1.18.0\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\go-micro@v1.18.0\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

三、服务调用 hello-cli

如上所述,我们已经启动了第一个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项目。

支持一下

原创不易,支持一下买杯咖啡,谢谢:p
支持一下

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐