go-zero 实战(5)

引入Prometheus

用 Prometheus 监控应用

1. 用 docker 启动 Prometheus

编辑配置位置,我将 prometheus.yaml 和 targets.json 文件放在了 /opt/prometheus/conf目录下

prometheus.yaml

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

yaml 复制代码
[
  {
    "targets":["192.168.10.20:9081"],
    "labels": {
      "job": "user-api",
      "app": "user-api",
      "env": "test",
      "instance": "192.168.10.20:8888"
    }    
  }
]

我的应用是启动在 宿主机的 8888 端口,因此,我这里写了宿主机的 ip 和端口。

上面的 9081 端口,可以随便写。但是要与应用中的配置一致,看后面配置。

yaml 复制代码
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

bash 复制代码
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或其它存储中。

2. 修改 userapi/etc/userapi-api.yaml 文件

在配置文件中加入如下配置:

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 配置文件,加入如下配置:

yaml 复制代码
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

bash 复制代码
go run main.go -c conf.yml 

2. 创建表

在当前使用的微服务对应的数据中创建表。我们曾在 go-zero实战(2)中创建过zero-mall数据库。同样在该数据库创建 barrier表。

sql 复制代码
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 表

sql 复制代码
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 文件

bash 复制代码
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. 生成代码

bash 复制代码
goctl rpc protoc user_score.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.

4. 整理代码

  1. 将 生成的 user_score.pb.go 文件和 user_score_grpc.pb.go 文件放入 rpc-common 工程的score目录下。

  2. 将 userscore.go 文件放入 rpc-common 工程的 userscore 目录下。

  3. 在user-score 创建 go.mod 文件,如下:

    go 复制代码
    module user-score
    
    go 1.22.2
  4. 在 mall 工程下,执行如下命令:

    bash 复制代码
    go work use user-score  # 加入 workspace
    go mod tidy             # 下载依赖
  5. 在 user-score 创建 database 目录,并增加 sqlx.go 文件,参考 go-zero 实战(3)

  6. 将user 微服务 user/internal 目录下的 dao、repo、model三个目录复制一份到 user-score 微服务的 user-score/internal 目录下并做相应的命名修改。

  7. 修改 user-score/etc/userscore.yaml 文件

    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
  8. 修改 user-score/internal/svc/servicecontext.go 文件,加入 UserScoreRepo

  9. 修改生成的 savescorelogic.go 和 savescorecallbacklogic.go 文件

到此,积分服务创建完成。

4. 在 userapi 中调用积分服务

1. 修改 userapi/etc/userapi-api.yaml 文件

加入 user-score 微服务的 rpc 配置

yaml 复制代码
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 下执行命令:

bash 复制代码
go get github.com/dtm-labs/dtm

2. 项目中加入dtm 驱动

  1. 修改 userapi/internal/logic/userapilogic.go 文件

    加入驱动

    go 复制代码
    	_ "github.com/dtm-labs/dtmdriver-gozero"
    	// 这里的地址在文章 分布式事务 1.2 这个步骤,修改配置文件时候,指定的地址
    	// 先上看 1.2 步骤可以找到
    	var dtmServer = "etcd://localhost:2379/dtmservice" 

3. 修改 userapi/internal/userapilogic.go 文件

修改 Register 方法的逻辑,引入 dtm

go 复制代码
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. 修改服务端

  1. 修改 user-score/internal/logic/savescorelogic.go 文件
    引入 dtm
  2. 修改 user/internal/logic/userlogic.go 文件

5. 测试

指定id,插入用于,测试成功。

6. 测试事务

现在模拟 user-score 服务的逻辑出现了问题。检查事务是否生效。

我们用 postman测试后,发现, user 表中插入了新的数据,但是积分表中是没有新数据的。那是否事务没有生效呢?

当从后台打印的日志可以看出,saveCallback 方法被调用。

这里需要明白一点。

userapi/internal/logic/userapilogic.go 代码逻辑如下:

代码提交到了 dtm 分支。代码

相关推荐
Ajiang282473530422 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空27 分钟前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic6 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it6 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康6 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
qq_17448285756 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
转世成为计算机大神7 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式