部署开发环境

Micro目前V3版本未稳定,使用V2版本进行学习和开发使用。

Micro已经出了V3版本,不过最近asim发言称,V3是最后一个版本了,Micro也不属于Micro团队维护项目了,它将回归于个人项目。

以前的go-micro版本:

https://github.com/microhq/go-micro

https://github.com/microhq/go-plugins

https://github.com/microhq/protoc-gen-micro

现在的消息,go-micro已经回到asim个人仓库,但其已经把开源项目的issue都关闭了(2021/5/27),只接受PR,且不会有maintainer的支持。因为他觉得Github上的人只是去抱怨,而不去解决问题。然后他就把解决问题的人给解决了,这波操作在大气层。说实话,我觉得他真的太能折腾了,这个框架实在不建议使用了,maintainer想法太多了,有点伤。

有替代品吗?有的,而且就是国内的,工程性都做的比较好,推荐两个,go-kratosgo-zerogo-zero听说是要进入CNCF基金会了。go-kratos在腾讯的一些业务上有用到。

1
2
3
4
5
6
7
8
9
10
## 安装go-micro
go get github.com/micro/go-micro/v2

## 安装micro
go get github.com/micro/micro/v2

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

常见开发组件如何一键安装,使用docker进行环境部署,见组件环境

查看Micro命令的Help,命令参数后跟上--help,例如:

1
micro new --help

Micro Handler介绍

在micro的系统中,有许多资源类型,作为框架对服务的一种抽象归类,比如常用的有:api、fnc(函数)、srv、web。其中经常需要使用的是api、srv、web,关于web,api,srv这三种服务的疑问,可以查看该Issue。

例如,在实际使用中,我的理解是,简化分类,使用web,srv作为整体架构,web处理HTTP请求,srv提供特定的服务,例如,登录,验证,访问数据等操作。

web框架代码生成:

1
micro new --namespace=mu.micro.book --type=web --alias=order micro_learn/orders-web

service框架代码生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ micro new --namespace=mu.micro.book --type=service --alias=u12 user-test
Creating service mu.micro.book.service.u12 in user-test

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

删除subscriber目录,这是用于专门放订阅异步消息组件的目录,我们暂时用不到。

删除go mod文件,仅需要在项目最外层有统一的go mod。

删除Dockerfile, Makefile打包编程文件与README.md,可以选择性保留。


Web和Srv的区别

Micro Grpc代码生成:

1
protoc --proto_path=. --go_out=. --micro_out=. u12.proto

这里有个小细节,生成代码时,如果想生成micro目录下的user服务,那就在micro的同级目录下,运行命令。

micro new --namespace=mu.micro.book --type=web --alias=order micro/user

这样生成,proto文件路径才不会出现路径问题。

Micro代理启动

启动api网关

micro api 即可启动api一个网关,默认的端口是8080
可以通过--address=0.0.0.0:8080flag或者设置环境MICRO_API_ADDRESS=0.0.0.0:8080来修改

设置命名空间

micro --api_namespace=namespace apiMICRO_API_NAMESPACE=namespace micro api
注意启动api时设置的namespace必须与要访问的资源的namespace一致不然无法访问,Web管理控制台类似

设置服务发现

Micro默认是使用Grpc Mdns的方式进行局域网的服务发现,使用--registry参数修改注册服务,consul、etcd等,micro --registry=etcd list services--registry_address修改默认ETCD注册地址。

1
micro --registry=etcd --api_namespace=mu.micro.book.web api --handler=web

RPC使用网关访问

--enable_rpc参数是micro api的参数,默认为false,这个得注意,注意,再注意,因为看到网上很多教程没有提到要开启,显然是Micro更新过快的锅。。。。

1
micro_v2.exe --registry=etcd api --enable_rpc

接下来就是访问了。

1
2
3
4
5
6
7
8
9
10
curl --location --request POST 'http://localhost:8080/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"service": "micro.xxx.service.process_route",
"method": "ProcessRoute.StoreProcessInfo",
"request": {
"xxxx": "xxx",
"xxx": "xxx",
}
}'

servicemethod如何去确定,若没有经验的情况下,可以使用我的办法:

1
2
3
micro --registry=etcd api --enable_rpc

./etcdctl get "" --prefix

随便找一个去看一下就好了。

PROTO文件兼容其他tag

使用工具:https://github.com/favadi/protoc-go-inject-tag

1
go get github.com/favadi/protoc-go-inject-tag

Example:

1
2
3
4
5
6
7
8
9
// file: test.proto
syntax = "proto3";

package pb;

message IP {
// @inject_tag: valid:"ip"
string Address = 1;
}

Generate with protoc command as normal.

1
protoc --go_out=. test.proto

Run protoc-go-inject-tag with generated file test.pb.go.

1
protoc-go-inject-tag -input=./test.pb.go

The custom tags will be injected to test.pb.go.

1
2
3
4
type IP struct {
// @inject_tag: valid:"ip"
Address string `protobuf:"bytes,1,opt,name=Address,json=address" json:"Address,omitempty" valid:"ip"`
}

使用Apollo配置中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import (
apollo "github.com/xxxmicro/go-micro-apollo-plugin"
)

e := json.NewEncoder()
if err := config.Load(apollo.NewSource(
apollo.WithAddress("172.16.9.229"+":8080"),
apollo.WithNamespace("application"),
apollo.WithAppId("12345"),
apollo.WithCluster("dev"),
source.WithEncoder(e),
)); err != nil {
log.Error(err)
}

if err := config.Get("etcd").Scan(&etcdConfig); err != nil {
log.Error(err)
}
if err := config.Get("mysql").Scan(&mysqlConfig); err != nil {
log.Error(err)
}
if err := config.Get("redis").Scan(&redisConfig); err != nil {
log.Error(err)
}
if err := config.Get("zap").Scan(&zapConfig); err != nil {
log.Error(err)
}
if err := config.Get("jwt").Scan(&jwtConfig); err != nil {
log.Error(err)
}

OpenTrace设置

Trace ID 在入口函数设置后,后续均一样。Span ID则标识每一个服务实例。

其他服务,可通过此操作,从context中拿到trace,拿到后,可以使用Log,将一些信息打印到trace上。

Web端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
	// 分布式追踪链路
t, io, err := tracer.NewTracer("mu.micro.book.web.api.user", "")
if err != nil {
log.Fatal(err.Error())
}
defer io.Close()
opentracing.SetGlobalTracer(t)

.......

//设置采样率
gin2micro.SetSamplingFrequency(50)
router := gin.Default()
r := router.Group("/user")

//添加Tracer中间件
r.Use(gin2micro.TracerWrapper)

func Login(c *gin.Context) {
ctx, ok := gin2micro.ContextWithSpan(c)
if ok == false {
log.Error("get context err")
}

sp := opentracing.SpanFromContext(ctx)
// Get request ID for context
if sc, ok := sp.Context().(jaeger.SpanContext); ok {
fmt.Println(sc.TraceID().String())
}

......

// 调用后台服务
rsp, err := userClient.QueryUserByName(ctx, &us.Request{
UserName: c.Request.Form.Get("userName"),
})
}

Service端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (
"github.com/opentracing/opentracing-go"
tarceLog "github.com/opentracing/opentracing-go/log"
)

func (e *Service) QueryUserByName(ctx context.Context, req *s.Request, rsp *s.Response) error {

sp := opentracing.SpanFromContext(ctx)
if sc, ok := sp.Context().(jaeger.SpanContext); ok {
fmt.Println(sc.TraceID().String(), sc)
}

sp.LogFields(
tarceLog.String("event", "soft error"),
tarceLog.String("type", "cache timeout"),
tarceLog.Int("waited.millis", 1500))

.......
}

Web设置Prometheus监控

1
2
3
4
5
6
7
8
promMonitor := monitor.NewPrometheusMonitor("user_web", "user")
r.Use(promMonitor.PromMiddleware())

r.POST("/login", handler.Login)

router.GET("/metrics", gin.WrapH(promhttp.Handler()))

....

获取其他服务的信息

1
2
3
4
5
6
micReg registry.Registry

services, err := micReg.GetService(appName)
log.Info(services[0].Version)

// level=info latest

etcd.go中:

1
2
3
4
5
6
7
GetService(xxxxxxxxxxxxx)
.....
rsp, err := e.client.Get(ctx, servicePath(name)+"/", clientv3.WithPrefix(), clientv3.WithSerializable())
if err != nil {
return nil, err
}
........

WithPrefix,将会把传入的name作为前缀的key全部取下来,所以GetService才会返回的是一个数组。

另外,ListServices则是把服务信息全部返回。

Micro工具获取服务信息:

1
micro.exe --registry=etcd --registry_address="192.xx.xx.xxx:2379" get service "xxxxxxxxxxxx"

教程中的问题:

一、版本更迭,web handler无法使用

造成问题,micro api,POST /user/login HTTP/1.1" 500 0。问题是使用了最新的稳定版本micro。

micro 2.9.3没有web handler,开发组尚未解决。issue 不是没有了,看代码,其实是cmd中没有添加而已。

现在的解决方法就是,虽然使用--type=web创建项目:

1
micro new --namespace=mu.micro.book --type=web --alias=user micro/user-web

代码修改:

1
2
3
4
5
默认
web.Name("mu.micro.book.web.user")

改为
web.Name("mu.micro.book.web.api.user")

注意,这里这个api是必须的,查看代码会发现api模式代理rpc、http、proxy、web等,默认创建的只有web,显然是V1版本时代的产物,这里可以修改生成代码器将其改掉。

micro启动命令:

1
micro --registry=etcd --api_namespace=mu.micro.book.web api --handler=web

解决方法参考☞issue

Web HTTP代理

micro api --handler=web

web handler是一个基于服务发现和web socket支持的http反向代理。

  • Content-Type: Any
  • Body: Any
  • 正向格式:HTTP反向代理,包括web sockets。
  • Path: /[service]
  • 解析器:路径用于解析服务名称。
  • 配置:Flag —handler=webMICRO_API_HANDLER=web

上面这段需要怎么理解呢?

例如Service Name为micro.cloud.api.test

启动micro进行代理:micro_v2.exe --api_namespace=micro.cloud api --handler=web.

此时,凡是/test前缀的HTTP请求,均从此服务请求,同理,此服务其他不是/test前缀,通过Micro则访问不到。

此时这个service,不能是下划线形式,例如,test_one。

二、Trace部分HTTP2Micro无法使用

按照原生HTTP方式注入Trace,没有能够完整使用,使用Gin或者Echo第三方Web库,利用其插件形式进行使用,则可以正常使用Trace服务。

三、设置Prometheus监控

需上文提到的方式,设置Gin框架的监控,按照网上操作,无法监视到接口访问。

组件环境

ETCD:

1
docker run -p 2379:2379 -p 2380:2380 --name etcd gcr.io/etcd-development/etcd:v3.4.13

MySQL:

1
2
3
docker pull mysql

docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql

Redis:

1
2
3
docker pull redis:latest

docker run -itd --name redis -p 6379:6379 redis

Jaeger:

1
docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:1.6

Promethes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
prometheus.yml
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.

# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'codelab-monitor'

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# Override the global default and scrape targets from this job every 5 seconds.
scrape_interval: 5s
static_configs:
- targets: ['10.2.43.4:8088']

targets : 作为拉取数据的地址。


docker run -d -p 9090:9090 -v C:/tmp/prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus prom/prometheus

Grafana

1
docker run -d -p 3000:3000 --name grafana grafana/grafana

Web和Srv的区别

web.NewService 和 micro.NewService有啥区别

功能上:web打开的Http服务,micro打开的RPC/API服务
联系:为了让web服务能像RPC/API一样融合到Micro的微服务体系中,web.Micro做了以下事情:

  1. 与RPC一样注册服务
  2. 可以复用Service的配置,声明micro.client调用RPC
  3. client为http.client,非micro.client,故而无法直接使用web.client调用micro.service
  4. web的Transport并非micro.Transport,所以micro的RPC服务无法直接调用web.service,需要使用http.client调用。

一句话总结:web面向http,可以向异构服务提供服务,rpc则是纯内部服务。

学习资料

Micro 中国站教程系列

microservices

sass-web-server

牌类游戏使用微服务重构笔记(二): micro框架简介:micro toolkit