引入Prometheus
用 Prometheus 监控应用
1. 用 docker 启动 Prometheus
编辑配置位置,我将 prometheus.yaml 和 targets.json 文件放在了 /opt/prometheus/conf目录下
prometheus.yaml
global:scrape_interval: 15s # 抓取间隔evaluation_interval: 15s # 评估间隔scrape_configs:- job_name: 'file_ds'file_sd_configs:- files:- 'targets.json'
由于 prometheus.yaml 文件中,用到了 targets.json 文件,因此,引入 targets.json文件
targets.json
[{"targets":["192.168.10.20:9081"],"labels": {"job": "user-api","app": "user-api","env": "test","instance": "192.168.10.20:8888"} }
]
我的应用是启动在 宿主机的 8888 端口,因此,我这里写了宿主机的 ip 和端口。
上面的 9081 端口,可以随便写。但是要与应用中的配置一致,看后面配置。
docker run -d --name prometheus --dns=192.168.10.20 -p 9090:9090 -v /opt/prometheus/conf/prometheus.yaml:/etc/prometheus/prometheus.yml -v /opt/prometheus/conf/targets.json:/etc/prometheus/targets.json quay.io/prometheus/prometheusdocker run -d --name prometheus --network host -v /opt/prometheus/conf/prometheus.yaml:/etc/prometheus/prometheus.yml -v /opt/prometheus/conf/targets.json:/etc/prometheus/targets.json quay.io/prometheus/prometheus
下面两条命令都可以启动 Prometheus。
2. 启动应用
修改应用的配置文件 userapi/etc/userapi-api.yaml 文件。
增加了 Prometheus 配置,端口号与 targets.json 文件中的 targets 条目标识的端口号保持一致。
3.测试
访问 http://192.168.10.20:9090/targets?search=
这样可以看到 Prometheus 监控了应用。
引入 jaeger
jaeger 是一个用于链路追踪的中间件。
1. docker 启动 jaeger
docker run -d -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 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest
这样启动的 jaeger,数据默认是放在内存中的。可以根据直接的需求,选择将数据放在 elasticsearch或其它存储中。
- 其中 16686 是 ui 端口,直接访问 http://localhost:16686 便可以进入到 ui 界面
2. 修改 userapi/etc/userapi-api.yaml 文件
在配置文件中加入如下配置:
Telemetry:Name: user-apiEndpoint: http://localhost:14268/api/tracesSampler: 1.0Batcher: jaeger
3. 测试
启动 userapi 应用,用postman 访问接口。
这样就可以在 jaeger 的 ui 上看到访问的接口。
分布式事务
分布式事务也是微服务架构中必不可少的一部分。go-zero 使用了dtm的方案来解决分布式事务问题。
1. 引入 DTM
官网链接
1. github clone 项目
项目地址:https://github.com/dtm-labs/dtm.git
2. 进入项目
创建 conf.yml 配置文件,加入如下配置:
MicroService: # gRPC/HTTP based microservice configDriver: 'dtm-driver-gozero' # name of the driver to handle register/discoverTarget: 'etcd://localhost:2379/dtmservice' # register dtm server to this urlEndPoint: 'localhost:36790'
3. 启动dtm
从源码处启动 dtm
go run main.go -c conf.yml
2. 创建表
在当前使用的微服务对应的数据中创建表。我们曾在 go-zero实战(2)中创建过zero-mall数据库。同样在该数据库创建 barrier表。
create table if not exists barrier(id bigint(22) primary key auto_increment,trans_type varchar(45),gid varchar(128),branch_id varchar(128),op varchar(45),barrier_id varchar(45),reason varchar(45),create_time datetime default now(),update_time datetime default now(),key(create_time),key(update_time),unique key(gid, branch_id, op, barrier_id)
);
3. 创建积分服务
1. 在 zero_mall 数据库中 创建 user_score 表
create table user_score(id bigint(0) not null auto_increment,user_id bigint(0) not null,score int(0) not null,primary key(id) using btree
);
2. 创建 user_score.proto 文件
syntax = "proto3";package userscore;option go_package = "./score";message UserScoreRequest {int64 userId = 1;int32 score = 2;
}message UserScoreResponse {int64 userId = 1;int32 score = 2;
}service UserScore {rpc saveScore(UserScoreRequest) returns(UserScoreResponse);rpc saveScoreCallback(UserScoreRequest) returns(UserScoreResponse);
}
为了使用 dmt 实现的分布式事务,saveScore 方法,需要有一个相应的 Callback 方法。为了在发生异常回滚时,执行该方法。
3. 生成代码
goctl rpc protoc user_score.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
4. 整理代码
- 将 生成的 user_score.pb.go 文件和 user_score_grpc.pb.go 文件放入 rpc-common 工程的score目录下。
- 将 userscore.go 文件放入 rpc-common 工程的 userscore 目录下。
- 在user-score 创建 go.mod 文件,如下:
module user-scorego 1.22.2
- 在 mall 工程下,执行如下命令:
go work use user-score # 加入 workspace go mod tidy # 下载依赖
- 在 user-score 创建 database 目录,并增加 sqlx.go 文件,参考 go-zero 实战(3)
- 将user 微服务 user/internal 目录下的 dao、repo、model三个目录复制一份到 user-score 微服务的 user-score/internal 目录下并做相应的命名修改。
- 修改 user-score/etc/userscore.yaml 文件
Name: score.rpc ListenOn: 127.0.0.1:8081 Etcd:Hosts:- 127.0.0.1:2379Key: score.rpc Mysql:Datasource: root:thinker@tcp(127.0.0.1:3306)/zero_mall?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghaiCacheRedis:- Host: 127.0.0.1:6379Pass: thinkerType: node
- 修改 user-score/internal/svc/servicecontext.go 文件,加入 UserScoreRepo
- 修改生成的 savescorelogic.go 和 savescorecallbacklogic.go 文件
到此,积分服务创建完成。
4. 在 userapi 中调用积分服务
1. 修改 userapi/etc/userapi-api.yaml 文件
加入 user-score 微服务的 rpc 配置
UserScoreRpc:Etcd:Hosts:- 127.0.0.1:2379Key: score.rpc
2. 修改 userapi/intrenal/config/config.go 文件
在这里加入 user-score 微服务的 rpc。
3. 在 userapi/internal/svc/servicecontext.go 文件为上一步增加的变量赋值
因为userapi 是作为 rpc 客户端,而 user-score 微服务是 rpc 服务端。并且这两个服务都会用到公共的部分,于是,将公共部分抽取到 rpc-common 下。这样,userapi 微服务不会用到 user-score 微服务下的代码。
4. 修改 userapi/internal/logic/userapilogic.go 文件
我们修改了 Register 接口,增加调用 user-score 微服务的代码。
到此,算是正常走通了。在 userapi 微服务下,注册功能实现时,同时调用了 user服务和 user-score 服务。
5. 测试
代码,这部分代码提交到了 score 分支。
6. 使用 DTM
在上面的注册功能里,从userapi 远程调用了 user 和 user-score 两个服务。试想,如果有一个微服务调用错误,显然不会影响到另一个。我们引入分布式事务,就为了解决在注册成功后,用户能够增加积分。如果积分增加失败的情况下,也要保证注册不成功。
1. 在项目中导入 dtm
分别在 /mall/userapi、/mall/user、/mall/user-score 下执行命令:
go get github.com/dtm-labs/dtm
2. 项目中加入dtm 驱动
-
修改 userapi/internal/logic/userapilogic.go 文件
加入驱动_ "github.com/dtm-labs/dtmdriver-gozero"// 这里的地址在文章 分布式事务 1.2 这个步骤,修改配置文件时候,指定的地址// 先上看 1.2 步骤可以找到var dtmServer = "etcd://localhost:2379/dtmservice"
3. 修改 userapi/internal/userapilogic.go 文件
修改 Register 方法的逻辑,引入 dtm
func (l *UserLogic) Register(req *types.Request) (resp *types.Response, err error) {gid := dtmgrpc.MustGenGid(dtmServer)sagaGrpc := dtmgrpc.NewSagaGrpc(dtmServer, gid)userServer, err := l.svcCtx.Config.UserRpc.BuildTarget()if err != nil {return nil, err}userScoreServer, err := l.svcCtx.Config.UserScoreRpc.BuildTarget()if err != nil {return nil, err}userReq := &user.UserRequest{Id: req.Id,Name: req.Name,Gender: req.Gender,}// call save methodsagaGrpc.Add(userServer+"/user.User/save", userServer+"/user.User/saveCallback", userReq)// 这个地方,应该是传入一个User,因为远程调用拿不到返回值。暂且先写死,为了测试效果。userScoreReq := &score.UserScoreRequest{UserId: req.Id,Score: 10,}sagaGrpc.Add(userScoreServer+"/userscore.UserScore/saveScore", userScoreServer+"/userscore.UserScore/saveScoreCallback", userScoreReq)sagaGrpc.WaitResult = trueerr = sagaGrpc.Submit()if err != nil {fmt.Println("---------------------------")fmt.Println(err)return nil, err}//fmt.Sprintf("register add score %d \n", userScore.Score)return &types.Response{Message: "success",Data: "",}, nil
}
核心代码,就是将原来直接的rpc 调用,委托给 dtm 调用。
4. 修改服务端
- 修改 user-score/internal/logic/savescorelogic.go 文件
引入 dtm - 修改 user/internal/logic/userlogic.go 文件
5. 测试
指定id,插入用于,测试成功。
6. 测试事务
现在模拟 user-score 服务的逻辑出现了问题。检查事务是否生效。
我们用 postman测试后,发现, user 表中插入了新的数据,但是积分表中是没有新数据的。那是否事务没有生效呢?
当从后台打印的日志可以看出,saveCallback 方法被调用。
这里需要明白一点。
userapi/internal/logic/userapilogic.go 代码逻辑如下:
代码提交到了 dtm 分支。代码