nano-kit.github.io

Nano-kit is a set of utility to build go-micro services.

Getting Started 快速开始

通过阅读本教程,你将学会

Install 安装开发工具

我用的开发机是 MacOS v10.11.6 (OS X El Capitan),在这个计算机上,能安装的 Go 最高版本是 v1.14。如果你用更高版本的 Go 也可以。

Go 1.14 is the last release that will run on macOS 10.11 El Capitan. Go 1.15 will require macOS 10.12 Sierra or later.

golang.org/doc/go1.14#darwin

Go 安装好之后,有关的环境变量如下

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/huang/Library/Caches/go-build"
GOENV="/Users/huang/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/huang/GoWork"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go@1.14/1.14.9/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go@1.14/1.14.9/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/72/gdh8xjz13kz41rysd10rd9tw0000gn/T/go-build864495291=/tmp/go-build -gno-record-gcc-switches -fno-common"

除了 Go 之外,还需要安装

用 Protobuf 来定义服务 API 是开发过程中的最佳实践。协议生成工具能把 Protobuf 定义转换成 Go 代码,让项目直接使用。项目生成工具能生成新项目的骨架,不纠结目录结构和构建脚本,对新手非常友好。

首先下载 go-micro 和 micro 的代码到本地目录,

git clone https://github.com/nano-kit/go-micro.git
git clone https://github.com/nano-kit/micro.git

注意,这里没有去 https://github.com/micro/micro 下载,原因是 go-micro 从 v3.0 开始进行商业化的大改变,代码演进剧烈,不利于我们自己的项目使用。本教程用的是我们自己独立维护的 v2 稳定版,基于上游的 Go Micro v2.9.1

安装 microprotoc-gen-micro

cd micro && go install
cd cmd/protoc-gen-micro && go install

安装 protoc-gen-go,必须用 v1.3.5

cd $HOME; GO111MODULE=on go get github.com/golang/protobuf/protoc-gen-go@v1.3.5

命令执行成功后,它们将被安装在 $GOPATH/bin/ 下。

注意,安装 protoc-gen-go 的命令,必须先 cd 到 $HOME 下,否则 go get 会安装到当前所在项目的依赖里去。

to avoid accidentally modifying go.mod, people started suggesting more complex commands

blog.golang.org/go116-module-changes

我们使用的是 protoc-gen-go@v1.3.5 ,新版的 protoc-gen-go 生成的 Go 代码要求使用最新的 google.golang.org/protobuf 而不是 github.com/golang/protobuf ,与 go-micro/v2 不能一起使用。

查看刚才安装好的开发工具

$ ls -lt $GOPATH/bin/
total 150264
-rwxr-xr-x  1 huang  staff  61932564 Feb 26 09:30 micro
-rwxr-xr-x  1 huang  staff   6277416 Feb 26 09:27 protoc-gen-go
-rwxr-xr-x  1 huang  staff   7789496 Feb 26 09:28 protoc-gen-micro

注意,$PATH 必须包括 $GOPATH/bin,也就是说下面这些命令都能在 $PATH 中找到

$ which go protoc protoc-gen-go protoc-gen-micro micro
/usr/local/opt/go@1.14/bin/go
/usr/local/bin/protoc
/Users/huang/GoWork/bin/protoc-gen-go
/Users/huang/GoWork/bin/protoc-gen-micro
/Users/huang/GoWork/bin/micro

Generate 生成骨架项目

新项目启动,可以用 micro new 生成骨架。需要指定的参数,有

$ micro new --namespace com.example --alias realworld realworld-example-app
Creating service com.example.service.realworld in realworld-example-app

.
├── main.go
├── generate.go
├── plugin.go
├── handler
│   └── realworld.go
├── subscriber
│   └── realworld.go
├── proto
│   ├── realworld
│   │   └── realworld.proto
│   └── imports
│       └── api.proto
├── Dockerfile
├── Makefile
├── README.md
├── .gitignore
└── go.mod


compile the proto file realworld.proto:

	cd realworld-example-app
	make proto

build the project:

	make build

进入项目的目录,用 make build 构建,并运行 ./realworld-service,微服务启动成功了。

上面的步骤,已经自动生成了这个微服务的接口协议,是用 Protobuf 语言来描述的,

syntax = "proto3";

package com.example.service.realworld;

service Realworld {
	rpc Call(Request) returns (Response) {}
    // 省略了流式调用的接口,与本教程无关
    // ...
}

message Request {
	string name = 1;
}

message Response {
	string msg = 1;
}

// Message 用于异步消息
message Message {
	string say = 1;
}

改动这个接口协议文件之后,可以用 make proto 重新生成 Go 代码。

基于这个新项目,后续迭代开发的步骤一般是,

  1. 改进 Protobuf 接口协议,make proto 重新生成接口协议的 Go 代码
  2. 改进项目里的业务逻辑 Go 代码,make build 构建,生成微服务的可执行文件
  3. 启动可执行文件,测试
  4. 发布可执行文件,上线
  5. 回到 1

Play 把玩第一个微服务

保持 ./realworld-service 在一个终端窗口中运行,我们打开另一个终端窗口来观察这个服务的特性。

List 查看服务注册表

$ micro list services
com.example.service.realworld
micro.http.broker

注册表里,有我们的服务 com.example.service.realworld。这是服务的 FQDN (Fully qualified domain name),即网络上的唯一名称。它由 namespace.type.alias 组合而成。

注意,micro.http.broker 是由 go-micro/broker 悄悄注册的,用于消息投递。在异步消息这一章节里有详细解释。

Get 查看服务详情

服务的详细信息有

$ micro get service com.example.service.realworld
service  com.example.service.realworld

version latest

ID	Address	Metadata
com.example.service.realworld-f653af63-1467-4379-a990-3dd51cad0e30	192.168.0.102:56203	protocol=grpc,registry=mdns,server=grpc,transport=grpc,broker=http

Endpoint: Realworld.Call

Request: {
	name string
}

Response: {
	msg string
}


Endpoint: Realworld.PingPong

Metadata: stream=true

Request: {}

Response: {}


Endpoint: Realworld.Stream

Metadata: stream=true

Request: {}

Response: {}


Endpoint: Realworld.Handle

Metadata: subscriber=true,topic=com.example.service.realworld

Request: {
	say string
}

Response: {}

Stats 查看服务状态

服务的运行状态有

$ micro stats com.example.service.realworld
service  com.example.service.realworld

version latest

node		address:port		started	uptime	requests	memory	threads	gc	errors
com.example.service.realworld-f653af63-1467-4379-a990-3dd51cad0e30		192.168.0.102:56203		Feb 27 09:47:18	44m37s	11	2.22mb	25	2.304788ms	0

Call 调用服务的接口

micro call 的参数依次填写

$ micro call com.example.service.realworld Realworld.Call '{"name":"Jack"}'
{
	"msg": "Hello Jack"
}

观察服务的终端窗口输出,会有处理这个请求的日志

2021-02-28 11:13:28  file=handler/realworld.go:17 level=info Received Realworld.Call request

Publish 给服务发异步消息

micro publish 的参数依次填写

$ micro publish com.example.service.realworld '{"say": "Hello!"}'
ok

观察服务的终端窗口输出,会有处理这个消息的日志

2021-02-28 11:15:08  file=subscriber/realworld.go:13 level=info Handler Received message: Hello!

Export API 用 micro api 网关导出服务 API

通常,客户端需要服务提供 REST API。按照微服务的最佳实践,客户端只用统一连接到服务网关(API Gateway),由服务网关自动发现后端微服务,转发请求。

基于 go-micro 开发的微服务,与 micro api 网关配合一起使用,是最方便的。启动网关的命令是,

micro --auth_namespace com.example api --namespace com.example --type service

启动的参数,指定了这个网关后端的服务都在 com.example.service 命名空间下。网关自己默认监听在 0.0.0.0:8080 这个地址。

用 curl 测试网关,确认这个请求被成功转发到后端的微服务 ./realworld-service 了。

$ curl -XPOST http://127.0.0.1:8080/realworld/Realworld/Call -d '{"name":"Jack"}'
{"msg":"Hello Jack"}

请求路径与后端服务的对应规则是,

Path service method
/realworld/Realworld/Call realworld Realworld.Call

如果后端服务成功处理了请求,HTTP 的状态码总是 200 OK。如果后端服务在处理过程中出错,错误处理的最佳实践是用 “github.com/micro/go-micro/v2/errors” 这个包里定义的函数生成自定义错误,例如:


    // Inside Realworld.Call handler

    // ...

    if req.Age == 0 {
		return errors.BadRequest("zero-age", "age is %v, forget to set the age?", req.Age)
	}

    if req.Age > 200 {
		return errors.New("age-too-old", fmt.Sprintf("age is %v, too old!", req.Age), 501)
	}

    // ...

向网关发请求,验证这些出错的场景,确认从网关成功接收到错误响应。错误响应里既有 HTTP 的(非 200 OK)状态码,又有错误详情。

$ curl -v -XPOST http://127.0.0.1:8080/realworld/Realworld/Call -d '{"name":"Jacket", "age":201}'
< HTTP/1.1 501 Not Implemented
{"id":"age-too-old","code":501,"detail":"age is 201, too old!","status":"Not Implemented"}

$ curl -v -XPOST http://127.0.0.1:8080/realworld/Realworld/Call -d '{"name":"Jacket"}'
< HTTP/1.1 400 Bad Request
{"id":"zero-age","code":400,"detail":"age is 0, forget to set the age?","status":"Bad Request"}

也就是说,客户端的处理逻辑是,

HTTP status code 处理逻辑
200 OK 按 Response 协议规格处理请求的响应
其它 按 Error 协议规格处理出错

Error 协议规格

服务端需要注意,code 只能填写已知的 HTTP status codes

这种约定的好处是,

Summary 总结

这是 go-micro 的快速开始教程。利用 go-micro 微服务框架,你不需要写一行代码,就能获得一套遵循最佳实践成熟生产可用的微服务系统。用到的命令为,