本文是对 使用go-zero快速构建微服务的亲手实践
编写API Gateway代码
bash
mkdir bookstore && cd bookstore
go mod init bookstore
mkdir api && goctl api -o api/bookstore.api
api
syntax = "v1"
info(
title: "xx使用go-zero"
desc: "xx用来上手go-zero"
author: "xxxx"
email: "xxxx@gmail.com"
)
type (
addReq struct {
book string `form:"book"`
price int64 `form:"price"`
}
addResp struct {
ok bool `json:"ok"`
}
)
type (
checkReq struct {
book string `form:"book"`
}
checkResp struct {
found bool `json:"found"`
price int64 `json:"price"`
}
)
service bookstore-api {
@handler AddHandler
get /add (addReq) returns (addResp)
@handler CheckHandler
get /check (checkReq) returns (checkResp)
}
cd api && goctl api go -api bookstore.api -dir .
go
api
├── bookstore.api // api定义
├── bookstore.go // main入口定义
├── etc
│ └── bookstore-api.yaml // 配置文件
└── internal
├── config
│ └── config.go // 定义配置
├── handler
│ ├── addhandler.go // 实现addHandler
│ ├── checkhandler.go // 实现checkHandler
│ └── routes.go // 定义路由处理
├── logic
│ ├── addlogic.go // 实现AddLogic
│ └── checklogic.go // 实现CheckLogic
├── svc
│ └── servicecontext.go // 定义ServiceContext
└── types
└── types.go // 定义请求、返回结构体
go run bookstore.go -f etc/bookstore-api.yaml
启动API Gateway服务,默认侦听在8888端口
因为默认生成的api/etc/bookstore-api.yml
为:
yaml
Name: bookstore-api
Host: 0.0.0.0
Port: 8888
按提示下载,再次运行:
yaml
{"@timestamp":"2023-02-16T16:31:09.658+08:00","caller":"stat/usage.go:61","content":"CPU: 0m, MEMORY: Alloc=2.5Mi, TotalAlloc=2.5Mi, Sys=14.5Mi, NumGC=0","level":"stat"}
{"@timestamp":"2023-02-16T16:31:09.662+08:00","caller":"load/sheddingstat.go:61","content":"(api) shedding_stat [1m], cpu: 0, total: 0, pass: 0, drop: 0","level":"stat"}
{"@timestamp":"2023-02-16T16:31:15.044+08:00","caller":"stat/metrics.go:210","content":"(bookstore-api) - qps: 0.0/s, drops: 0, avg time: 0.0ms, med: 0.0ms, 90th: 0.0ms, 99th: 0.0ms, 99.9th: 0.0ms","level":"stat"}
会定时(默认一分钟)输出cpu,内存等的统计信息,可以通过 logx.DisableStat()
禁用 (可以做到自定义模板.tpl里)
返回的是null,并不是预期的{"found":false,"price":0}
这是因为:
resp是一个指针,这样直接return会是nil,需要如下显式声明
重启服务,再次发起请求,这样的response就符合预期了~
目前只返回了个空值,接下来会在rpc服务里实现业务逻辑
可以修改internal/svc/servicecontext.go来传递服务依赖(如果需要,比如Config,Auth,后续用到的RPC等)
实现逻辑可以修改internal/logic下的对应文件(如果接口较多,可以在.api里定义不同的group,使用goctl生成代码时,会自动在logic下根据group名称创建不同的文件夹)
可以通过goctl生成各种客户端语言的api调用代码(供客户端同学使用;支持多种语言)
编写RPC代码
编写add rpc服务
切到bookstore目录下
mkdir -p rpc/add && cd rpc/add
goctl rpc template -o add.proto
修改后文件内容如下:
proto
syntax = "proto3";
package add;
option go_package = "./pb";
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}
goctl rpc protoc add.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.
yaml
rpc/add
├── add.go // rpc服务main函数
├── add.proto // rpc接口定义
├── adder
│ ├── adder.go // 提供了外部调用方法,无需修改
│ ├── adder_mock.go // mock方法,测试用
│ └── types.go // request/response结构体定义
├── etc
│ └── add.yaml // 配置文件
├── internal
│ ├── config
│ │ └── config.go // 配置定义
│ ├── logic
│ │ └── addlogic.go // add业务逻辑在这里实现
│ ├── server
│ │ └── adderserver.go // 调用入口, 不需要修改
│ └── svc
│ └── servicecontext.go // 定义ServiceContext,传递依赖
└── pb
└── add.pb.go
go run add.go -f etc/add.yaml
可运行该服务
默认每隔一分钟输出cpu和内存信息
less
{"@timestamp":"2023-02-16T20:02:10.640+08:00","caller":"stat/usage.go:61","content":"CPU: 0m, MEMORY: Alloc=3.3Mi, TotalAlloc=6.2Mi, Sys=15.9Mi, NumGC=3","level":"stat"}
{"@timestamp":"2023-02-16T20:02:10.656+08:00","caller":"load/sheddingstat.go:61","content":"(rpc) shedding_stat [1m], cpu: 0, total: 0, pass: 0, drop: 0","level":"stat"}
编写check rpc服务
切到bookstore目录下
mkdir -p rpc/check && cd rpc/check
goctl rpc template -o check.proto
修改后文件内容如下:
proto
syntax = "proto3";
package check;
option go_package = "./pb";
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}
goctl rpc protoc check.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.
yaml
rpc/check
├── check.go // rpc服务main函数
├── check.proto // rpc接口定义
├── checker
│ ├── checker.go // 提供了外部调用方法,无需修改
│ ├── checker_mock.go // mock方法,测试用
│ └── types.go // request/response结构体定义
├── etc
│ └── check.yaml // 配置文件
├── internal
│ ├── config
│ │ └── config.go // 配置定义
│ ├── logic
│ │ └── checklogic.go // check业务逻辑在这里实现
│ ├── server
│ │ └── checkerserver.go // 调用入口, 不需要修改
│ └── svc
│ └── servicecontext.go // 定义ServiceContext,传递依赖
└── pb
└── check.pb.go
go run check.go -f etc/check.yaml
可运行该服务
修改etc/check.yaml的端口为8081(因为8080已经被add服务使用了)
再回去修改API Gateway代码,调用add/check rpc服务
api/etc/bookstore-api.yaml,增加如下内容
yaml
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpc
Check:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc
通过etcd自动去发现可用的add和check服务
修改api/internal/config/config.go如下,增加add&check服务依赖
修改api/internal/svc/servicecontext.go,如下:
通过ServiceContext在不同业务逻辑之间传递依赖
(问:怎么解决依赖注入问题)
修改api/internal/logic/addlogic.go里的Add方法,如下:
通过调用adder的Add方法实现添加图书到bookstore系统
修改api/internal/logic/checklogic.go里的Check方法,如下:
通过调用checker的Check方法实现从bookstore系统中查询图书的价格
定义数据库表结构,并生成CRUD+cache代码
bookstore下创建rpc/model目录
mkdir -p rpc/model
(不过一般习惯把这个model文件夹抽出来,和api,rpc在一层)
在rpc/model目录下编写创建book表的sql文件book.sql,如下:
sql
CREATE TABLE `book`
(
`book` varchar(255) NOT NULL COMMENT 'book name',
`price` int NOT NULL COMMENT 'book price',
PRIMARY KEY(`book`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
进入mysql命令行,创建DB和table
ini
create database gozero;
source book.sql;
在rpc/model目录下执行如下命令生成CRUD+cache代码,-c表示使用redis cache
goctl model mysql ddl -c -src book.sql -dir .
修改add rpc和check rpc,调用crud+cache代码
修改rpc/add/etc/add.yaml和rpc/check/etc/check.yaml,均增加如下内容:
yaml
DataSource: root:123456@@tcp(xxx.xxx.xx.xx:3306)/gozero
#DataSource: root:123456@@tcp(localhost:3306)/gozero?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai #可以这样指定一些其他信息
Table: book
Cache:
- Host: localhost:6379
可以使用多个redis作为cache,支持redis单点或者redis集群
修改rpc/add/internal/config.go和rpc/check/internal/config.go,如下:
修改rpc/add/internal/svc/servicecontext.go和rpc/check/internal/svc/servicecontext.go,如下:
修改rpc/add/internal/logic/addlogic.go,如下
修改rpc/check/internal/logic/checklogic.go,如下:
项目使用
需要先全部启动api服务所依赖的rpc服务。如果先启动api,则会报错:
error: context deadline exceeded, make sure rpc service "add.rpc" is already started
全部启动:
(后面可以 -f指定不同环境的xxx.yaml)
调用add api,新增图书
curl -i "http://localhost:8888/add?book=Bible&price=10"
此时看数据库,book表里新增了一行数据
调用check api,检查某本图书的价格
curl -i "http://localhost:8888/check?book=Bible"
重启check rpc,再次执行curl -i "http://localhost:8888/check?book=Bible"