go-micro-微服务框架记录
go-micro-微服务框架记录
代码实践仓库: https://github.com/yangxuan0261/GoMicro
fork 官方 examples: https://github.com/yangxuan0261/examples
前篇
- 官方
- go-micro 微服务开发中文手册 - https://www.kancloud.cn/linimbus/go-micro
- 编写Golang服务 - https://micro.mu/docs/writing-a-go-service.html
- service.Run() 内部实现 - https://www.kancloud.cn/linimbus/go-micro/529030
- Micro文档 - https://micro.mu/docs/cn/
- Micro 中国站教程系列 ( 推荐 ) - https://github.com/micro-in-cn/tutorials
- slack 社区 - https://micro-services.slack.com
- golang微服务框架go-micro 入门笔记 - http://www.spring4all.com/common/user/9843
- Go-Micro 文档 - https://www.bookstack.cn/read/go-micro/gm.md
- 牌类游戏使用微服务重构笔记
- 微服务架构详解 - https://zhuanlan.zhihu.com/p/34392808
- go-micro 框架初探 - https://segmentfault.com/a/1190000017572032
- 基于Go Micro的微服务架构本地实战 - https://www.wangtianyi.top/blog/2019/03/26/ji-yu-go-microde-wei-fu-wu-jia-gou-ben-di-shi-zhan/
- 团队准备使用 Go 构建微服务,请问大家有好的框架推荐么? - https://golangtc.com/t/5c75fb7db17a826706f8ada3
- 基于Go Micro的微服务架构本地实战 - https://www.wangtianyi.top/blog/2019/03/26/ji-yu-go-microde-wei-fu-wu-jia-gou-ben-di-shi-zhan/
- 一个微服务游戏服务架构 - http://gonet2.github.io/
- go-micro微服务框架 - https://www.qfgolang.com/?special=go-microweifuwukuangjia
入门资料
- Go Micro 入门指南 - https://erikjiang.github.io/2018/07/05/GoMicroGuide/
- golang微服务框架go-micro 入门笔记1.搭建 go-micro环境 - http://www.spring4all.com/article/15595
- go微服务框架go-micro深度学习-目录 - https://www.cnblogs.com/li-peng/category/1292629.html
go-micro的组件
架构图:
Registry组件:服务发现组件,提供服务发现机制:解析服务名字至服务地址。目前支持的注册中心有consul、etcd、 zookeeper、dns、gossip等
Selector组件:构建在Registry之上的客户端智能负载均衡组件,用于Client组件对Registry返回的服务进行智能选择。
Broker组件:发布/订阅组件,服务之间基于消息中间件的异步通信方式,默认使用http方式,线上通常使用消息中间件,如Kafka、RabbitMQ等。
Transport组件:服务之间同步通信方式。
Codec组件:服务之间消息的编码/解码。
Server组件:服务主体,该组件基于上面的Registry/Selector/Transport/Broker组件,对外提供一个统一的服务请求入口。
Client组件:提供访问微服务的客户端。类似Server组件,它也是通过Registry/Selector/Transport/Broker组件实现查找服务、负载均衡、同步通信、异步消息等功能。
(一般分布式架构, 服务间的 rpc 都是通过 Server,Client 实现的)
常见的微服务架构图. 出处
go-micro 默认值
参考: micro 在 创建并运行之后 时,各个 options/plugin 的默认值是什么? - https://gitissue.com/issues/5a20634a9a954719b8dd694e
各个 option/plugin 的默认值是:
- Service Option: 参见 micro/go-micro/options.go
- broker: “http”
- client: “rpcclient”
- server: “rpcserver”
- registry: “mdns”
- transport: “http”
- service id: servicename-uuid.NewUUID()
- Service Server Option: 参见
- address: “:0”
- name: “go-server”
- version: “1.0.0”
- Id: uuid.NewUUID()
- RegisterInterval: 1分钟(微服务自动向 registry 注册的心跳时间)
在 src/github.com/micro/go-micro/options.go 中可以看到默认值
1 | func newOptions(opts ...Option) Options { |
proto 生成 micro 所需文件
所需文件 protoc.exe, protoc-gen-go.exe, protoc-gen-micro.exe 可执行文件需要在环境变量中.
protoc.exe 下载地址: https://github.com/protocolbuffers/protobuf/releases
生成 protoc-gen-go.exe, protoc-gen-micro.exe
1
2go get -v -u github.com/golang/protobuf/protoc-gen-go // 安装protoc go插件
go get -v -u github.com/micro/protoc-gen-micro // protoc micro插件, 用于通过.proto文件生成.micro.go代码文件创建 proto 文件. user.proto
1
2
3
4
5
6
7
8
9
10
11
12
13syntax = "proto3";
service User {
rpc Hello(Request) returns (Response) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}然后生成 user.micro.go 和 user.pb.go 文件
1
2F:\a_link_workspace\go\GoWinEnv_Test01\src\GoMicro (master -> origin)
λ protoc -I ./protos/ --go_out=protoc-gen-go:./proto_gen --micro_out=./proto_gen ./protos/user.proto--micro_out
参数就是生成 xxx.micro.go 的参数
容错
Api 网关
- API - https://micro.mu/docs/cn/api.html
- Micro API - https://www.kancloud.cn/linimbus/go-micro/529031
- Go API (官方) - https://micro.mu/docs/go-api.html
使用流程
参考: 官方 example: examples\greeter
micro 启动 api 网关
micro api
即可启动 api 一个网关,默认的端口是8080
可以通过 命令行参数--address=0.0.0.0:8081
或者 设置环境MICRO_API_ADDRESS=0.0.0.0:8081
来修改监听端口1
2
3$ micro api --handler=api --address=0.0.0.0:8081
2019/10/11 19:50:55 Registering API Request Handler at /
2019/10/11 19:50:55 HTTP API Listening on [::]:8081启动 api 时一定指定
--handler=api
参数, 不然请求时会报错:go.micro.api 500
. 参考: Micro API使用 - https://blog.csdn.net/benben_2015/article/details/92675678启动 api 服务. (可以启动多个 api 服务, 会 负载均衡)
参考: src/GoMicro/test_api/api/api.go.
[api 服务 注意事项](#api 服务 注意事项)
1
2f:\go\GoWinEnv_new\src\GoMicro\test_api\api (master -> origin)
$ go run api.go命令行 curl 请求测试一下. ( 使用的是 bash.exe 命令行工具, cmder 有问题 )
1
2
3
4
5
6
7// get
$ curl http://localhost:8081/example/call?name=john
{"message":"我们已经收到你的请求,john"}
// post
$ curl -H 'Content-Type: application/json' -d '{data:123}' http://localhost:8081/example/foo/bar
收到消息:{data:123}api 服务 ( API gateway, 一般用来验证请求是否合法, 也就是 token 是否合法) 是对外访问的接口, api 服务之后是 后端不对外访问的的各种 微服务. 参考
examples\greeter
, 访问流程:
api 服务 注意事项
参考: src/GoMicro/test_api/api/api.go
使用的 proto 要引用 micro 的 go.api.Request/go.api.Response (
github.com/micro/go-micro/api/proto/api.proto
) . 如:1
2
3
4
5
6
7
8
9
10
11syntax = "proto3";
import "github.com/micro/go-micro/api/proto/api.proto";
service Example {
rpc Call(go.api.Request) returns(go.api.Response) {};
}
service Foo {
rpc Bar(go.api.Request) returns(go.api.Response) {};
}服务命名空间
如果启动 api 网关是没有指定
--namespace
, 默认使用的 namespace 是go.micro.api
, 所以 api 服务中的命名前缀就必须是go.micro.api
, 如:1
2
3
4
5
6func main() {
service := micro.NewService(
micro.Name("go.micro.api.example"), // 请求路径: http://localhost:8081/example/xxx/xxx
)
...
}假如 api 网关指定命名空间参数为
--namespace=wilker
1
$ micro api --handler=api --address=0.0.0.0:8081 --namespace=wilker
那么对应的服务
1
2
3
4
5
6func main() {
service := micro.NewService(
micro.Name("wilker.yang"), // 请求路径: http://localhost:8081/yang/xxx/xxx
)
...
}
RESTful api 风格
- 参考: examples\greeter\api\rest\rest.go
Service
Service 是 Client 和 Server 的封装,他包含了一系列的方法使用初始值去初始化 Service 和 Client.
Registry
服务的注册和发现,目前实现的 consul,mdns,etcd,etcdv3,zookeeper,kubernetes 等等,
简单来说,就是 Service 进行 Register,来进行注册,Client 使用watch方法进行监控,当有服务加入或者删除时这个方法会被触发,以提醒客户端更新 Service 信息。
如: etcdv3
Selector
- 使用Micro构建有弹性的、高容错的应用 - http://btfak.com/%E5%BE%AE%E6%9C%8D%E5%8A%A1/2016/05/15/resiliency/
- Go Micro Selector 源码分析 - https://segmentfault.com/a/1190000019802132
- go-micro 防坑指南 - https://magodo.github.io/go-micro-tips/
参考: 官方 example: github.com/micro/examples/client/dc_selector/dc_selector.go
中的代码
演示了 用于 负载均衡, 选择节点
启动 3 个 server
1
2
3
4
5
6
7
8
9
10
11
12// server1
f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\server\wrapper (master -> origin)
$ go run main.go
2019/10/10 15:59:03 Transport [http] Listening on [::]:63115
// server2
$ go run main.go
2019/10/10 15:59:03 Transport [http] Listening on [::]:63086
// server3
$ go run main.go
2019/10/10 15:59:03 Transport [http] Listening on [::]:63115启动 client, 根据 自定义的选择算法, 选择节点调用.
1
2
3
4
5
6
7
8
9f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\client\dc_selector (master -> origin)
$ go run dc_selector.go
--- Select, service:go.micro.srv.example
--- node, Address:192.168.1.190:63115, Id:go.micro.srv.example-e58aab01-9350-4397-bd72-8e5b8bcf0f12
--- node, Address:192.168.1.190:63028, Id:go.micro.srv.example-5f24a905-c356-409a-9f81-8a1ac09e5869
--- node, Address:192.168.1.190:63086, Id:go.micro.srv.example-31ba7a47-f5d2-4946-a9f7-8384f2550d26
--- Select, len(nodes):3
--- Select, call node:1
Call: 0 rsp: 5f24a905-c356-409a-9f81-8a1ac09e5869: Hello John
filter
selector 中可以自定义 filter 选出自己想要的节点
参考: 官方 example:
- 简单版 -
src/github.com/micro/examples/client/dc_filter/dc_filter.go
- 复杂版 -
src/github.com/micro/examples/client/dc_selector/dc_selector.go
在 rpc 的 Call
(请求->响应) 或者 Stream
(send->recv) 都可以添加 filter 过滤出需要的节点. 添加一个 自定义的 wrapper 丢到 micro 的 ClientWrapper 中
参考: src/GoSrv/test/srv_template/server/stream.go
Wrapper
请求拦截, 鉴权等, 合法才调用对应的请求
参考: 官方 example: src/github.com/micro/examples/wrapper
源码 github.com/micro/go-micro/options.go
1
2
3
4
5
6
7
8
9// Wrappers are applied in reverse order so the last is executed first.
func WrapClient(w ...client.Wrapper) Option {
return func(o *Options) {
// apply in reverse
for i := len(w); i > 0; i-- {
o.Client = w[i-1](o.Client)
}
}
}实际测试分一下两种情况
wrapper 分多出调用 WrapClient 传进去
1
2micro.WrapClient(logWrap1),
micro.WrapClient(logWrap2),输出则为 逆序
1
2[Call2] 客户端请求服务:greeter,方法:Greeter.Hello
[Call1] 客户端请求服务:greeter,方法:Greeter.Hellowrapper 一次调用 WrapClient 传进去
1
micro.WrapClient(logWrap1, logWrap2),
输出则为 顺序
1
2[Call1] 客户端请求服务:greeter,方法:Greeter.Hello
[Call2] 客户端请求服务:greeter,方法:Greeter.Hello
Client or Server
具体是拦截 客户端req, 还是 服务端handler, 可以从源码 github.com/micro/go-micro/options.go 中看到.
1 | func WrapClient(w ...client.Wrapper) Option { |
Codec
消息的 编码/解码
- go-micro源码之Codec - 消息编解码 - http://mckee.cn/golang/go-micro/codec
Transport
- go-micro源码值Transport - http://www.mckee.cn/golang/go-micro/transport
内置支持的几种传输方式在
github.com/micro/go-micro/transport
目录下
默认使用的是 http 作为传输层, 直接 run 一个空服务就可以看出来
1 | f:\go\GoWinEnv_Test01\src\GoMicro\test001\srv (master -> origin) |
使用 grpc
- 参考: test_transport_grpc
要求 client - server 必须同时 grpc 方式传输, 否则调用不到
proto 文件生成 go 文件必须用 grpc 插件生成. 参考: go-入坑笔记.md 中的 grpc
1 | $ protoc --go_out=plugins=grpc:. your_pb.proto |
使用非常简单
1 | import ( |
启动是就可以看到 transport 使用的 grpc
1
2
3
4
5$ go run service.go
2019/10/08 19:53:58 Transport [grpc] Listening on [::]:58927 // grpc
2019/10/08 19:53:58 Broker [http] Connected to [::]:58928
2019/10/08 19:53:58 Registry [mdns] Registering node: user-0c12feb1-56b4-4496-a743-f22cc4ebd8ac
--- req, name:World ^_^
Broker
Broker 是消息发布和订阅的接口。因为服务的节点是不固定的,如果有需要修改所有服务行为的需求,可以使服务订阅某个主题,当有信息发布时,所有的监听服务都会收到信息,根据你的需要做相应的行为。
Broker 默认的实现方式是http方式,但是这种方式不要在生产环境用。go-plugins里有很多成熟的消息队列实现方式,有 kafka、nsq、rabbitmq、redis,等等
参考: pubsub
其他参考
- micro工具之消息接收和发布 - https://aijishu.com/a/1060000000007366
使用 grpc
proto 文件生成 go 文件必须用 grpc 插件生成. 参考: go-入坑笔记.md 中的 grpc
1 | $ protoc --go_out=plugins=grpc:. your_pb.proto |
参考: test_broker_grpc
1 | import ( |
cmd.Init()
用来读取 启动参数 和 环境变量
1 | import { |
TLS
- A step by step guide to mTLS in Go - https://venilnoronha.io/a-step-by-step-guide-to-mtls-in-go
- 带入gRPC:基于 CA 的 TLS 证书认证 - https://studygolang.com/articles/15331
- golang游戏项目中使用 tls、https 与etcd v3服务通讯 - https://www.cnxct.com/use-tls-https-to-transport-with-etcd-server-in-golang-gameserver/
设置TLS证书
1
2
3micro --enable_tls=true --tls_cert_file=/path/to/cert --tls_key_file=/path/to/key api
// 或者环境变量中设置证书参数
MICRO_ENABLE_TLS=true MICRO_TLS_CERT_FILE=/path/to/cert MICRO_TLS_KEY_FILE=/path/to/key micro api
方式一: 使用内置
在 github.com/micro/go-micro/transport/options.go
中有说明, 如果没有指定 TLSConfig, 可以通过这个接口来使用 tls.
1 | // Use secure communication. If TLSConfig is not specified we |
参考: 官方 example: github.com\micro\examples\secure
演示了 简单使用 tls 安全验证的调用
启动 server
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\secure\srv (master -> origin)
$ go run main.go启动 client
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\secure\cli (master -> origin)
$ go run main.go
方式二: 自定义 tls 证书
参考: go-micro-安全tls.md 对应的代码: test_secure_tls
重新编译带 tls 的 micro
一旦微服务加入 tls, 相应的 micro 也需要加入 tls, 才能正常的使用.
加入 tls 的方式和 registry 加入 etcdv3 类似.
参考: https://github.com/micro/examples/tree/master/secure
Cli-Srv 数据交互
参考: 官方 example: github.com\micro\examples\server\codegen
或者 github.com/micro/examples/stream
演示了 请求->响应, 服务端推送, 客户端请求->服务端推送 三种 rpc 调用方式
codegen
形参直接可以 Recv
1 | func (e *Example) PingPong(ctx context.Context, stream example.Example_PingPongStream) error { |
启动 server
1
go run src\github.com\micro\examples\server\codegen\codegen.go
启动 client
1
go run src/github.com/micro/examples/client/codegen/codegen.go
handler
Recv 需要解码. 易用性不如 codegen,
Send 一样
1 | func (e *Example) PingPong(ctx context.Context, stream server.Stream) error { |
pubsub
参考: 官方 example: github.com\micro\examples\pubsub
演示了 服务间的 异步 通信, 使用的是 broker 而不是 transport.
broker 默认使用的是 http, 可以替换为 grpc
启动 server
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\pubsub\srv (master -> origin)
$ go run main.go启动 client
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\pubsub\cli (master -> origin)
$ go run main.go
metadata
参考: 官方 example: github.com\micro\examples\metadata
演示了 自定义的元数据 ( metadata )发送到一个服务
启动 server
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\metadata\srv (master -> origin)
$ go run main.go启动 client
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\metadata\cli (master -> origin)
$ go run main.go
Bot
micro bot 是一只藏在微服务中的小马蝇,有了它,我们可以在 Slack、HipChat、XMPP 等等聊天程序中与它对话,通过它来操控服务。
调用服务
启动官方 examples 的一个服务
src/github.com/micro/examples/greeter/srv/main.go
1
2f:\go\GoWinEnv_MicroExamples\src\github.com\micro\examples\greeter\srv (master -> origin)
$ go run main.go
命令行 micro 工具
使用 call 命令. (注意需要转义
"
)1
2
3
4$ micro call go.micro.srv.greeter Say.Hello {\"name\": \"wolegequ\"}
{
"msg": "Hello wolegequ"
}
web 界面
client 中调用服务
启动 web 界面.
1
2$ micro web
2019/10/11 18:45:00 HTTP API Listening on [::]:8082访问: http://localhost:8082/client , 这里的 request 不需要转义
"
, 如:{"name": "wolegequ"}
slack 界面 ( bot )
其实也就是 micro 工具包的 call 命令
启动 bot
1
2
3$ micro bot --inputs=slack --slack_token=xoxb-123-123-asdasd
2019/10/10 19:25:06 [bot] starting
2019/10/10 19:25:06 [bot] starting input slack在 Slack 就可以输入 call 命令. 不需要转义
"
, 如:{"name": "wolegequ"}
无 proto 的调用
参考: 官方 example src/github.com/micro/examples/noproto/main.go
适用于 cmd 指令.
RESTful API
- Restful API 风格 - https://www.jianshu.com/p/089f82811c9b
- RESTful API 设计指南 - http://www.ruanyifeng.com/blog/2014/05/restful_api.html
简单说就是把一些参数丢到 url 中去获取.
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
TODO:
- 消息 编码,解码
- 熔断, 限流
踩坑
build 或 run 报错:
go: xxx requires
需要在 GOPATH 目录下使用命令 build 或 run
1
2E:\ws_go\GoWinEnv (master -> origin)
$ go build -o mainWithEtcdv3.exe ./src\github.com\micro\micro/main.go ./src\github.com\micro\micro/plugins.goverifying ‘protobuf’ malformed record data
错误:
verifying github.com/gogo/protobuf@v0.0.0-20190410021324-65acae22fc9/go.mod: github.com/gogo/protobuf@v0.0.0-20190410021324-65acae22fc9/go.mod: malformed record data
可能引用的版本不对. 参考: https://github.com/golang/go/issues/34394
在 go.mod 文件中, 替换合法的版本
1
replace github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9 => github.com/gogo/protobuf v0.0.0-20190723190241-65acae22fc9d
cmder 中 curl post json 请求报错:
[globbing] unmatched close brace/bracket in column
两种办法解决
- cmder 中的请求的
"
都是用转义\"
- 换 bash.exe 命名行工具去 curl 请求. ( 推荐 )
- cmder 中的请求的
网段为 10.192.75.100 导致调用接口408或500问
原因是用了个没用的网卡, 禁用掉就行了
参考: go-micro 调用接口408或500问题排查 - https://segmentfault.com/a/1190000020126702 (困扰了一天的问题, tmd)
所以造成这个问题的原因就是go-micro默认使用的网络连接问题了,只要将有问题的禁用掉即可
那不方便禁用网络连接的情况下怎么解决呢,只需要指定–server_address即可,如下
1 | go run main.go --server_address=localhost:8999 |
经测试不禁用网络连接的情况下,指定–server_address,服务也可正常调用
micro有很多参数可以通过命令行或代码中option来设置,具体可以micro -h 查看