引入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/prometheus
docker 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-api
Endpoint: http://localhost:14268/api/traces
Sampler: 1.0
Batcher: 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 config
Driver: 'dtm-driver-gozero' # name of the driver to handle register/discover
Target: 'etcd://localhost:2379/dtmservice' # register dtm server to this url
EndPoint: '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-score go 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:2379 Key: score.rpc Mysql: Datasource: root:thinker@tcp(127.0.0.1:3306)/zero_mall?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai CacheRedis: - Host: 127.0.0.1:6379 Pass: thinker Type: 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:2379
Key: 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 method
sagaGrpc.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 = true
err = 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 分支。代码