Compare commits

..

27 Commits

Author SHA1 Message Date
zhoushuguang
3a9b9ceace add (#54)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-08 21:20:44 +08:00
bittoy
3128d63134 fix goctl model path (#53) 2020-09-08 17:05:22 +08:00
kingxt
4408767981 fix command run path bug (#52)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* optimized go path logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-08 12:19:34 +08:00
kevin
ff7c14c6b6 make chinese readme as default 2020-09-08 09:24:12 +08:00
kevin
520f4d7c1b update readme to add mapreduce link 2020-09-07 23:35:57 +08:00
zhoushuguang
0e674933f3 add mr tool doc (#50)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-07 22:40:29 +08:00
kingxt
1d12f20ff6 refactor (#49)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:47:03 +08:00
kingxt
2b815162f6 refactor (#48)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:12:03 +08:00
kingxt
1602f6ce81 refactor gomod logic (#47)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:04:59 +08:00
kevin
c5cd0d32d1 fix typoin doc 2020-09-07 17:08:06 +08:00
kevin
1cb17311dd add unit test for mapreduce 2020-09-06 18:19:19 +08:00
kevin
e987eb60d3 fix mapreduce problem when reducer doesn't write 2020-09-06 18:13:42 +08:00
kevin
99a863e8be add language link 2020-09-06 16:37:14 +08:00
kevin
5333fb93e5 add bookstore english tutorial 2020-09-06 16:36:18 +08:00
kevin
cb13556461 add language link 2020-09-06 15:38:33 +08:00
kevin
561370d5c9 add shorturl english tutorial 2020-09-06 15:36:12 +08:00
kevin
7c779d0433 fix readme typo 2020-09-05 19:18:15 +08:00
kevin
6814c86fcd add english readme 2020-09-05 19:09:18 +08:00
windk
a1d2ea9d85 fix typo (#38)
fix typo: *shorten.ShortenReq -> *transform.ShortenReq
2020-09-04 17:38:20 +08:00
kevin
4dfbd66323 add goctl description 2020-09-04 15:40:12 +08:00
kevin
dbf556e7d2 update readme 2020-09-04 08:16:17 +08:00
kevin
c0d0e00803 update example doc 2020-09-04 08:13:22 +08:00
kevin
b4aa89fc25 add wechat qrcode 2020-09-04 08:00:10 +08:00
kevin
11dd3d75ec add bookstore example 2020-09-03 23:26:04 +08:00
wwek
167422ac4f fix LF (#37)
* fix LF

* fix  remove export
2020-09-03 22:37:52 +08:00
kevin
a74d73fb2e fix bookstore example 2020-09-03 20:23:27 +08:00
kevin
81a9ada2d9 add bookstore example 2020-09-03 18:09:12 +08:00
74 changed files with 4134 additions and 100 deletions

View File

@@ -16,7 +16,10 @@ const (
minWorkers = 1
)
var ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
var (
ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
ErrReduceNoOutput = errors.New("reduce not writing value")
)
type (
GenerateFunc func(source chan<- interface{})
@@ -93,7 +96,14 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
collector := make(chan interface{}, options.workers)
done := syncx.NewDoneChan()
writer := newGuardedWriter(output, done.Done())
var closeOnce sync.Once
var retErr errorx.AtomicError
finish := func() {
closeOnce.Do(func() {
done.Close()
close(output)
})
}
cancel := once(func(err error) {
if err != nil {
retErr.Set(err)
@@ -102,14 +112,15 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
}
drain(source)
done.Close()
close(output)
finish()
})
go func() {
defer func() {
if r := recover(); r != nil {
cancel(fmt.Errorf("%v", r))
} else {
finish()
}
}()
reducer(collector, writer, cancel)
@@ -122,7 +133,7 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
} else if ok {
return value, nil
} else {
return nil, nil
return nil, ErrReduceNoOutput
}
}

View File

@@ -378,6 +378,22 @@ func TestMapReduceVoidCancelWithRemains(t *testing.T) {
assert.True(t, done.True())
}
func TestMapReduceWithoutReducerWrite(t *testing.T) {
uids := []int{1, 2, 3}
res, err := MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
source <- uid
}
}, func(item interface{}, writer Writer, cancel func(error)) {
writer.Write(item)
}, func(pipe <-chan interface{}, writer Writer, cancel func(error)) {
drain(pipe)
// not calling writer.Write(...), should not panic
})
assert.Equal(t, ErrReduceNoOutput, err)
assert.Nil(t, res)
}
func BenchmarkMapReduce(b *testing.B) {
b.ReportAllocs()

617
doc/bookstore-en.md Normal file
View File

@@ -0,0 +1,617 @@
English | [简体中文](bookstore.md)
# Rapid development of microservices - multiple RPCs
## 0. Why building microservices are so difficult?
To build a well working microservice, we need lots of knowledges from different aspects.
* basic functionalities
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
2. service discovery, make sure new or terminated nodes are detected asap
3. load balancing, balance the traffic base on the throughput of nodes
4. timeout control, avoid the nodes continue to process the timed out requests
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
* advanced functionalities
1. authorization, make sure users can only access their own data
2. tracing, to understand the whole system and locate the specific problem quickly
3. logging, collects data and helps to backtrace problems
4. observability, no metrics, no optimization
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, its very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
Lets take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, youll find that its so easy to write microservices!
## 1. What is a bookstore service?
For simplicity, the bookstore service only contains two functionalities, adding books and quering prices.
Writting this bookstore service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this bookstore service is not suitable for production use.
## 2. Architecture of shorturl microservice
<img src="images/bookstore-arch.png" alt="architecture" width="800" />
## 3. goctl generated code overview
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
* API Gateway
<img src="images/api-gen.png" alt="api" width="800" />
* RPC
<img src="images/rpc-gen.png" alt="rpc" width="800" />
* model
<img src="images/model-gen.png" alt="model" width="800" />
And now, lets walk through the complete flow of quickly create a microservice with go-zero.
## 4. Get started
* install etcd, mysql, redis
* install goctl
```shell
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
```
* create the working dir bookstore
* in `bookstore` dir, execute `go mod init bookstore` to initialize `go.mod``
## 5. Write code for API Gateway
* use goctl to generate `api/bookstore.api`
```
goctl api -o bookstore.api
```
for simplicity, the leading `info` block is removed, and the code looks like:
```go
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 {
@server(
handler: AddHandler
)
get /add(addReq) returns(addResp)
@server(
handler: CheckHandler
)
get /check(checkReq) returns(checkResp)
}
```
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
* `service bookstore-api { defines the service name
* `@server` defines the properties that used in server side
* `handler` defines the handler name
* `get /add(addReq) returns(addResp)` defines this is a GET request, the request parameters, and the response parameters
* generate the code for API Gateway by using goctl
```shell
goctl api go -api bookstore.api -dir .
```
the generated file structure looks like:
```
api
├── bookstore.api // api definition
├── bookstore.go // main entrance
├── etc
│ └── bookstore-api.yaml // configuration file
└── internal
├── config
│ └── config.go // configuration definition
├── handler
│ ├── addhandler.go // implements addHandler
│ ├── checkhandler.go // implements checkHandler
│ └── routes.go // routes definition
├── logic
│ ├── addlogic.go // implements AddLogic
│ └── checklogic.go // implements CheckLogic
├── svc
│ └── servicecontext.go // defines ServiceContext
└── types
└── types.go // defines request/response
```
* start API Gateway service, listens on port 8888 by default
```shell
go run bookstore.go -f etc/bookstore-api.yaml
```
* test API Gateway service
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 06:46:18 GMT
Content-Length: 25
{"found":false,"price":0}
```
You can see that the API Gateway service did nothing except returned a zero value. And lets implement the business logic in rpc service.
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
* implement logic in package `internal/logic`
* you can use goctl to generate code for clients base on the .api file
* till now, the client engineer can work with the api, dont need to wait for the implementation of server side
## 6. Write code for add rpc service
* under directory `rpc/add` create `add.proto` file
```shell
goctl rpc template -o add.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
package add;
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}
```
* use goctl to generate the rpc code, execute the following command in `rpc/add`
```shell
goctl rpc proto -src add.proto
```
the generated file structure looks like:
```
rpc/add
├── add.go // rpc main entrance
├── add.proto // rpc definition
├── adder
│ ├── adder.go // defines how rpc clients call this service
│ ├── adder_mock.go // mock file, for test purpose
│ └── types.go // request/response definition
├── etc
│ └── add.yaml // configuration file
├── internal
│ ├── config
│ │ └── config.go // configuration definition
│ ├── logic
│ │ └── addlogic.go // add logic here
│ ├── server
│ │ └── adderserver.go // rpc handler
│ └── svc
│ └── servicecontext.go // defines service context, like dependencies
└── pb
└── add.pb.go
```
just run it, looks like:
```shell
$ go run add.go -f etc/add.yaml
Starting rpc server at 127.0.0.1:8080...
```
you can change the listening port in file `etc/add.yaml`.
## 7. Write code for check rpc service
* under directory `rpc/check` create `check.proto` file
```shell
goctl rpc template -o check.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
package check;
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}
```
* use goctl to generate the rpc code, execute the following command in `rpc/check`
```shell
goctl rpc proto -src check.proto
```
the generated file structure looks like:
```
rpc/check
├── check.go // rpc main entrance
├── check.proto // rpc definition
├── checker
│ ├── checker.go // defines how rpc clients call this service
│ ├── checker_mock.go // mock file, for test purpose
│ └── types.go // request/response definition
├── etc
│ └── check.yaml // configuration file
├── internal
│ ├── config
│ │ └── config.go // configuration definition
│ ├── logic
│ │ └── checklogic.go // check logic here
│ ├── server
│ │ └── checkerserver.go // rpc handler
│ └── svc
│ └── servicecontext.go // defines service context, like dependencies
└── pb
└── check.pb.go
```
you can change the listening port in `etc/check.yaml`.
we need to change the port in `etc/check.yaml` to `8081`, because `8080 ` is used by `add` service.
just run it, looks like:
```shell
$ go run check.go -f etc/check.yaml
Starting rpc server at 127.0.0.1:8081...
```
## 8. Modify API Gateway to call add/check rpc service
* modify the configuration file `bookstore-api.yaml`, add the following:
```yaml
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpc
Check:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc
```
automatically discover the add/check service by using etcd.
* modify the file `internal/config/config.go`, add dependency on add/check service:
```go
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf // manual code
Check rpcx.RpcClientConf // manual code
}
```
* modify the file `internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
Config config.Config
Adder adder.Adder // manual code
Checker checker.Checker // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // manual code
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // manual code
}
}
```
passing the dependencies among services within ServiceContext.
* modify the method `Add` in the file `internal/logic/addlogic.go`, looks like:
```go
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
// manual code start
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
Book: req.Book,
Price: req.Price,
})
if err != nil {
return nil, err
}
return &types.AddResp{
Ok: resp.Ok,
}, nil
// manual code stop
}
```
by calling the method `Add` of `adder` to add books into bookstore.
* modify the file `internal/logic/checklogic.go`, looks like:
```go
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
// manual code start
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
Book: req.Book,
})
if err != nil {
return nil, err
}
return &types.CheckResp{
Found: resp.Found,
Price: resp.Price,
}, nil
// manual code stop
}
```
by calling the method `Check` of `checker` to check the prices from the bookstore.
Till now, weve done the modification of API Gateway. All the manually added code are marked.
## 9. Define the database schema, generate the code for CRUD+cache
* under bookstore, create the directory `rpc/model`: `mkdir -p rpc/model`
* under the directory rpc/model create the file called `book.sql`, contents as below:
```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;
```
* create DB and table
```sql
create database gozero;
```
```sql
source book.sql;
```
* under the directory `rpc/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
```shell
goctl model mysql ddl -c -src book.sql -dir .
```
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
the generated file structure looks like:
```
rpc/model
├── bookstore.sql
├── bookstoremodel.go // CRUD+cache code
└── vars.go // const and var definition
```
## 10. Modify add/check rpc to call crud+cache
* modify `rpc/add/etc/add.yaml`, add the following:
```yaml
DataSource: root:@tcp(localhost:3306)/gozero
Table: book
Cache:
- Host: localhost:6379
```
you can use multiple redis as cache. redis node and cluster are both supported.
* modify `rpc/add/internal/config.go`, like below:
```go
type Config struct {
rpcx.RpcServerConf
DataSource string // manual code
Table string // manual code
Cache cache.CacheConf // manual code
}
```
added the configuration for mysql and redis cache.
* modify `rpc/add/internal/svc/servicecontext.go` and `rpc/check/internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
c config.Config
Model *model.BookModel // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
}
}
```
* modify `rpc/add/internal/logic/addlogic.go`, like below:
```go
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
// manual code start
_, err := l.svcCtx.Model.Insert(model.Book{
Book: in.Book,
Price: in.Price,
})
if err != nil {
return nil, err
}
return &add.AddResp{
Ok: true,
}, nil
// manual code stop
}
```
* modify `rpc/check/internal/logic/checklogic.go`, like below:
```go
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
// manual code start
resp, err := l.svcCtx.Model.FindOne(in.Book)
if err != nil {
return nil, err
}
return &check.CheckResp{
Found: true,
Price: resp.Price,
}, nil
// manual code stop
}
```
till now, we finished modifing the code, all the modified code is marked.
## 11. Call shorten and expand services
* call add api
```shell
curl -i "http://localhost:8888/add?book=go-zero&price=10"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:42:13 GMT
Content-Length: 11
{"ok":true}
```
* call check api
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:47:34 GMT
Content-Length: 25
{"found":true,"price":10}
```
## 12. Benchmark
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the check api. We read the data from mysql and cache it in redis. For simplicity, I only check one book, because of cache, the effect is the same for multiple books.
Before benchmark, we need to change the max open files:
```shel
ulimit -n 20000
```
And change the log level to error, to avoid too many logs affect the benchmark. Add the following in every yaml file:
```yaml
Log:
Level: error
```
![Benchmark](images/bookstore-benchmark.png)
as shown above, in my MacBook Pro, the QPS is like 30K+.
## 13. Full code
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
## 14. Conclusion
We always adhere to **prefer tools over conventions and documents**.
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

618
doc/bookstore.md Normal file
View File

@@ -0,0 +1,618 @@
[English](bookstore-en.md) | 简体中文
# 快速构建微服务-多RPC版
## 0. 为什么说做好微服务很难?
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
* 基本功能层面
1. 并发控制&限流,避免服务被突发流量击垮
2. 服务注册与服务发现,确保能够动态侦测增减的节点
3. 负载均衡,需要根据节点承受能力分发流量
4. 超时控制,避免对已超时请求做无用功
5. 熔断设计,快速失败,保障故障节点的恢复能力
* 高阶功能层面
1. 请求认证,确保每个用户只能访问自己的数据
2. 链路追踪,用于理解整个系统和快速定位特定请求的问题
3. 日志,用于数据收集和问题定位
4. 可观测性,没有度量就没有优化
对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。[go-zero微服务框架](https://github.com/tal-tech/go-zero)就是为此而生。
另外,我们始终秉承**工具大于约定和文档**的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了`goctl`工具。
下面我通过书店服务来演示通过[go-zero](https://github.com/tal-tech/go-zero)快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!
## 1. 书店服务示例简介
为了教程简单,我们用书店服务做示例,并且只实现其中的增加书目和检查价格功能。
写此书店服务是为了从整体上演示go-zero构建完整微服务的过程实现细节尽可能简化了。
## 2. 书店微服务架构图
<img src="images/bookstore-arch.png" alt="架构图" width="800" />
## 3. goctl各层代码生成一览
所有绿色背景的功能模块是自动生成的,按需激活,红色模块是需要自己写的,也就是增加下依赖,编写业务特有逻辑,各层示意图分别如下:
* API Gateway
<img src="images/bookstore-api.png" alt="api" width="800" />
* RPC
<img src="images/bookstore-rpc.png" alt="架构图" width="800" />
* model
<img src="images/bookstore-model.png" alt="model" width="800" />
下面我们来一起完整走一遍快速构建微服务的流程Lets `Go`!🏃‍♂️
## 4. 准备工作
* 安装etcd, mysql, redis
* 安装goctl工具
```shell
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
```
* 创建工作目录`bookstore`
* 在`bookstore`目录下执行`go mod init bookstore`初始化`go.mod`
## 5. 编写API Gateway代码
* 在`bookstore/api`目录下通过goctl生成`api/bookstore.api`
```
goctl api -o bookstore.api
```
编辑`bookstore.api`,为了简洁,去除了文件开头的`info`,代码如下:
```go
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 {
@server(
handler: AddHandler
)
get /add(addReq) returns(addResp)
@server(
handler: CheckHandler
)
get /check(checkReq) returns(checkResp)
}
```
type用法和go一致service用来定义get/post/head/delete等api请求解释如下
* `service bookstore-api {`这一行定义了service名字
* `@server`部分用来定义server端用到的属性
* `handler`定义了服务端handler名字
* `get /add(addReq) returns(addResp)`定义了get方法的路由、请求参数、返回参数等
* 使用goctl生成API Gateway代码
```shell
goctl api go -api bookstore.api -dir .
```
生成的文件结构如下:
```
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 // 定义请求、返回结构体
```
* 启动API Gateway服务默认侦听在8888端口
```shell
go run bookstore.go -f etc/bookstore-api.yaml
```
* 测试API Gateway服务
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
返回如下:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 06:46:18 GMT
Content-Length: 25
{"found":false,"price":0}
```
可以看到我们API Gateway其实啥也没干就返回了个空值接下来我们会在rpc服务里实现业务逻辑
* 可以修改`internal/svc/servicecontext.go`来传递服务依赖(如果需要)
* 实现逻辑可以修改`internal/logic`下的对应文件
* 可以通过`goctl`生成各种客户端语言的api调用代码
* 到这里你已经可以通过goctl生成客户端代码给客户端同学并行开发了支持多种语言详见文档
## 6. 编写add rpc服务
* 在`rpc/add`目录下编写`add.proto`文件
可以通过命令生成proto文件模板
```shell
goctl rpc template -o add.proto
```
修改后文件内容如下:
```protobuf
syntax = "proto3";
package add;
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}
```
* 用`goctl`生成rpc代码在`rpc/add`目录下执行命令
```shell
goctl rpc proto -src add.proto
```
文件结构如下:
```
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
```
直接可以运行,如下:
```shell
$ go run add.go -f etc/add.yaml
Starting rpc server at 127.0.0.1:8080...
```
`etc/add.yaml`文件里可以修改侦听端口等配置
## 7. 编写check rpc服务
* 在`rpc/check`目录下编写`check.proto`文件
可以通过命令生成proto文件模板
```shell
goctl rpc template -o check.proto
```
修改后文件内容如下:
```protobuf
syntax = "proto3";
package check;
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}
```
* 用`goctl`生成rpc代码在`rpc/check`目录下执行命令
```shell
goctl rpc proto -src check.proto
```
文件结构如下:
```
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
```
`etc/check.yaml`文件里可以修改侦听端口等配置
需要修改`etc/check.yaml`的端口为`8081`,因为`8080`已经被`add`服务使用了,直接可以运行,如下:
```shell
$ go run check.go -f etc/check.yaml
Starting rpc server at 127.0.0.1:8081...
```
## 8. 修改API Gateway代码调用add/check rpc服务
* 修改配置文件`bookstore-api.yaml`,增加如下内容
```yaml
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpc
Check:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc
```
通过etcd自动去发现可用的add/check服务
* 修改`internal/config/config.go`如下增加add/check服务依赖
```go
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf // 手动代码
Check rpcx.RpcClientConf // 手动代码
}
```
* 修改`internal/svc/servicecontext.go`,如下:
```go
type ServiceContext struct {
Config config.Config
Adder adder.Adder // 手动代码
Checker checker.Checker // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // 手动代码
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // 手动代码
}
}
```
通过ServiceContext在不同业务逻辑之间传递依赖
* 修改`internal/logic/addlogic.go`里的`Add`方法,如下:
```go
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
// 手动代码开始
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
Book: req.Book,
Price: req.Price,
})
if err != nil {
return nil, err
}
return &types.AddResp{
Ok: resp.Ok,
}, nil
// 手动代码结束
}
```
通过调用`adder`的`Add`方法实现添加图书到bookstore系统
* 修改`internal/logic/checklogic.go`里的`Check`方法,如下:
```go
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
// 手动代码开始
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
Book: req.Book,
})
if err != nil {
return nil, err
}
return &types.CheckResp{
Found: resp.Found,
Price: resp.Price,
}, nil
// 手动代码结束
}
```
通过调用`checker`的`Check`方法实现从bookstore系统中查询图书的价格
## 9. 定义数据库表结构并生成CRUD+cache代码
* bookstore下创建`rpc/model`目录:`mkdir -p rpc/model`
* 在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;
```
* 创建DB和table
```sql
create database gozero;
```
```sql
source book.sql;
```
* 在`rpc/model`目录下执行如下命令生成CRUD+cache代码`-c`表示使用`redis cache`
```shell
goctl model mysql ddl -c -src book.sql -dir .
```
也可以用`datasource`命令代替`ddl`来指定数据库链接直接从schema生成
生成后的文件结构如下:
```
rpc/model
├── bookstore.sql
├── bookstoremodel.go // CRUD+cache代码
└── vars.go // 定义常量和变量
```
## 10. 修改add/check rpc代码调用crud+cache代码
* 修改`rpc/add/etc/add.yaml`和`rpc/check/etc/check.yaml`,增加如下内容:
```yaml
DataSource: root:@tcp(localhost:3306)/gozero
Table: book
Cache:
- Host: localhost:6379
```
可以使用多个redis作为cache支持redis单点或者redis集群
* 修改`rpc/add/internal/config.go`和`rpc/check/internal/config.go`,如下:
```go
type Config struct {
rpcx.RpcServerConf
DataSource string // 手动代码
Table string // 手动代码
Cache cache.CacheConf // 手动代码
}
```
增加了mysql和redis cache配置
* 修改`rpc/add/internal/svc/servicecontext.go`和`rpc/check/internal/svc/servicecontext.go`,如下:
```go
type ServiceContext struct {
c config.Config
Model *model.BookModel // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码
}
}
```
* 修改`rpc/add/internal/logic/addlogic.go`,如下:
```go
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
// 手动代码开始
_, err := l.svcCtx.Model.Insert(model.Book{
Book: in.Book,
Price: in.Price,
})
if err != nil {
return nil, err
}
return &add.AddResp{
Ok: true,
}, nil
// 手动代码结束
}
```
* 修改`rpc/check/internal/logic/checklogic.go`,如下:
```go
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
// 手动代码开始
resp, err := l.svcCtx.Model.FindOne(in.Book)
if err != nil {
return nil, err
}
return &check.CheckResp{
Found: true,
Price: resp.Price,
}, nil
// 手动代码结束
}
```
至此代码修改完成,凡事手动修改的代码我加了标注
## 11. 完整调用演示
* add api调用
```shell
curl -i "http://localhost:8888/add?book=go-zero&price=10"
```
返回如下:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:42:13 GMT
Content-Length: 11
{"ok":true}
```
* check api调用
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
返回如下:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:47:34 GMT
Content-Length: 25
{"found":true,"price":10}
```
## 12. Benchmark
因为写入依赖于mysql的写入速度就相当于压mysql了所以压测只测试了check接口相当于从mysql里读取并利用缓存为了方便直接压这一本书因为有缓存多本书也是一样的对压测结果没有影响。
压测之前,让我们先把打开文件句柄数调大:
```shel
ulimit -n 20000
```
并日志的等级改为`error`防止过多的info影响压测结果在每个yaml配置文件里加上如下
```yaml
Log:
Level: error
```
![Benchmark](images/bookstore-benchmark.png)
可以看出在我的MacBook Pro上能达到3万+的qps。
## 13. 完整代码
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
## 14. 总结
我们一直强调**工具大于约定和文档**。
go-zero不只是一个框架更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

BIN
doc/images/api-gen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
doc/images/model-gen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
doc/images/mr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
doc/images/mr_time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
doc/images/rpc-gen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
doc/images/wechat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -1,29 +1,184 @@
# mapreduce用法
# 通过MapReduce降低服务响应时间
## Map
在微服务中开发中api网关扮演对外提供restful api的角色而api的数据往往会依赖其他服务复杂的api更是会依赖多个甚至数十个服务。虽然单个被依赖服务的耗时一般都比较低但如果多个服务串行依赖的话那么整个api的耗时将会大大增加。
> channel是Map的返回值
那么通过什么手段来优化呢我们首先想到的是通过并发来的方式来处理依赖这样就能降低整个依赖的耗时Go基础库中为我们提供了 [WaitGroup](https://golang.org/pkg/sync/#WaitGroup) 工具用来进行并发控制但实际业务场景中多个依赖如果有一个出错我们期望能立即返回而不是等所有依赖都执行完再返回结果而且WaitGroup中对变量的赋值往往需要加锁每个依赖函数都需要添加Add和Done对于新手来说比较容易出错
由于Map是个并发操作如果不用range或drain的方式那么在使用返回值的时候可能Map里面的代码还在读写这个返回值可能导致数据不全或者`concurrent read write错误`
基于以上的背景go-zero框架中为我们提供了并发处理工具[MapReduce](https://github.com/tal-tech/go-zero/blob/master/core/mr/mapreduce.go)该工具开箱即用不需要做什么初始化我们通过下图看下使用MapReduce和没使用的耗时对比:
* 如果需要收集Map生成的结果那么使用如下方式
![依赖耗时对比](./images/mr_time.png)
```
for v := range channel {
// v is with type interface{}
相同的依赖串行处理的话需要200ms使用MapReduce后的耗时等于所有依赖中最大的耗时为100ms可见MapReduce可以大大降低服务耗时而且随着依赖的增加效果就会越明显减少处理耗时的同时并不会增加服务器压力
## 并发处理工具[MapReduce](https://github.com/tal-tech/go-zero/tree/master/core/mr)
[MapReduce](https://zh.wikipedia.org/wiki/MapReduce)是Google提出的一个软件架构用于大规模数据集的并行运算go-zero中的MapReduce工具正是借鉴了这种架构思想
go-zero框架中的MapReduce工具主要用来对批量数据进行并发的处理以此来提升服务的性能
![mapreduce原理图](./images/mr.png)
我们通过几个示例来演示MapReduce的用法
MapReduce主要有三个参数第一个参数为generate用以生产数据第二个参数为mapper用以对数据进行处理第三个参数为reducer用以对mapper后的数据做聚合返回还可以通过opts选项设置并发处理的线程数量
场景一: 某些功能的结果往往需要依赖多个服务比如商品详情的结果往往会依赖用户服务、库存服务、订单服务等等一般被依赖的服务都是以rpc的形式对外提供为了降低依赖的耗时我们往往需要对依赖做并行处理
```
func productDetail(uid, pid int64) (*ProductDetail, error) {
var pd ProductDetail
err := mr.Finish(func() (err error) {
pd.User, err = userRpc.User(uid)
return
}, func() (err error) {
pd.Store, err = storeRpc.Store(pid)
return
}, func() (err error) {
pd.Order, err = orderRpc.Order(pid)
return
})
if err != nil {
log.Printf("product detail error: %v", err)
return nil, err
}
```
* 如果不需要收集结果那么就需要显式的调用mapreduce.Drain
```
mapreduce.Drain(channel)
```
## MapReduce
return &pd, nil
}
```
该示例中返回商品详情依赖了多个服务获取数据,因此做并发的依赖处理,对接口的性能有很大的提升
场景二: 很多时候我们需要对一批数据进行处理比如对一批用户id效验每个用户的合法性并且效验过程中有一个出错就认为效验失败返回的结果为效验合法的用户id
```
func checkLegal(uids []int64) ([]int64, error) {
r, err := mr.MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
source <- uid
}
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
uid := item.(int64)
ok, err := check(uid)
if err != nil {
cancel(err)
}
if ok {
writer.Write(uid)
}
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
var uids []int64
for p := range pipe {
uids = append(uids, p.(int64))
}
writer.Write(uids)
})
if err != nil {
log.Printf("check error: %v", err)
return nil, err
}
return r.([]int64), nil
}
func check(uid int64) (bool, error) {
// do something check user legal
return true, nil
}
```
该示例中如果check过程出现错误则通过cancel方法结束效验过程并返回error整个效验过程结束如果某个uid效验结果为false则最终结果不返回该uid
**MapReduce使用注意事项**
* mapper和reducer中都可以调用cancel参数为error调用后立即返回返回结果为nil, error
* mapper中如果不调用writer.Write则item最终不会被reducer聚合
* reducer中如果不调用writer.Wirte则返回结果为nil, ErrReduceNoOutput
* reducer为单线程所有mapper出来的结果在这里串行聚合
***实现原理分析:***
MapReduce中首先通过buildSource方法通过执行generate(参数为无缓冲channel)产生数据并返回无缓冲的channelmapper会从该channel中读取数据
```
func buildSource(generate GenerateFunc) chan interface{} {
source := make(chan interface{})
go func() {
defer close(source)
generate(source)
}()
return source
}
```
在MapReduceWithSource方法中定义了cancel方法mapper和reducer中都可以调用该方法调用后主线程收到close信号会立马返回
```
cancel := once(func(err error) {
if err != nil {
retErr.Set(err)
} else {
// 默认的error
retErr.Set(ErrCancelWithNil)
}
drain(source)
// 调用close(ouput)主线程收到Done信号立马返回
finish()
})
```
在mapperDispatcher方法中调用了executeMappersexecuteMappers消费buildSource产生的数据每一个item都会起一个goroutine单独处理默认最大并发数为16可以通过WithWorkers进行设置
```
var wg sync.WaitGroup
defer func() {
wg.Wait() // 保证所有的item都处理完成
close(collector)
}()
pool := make(chan lang.PlaceholderType, workers)
writer := newGuardedWriter(collector, done) // 将mapper处理完的数据写入collector
for {
select {
case <-done: // 当调用了cancel会触发立即返回
return
case pool <- lang.Placeholder: // 控制最大并发数
item, ok := <-input
if !ok {
<-pool
return
}
wg.Add(1)
go func() {
defer func() {
wg.Done()
<-pool
}()
mapper(item, writer) // 对item进行处理处理完调用writer.Write把结果写入collector对应的channel中
}()
}
}
```
reducer单goroutine对数mapper写入collector的数据进行处理如果reducer中没有手动调用writer.Write则最终会执行finish方法对output进行close避免死锁
```
go func() {
defer func() {
if r := recover(); r != nil {
cancel(fmt.Errorf("%v", r))
} else {
finish()
}
}()
reducer(collector, writer, cancel)
}()
```
在该工具包中还提供了许多针对不同业务场景的方法实现原理与MapReduce大同小异感兴趣的同学可以查看源码学习
* MapReduceVoid 功能和MapReduce类似但没有结果返回只返回error
* Finish 处理固定数量的依赖返回error有一个error立即返回
* FinishVoid 和Finish方法功能类似没有返回值
* Map 只做generate和mapper处理返回channel
* MapVoid 和Map功能类似无返回
本文主要介绍了go-zero框架中的MapReduce工具在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助希望本篇文章能给大家带来一些收获。
* mapper和reducer方法里可以调用cancel调用了cancel之后返回值会是`nil, false`
* mapper里面如果有item不写入writer那么这个item就不会被reduce收集
* mapper里面如果有处理item时panic那么这个item也不会被reduce收集
* reduce是单线程所有mapper出来的结果在这里串行处理
* reduce里面不写writer或者panic会导致返回`nil, false`

536
doc/shorturl-en.md Normal file
View File

@@ -0,0 +1,536 @@
English | [简体中文](shorturl.md)
# Rapid development of microservices
## 0. Why building microservices are so difficult?
To build a well working microservice, we need lots of knowledges from different aspects.
* basic functionalities
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
2. service discovery, make sure new or terminated nodes are detected asap
3. load balancing, balance the traffic base on the throughput of nodes
4. timeout control, avoid the nodes continue to process the timed out requests
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
* advanced functionalities
1. authorization, make sure users can only access their own data
2. tracing, to understand the whole system and locate the specific problem quickly
3. logging, collects data and helps to backtrace problems
4. observability, no metrics, no optimization
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, its very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
Lets take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, youll find that its so easy to write microservices!
## 1. What is a shorturl service?
A shorturl service is that it converts a long url into a short one, by well designed algorithms.
Writting this shorturl service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this shorturl service is not suitable for production use.
## 2. Architecture of shorturl microservice
<img src="images/shorturl-arch.png" alt="Architecture" width="800" />
* In this tutorial, I only use one rpc service, transform, to demonstrate. Its not telling that one API Gateway only can call one RPC service, its only for simplicity here.
* In production, we should try best to isolate the data belongs to services, that means each service should only use its own database.
## 3. goctl generated code overview
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
* API Gateway
<img src="images/api-gen.png" alt="api" width="800" />
* RPC
<img src="images/rpc-gen.png" alt="rpc" width="800" />
* model
<img src="images/model-gen.png" alt="model" width="800" />
And now, lets walk through the complete flow of quickly create a microservice with go-zero.
## 4. Get started
* install etcd, mysql, redis
* install goctl
```shell
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
```
* create the working dir `shorturl`
* in `shorturl` dir, execute `go mod init shorturl` to initialize `go.mod`
## 5. Write code for API Gateway
* use goctl to generate `api/shorturl.api`
```shell
goctl api -o bookstore.api
```
for simplicity, the leading `info` block is removed, and the code looks like:
```go
type (
expandReq struct {
shorten string `form:"shorten"`
}
expandResp struct {
url string `json:"url"`
}
)
type (
shortenReq struct {
url string `form:"url"`
}
shortenResp struct {
shorten string `json:"shorten"`
}
)
service shorturl-api {
@server(
handler: ShortenHandler
)
get /shorten(shortenReq) returns(shortenResp)
@server(
handler: ExpandHandler
)
get /expand(expandReq) returns(expandResp)
}
```
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
* `service shorturl-api {` defines the service name
* `@server` defines the properties that used in server side
* `handler` defines the handler name
* `get /shorten(shortenReq) returns(shortenResp)` defines this is a GET request, the request parameters, and the response parameters
* generate the code for API Gateway by using goctl
```shell
goctl api go -api shorturl.api -dir .
```
the generated file structure looks like:
```
.
├── api
│   ├── etc
│   │   └── shorturl-api.yaml // configuration file
│   ├── internal
│   │   ├── config
│   │   │   └── config.go // configuration definition
│   │   ├── handler
│   │   │   ├── expandhandler.go // implements expandHandler
│   │   │   ├── routes.go // routes definition
│   │   │   └── shortenhandler.go // implements shortenHandler
│   │   ├── logic
│   │   │   ├── expandlogic.go // implements ExpandLogic
│   │   │   └── shortenlogic.go // implements ShortenLogic
│   │   ├── svc
│   │   │   └── servicecontext.go // defines ServiceContext
│   │   └── types
│   │   └── types.go // defines request/response
│   ├── shorturl.api
│   └── shorturl.go // main entrance
├── go.mod
└── go.sum
```
* start API Gateway service, listens on port 8888 by default
```shell
go run shorturl.go -f etc/shorturl-api.yaml
```
* test API Gateway service
```shell
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 27 Aug 2020 14:31:39 GMT
Content-Length: 15
{"shortUrl":""}
```
You can see that the API Gateway service did nothing except returned a zero value. And lets implement the business logic in rpc service.
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
* implement logic in package `internal/logic`
* you can use goctl to generate code for clients base on the .api file
* till now, the client engineer can work with the api, dont need to wait for the implementation of server side
## 6. Write code for transform rpc service
* under directory `rpc/transform` create `transform.proto` file
```shell
goctl rpc template -o transform.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
package transform;
message expandReq {
string shorten = 1;
}
message expandResp {
string url = 1;
}
message shortenReq {
string url = 1;
}
message shortenResp {
string shorten = 1;
}
service transformer {
rpc expand(expandReq) returns(expandResp);
rpc shorten(shortenReq) returns(shortenResp);
}
```
* use goctl to generate the rpc code, execute the following command in `rpc/transofrm`
```shell
goctl rpc proto -src transform.proto
```
the generated file structure looks like:
```
rpc/transform
├── etc
│   └── transform.yaml // configuration file
├── internal
│   ├── config
│   │   └── config.go // configuration definition
│   ├── logic
│   │   ├── expandlogic.go // implements expand logic
│   │   └── shortenlogic.go // implements shorten logic
│   ├── server
│   │   └── transformerserver.go // rpc handler
│   └── svc
│   └── servicecontext.go // defines service context, like dependencies
├── pb
│   └── transform.pb.go
├── transform.go // rpc main entrance
├── transform.proto
└── transformer
├── transformer.go // defines how rpc clients call this service
├── transformer_mock.go // mock file, for test purpose
└── types.go // request/response definition
```
just run it, looks like:
```shell
$ go run transform.go -f etc/transform.yaml
Starting rpc server at 127.0.0.1:8080...
```
you can change the listening port in file `etc/transform.yaml`.
## 7. Modify API Gateway to call transform rpc service
* modify the configuration file `shorturl-api.yaml`, add the following:
```yaml
Transform:
Etcd:
Hosts:
- localhost:2379
Key: transform.rpc
```
automatically discover the transform service by using etcd.
* modify the file `internal/config/config.go`, add dependency on transform service:
```go
type Config struct {
rest.RestConf
Transform rpcx.RpcClientConf // manual code
}
```
* modify the file `internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
Config config.Config
Transformer transformer.Transformer // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // manual code
}
}
```
passing the dependencies among services within ServiceContext.
* modify the method `Expand` in the file `internal/logic/expandlogic.go`, looks like:
```go
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
// manual code start
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
Shorten: req.Shorten,
})
if err != nil {
return nil, err
}
return &types.ExpandResp{
Url: resp.Url,
}, nil
// manual code stop
}
```
by calling the method `Expand` of `transformer` to restore the shortened url.
* modify the file `internal/logic/shortenlogic.go`, looks like:
```go
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
// manual code start
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
Url: req.Url,
})
if err != nil {
return nil, err
}
return &types.ShortenResp{
Shorten: resp.Shorten,
}, nil
// manual code stop
}
```
by calling the method `Shorten` of `transformer` to shorten the url.
Till now, weve done the modification of API Gateway. All the manually added code are marked.
## 8. Define the database schema, generate the code for CRUD+cache
* under shorturl, create the directory `rpc/transform/model`: `mkdir -p rpc/transform/model`
* under the directory rpc/transform/model create the file called shorturl.sql`, contents as below:
```sql
CREATE TABLE `shorturl`
(
`shorten` varchar(255) NOT NULL COMMENT 'shorten key',
`url` varchar(255) NOT NULL COMMENT 'original url',
PRIMARY KEY(`shorten`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
* create DB and table
```sql
create database gozero;
```
```sql
source shorturl.sql;
```
* under the directory `rpc/transform/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
```shell
goctl model mysql ddl -c -src shorturl.sql -dir .
```
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
the generated file structure looks like:
```
rpc/transform/model
├── shorturl.sql
├── shorturlmodel.go // CRUD+cache code
└── vars.go // const and var definition
```
## 9. Modify shorten/expand rpc to call crud+cache
* modify `rpc/transform/etc/transform.yaml`, add the following:
```yaml
DataSource: root:@tcp(localhost:3306)/gozero
Table: shorturl
Cache:
- Host: localhost:6379
```
you can use multiple redis as cache. redis node and cluster are both supported.
* modify `rpc/transform/internal/config.go`, like below:
```go
type Config struct {
rpcx.RpcServerConf
DataSource string // manual code
Table string // manual code
Cache cache.CacheConf // manual code
}
```
added the configuration for mysql and redis cache.
* modify `rpc/transform/internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
c config.Config
Model *model.ShorturlModel // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
}
}
```
* modify `rpc/transform/internal/logic/expandlogic.go`, like below:
```go
func (l *ExpandLogic) Expand(in *transform.ExpandReq) (*transform.ExpandResp, error) {
// manual code start
res, err := l.svcCtx.Model.FindOne(in.Shorten)
if err != nil {
return nil, err
}
return &transform.ExpandResp{
Url: res.Url,
}, nil
// manual code stop
}
```
* modify `rpc/shorten/internal/logic/shortenlogic.go`, looks like:
```go
func (l *ShortenLogic) Shorten(in *transform.ShortenReq) (*transform.ShortenResp, error) {
// manual code start, generates shorturl
key := hash.Md5Hex([]byte(in.Url))[:6]
_, err := l.svcCtx.Model.Insert(model.Shorturl{
Shorten: key,
Url: in.Url,
})
if err != nil {
return nil, err
}
return &transform.ShortenResp{
Shorten: key,
}, nil
// manual code stop
}
```
till now, we finished modifing the code, all the modified code is marked.
## 10. Call shorten and expand services
* call shorten api
```shell
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 29 Aug 2020 10:49:49 GMT
Content-Length: 21
{"shorten":"f35b2a"}
```
* call expand api
```shell
curl -i "http://localhost:8888/expand?shorten=f35b2a"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 29 Aug 2020 10:51:53 GMT
Content-Length: 34
{"url":"http://www.xiaoheiban.cn"}
```
## 11. Benchmark
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the expand api. We read the data from mysql and cache it in redis. I chose 100 hot keys hardcoded in shorten.lua to generate the benchmark.
![Benchmark](images/shorturl-benchmark.png)
as shown above, in my MacBook Pro, the QPS is like 30K+.
## 12. Full code
[https://github.com/tal-tech/go-zero/tree/master/example/shorturl](https://github.com/tal-tech/go-zero/tree/master/example/shorturl)
## 13. Conclusion
We always adhere to **prefer tools over conventions and documents**.
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

View File

@@ -1,3 +1,5 @@
[English](shorturl-en.md) | 简体中文
# 快速构建高并发微服务
## 0. 为什么说做好微服务很难?
@@ -61,7 +63,7 @@
* 安装goctl工具
```shell
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
```
* 创建工作目录`shorturl`
@@ -287,13 +289,13 @@
```go
type ServiceContext struct {
Config config.Config
Transformer rpcx.Client // 手动代码
Transformer transformer.Transformer // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Transformer: rpcx.MustNewClient(c.Transform), // 手动代码
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // 手动代码
}
}
```
@@ -305,8 +307,7 @@
```go
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
// 手动代码开始
trans := transformer.NewTransformer(l.svcCtx.Transformer)
resp, err := trans.Expand(l.ctx, &transformer.ExpandReq{
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
Shorten: req.Shorten,
})
if err != nil {
@@ -319,16 +320,16 @@
// 手动代码结束
}
```
通过调用`transformer`的`Expand`方法实现短链恢复到url
通过调用`transformer`的`Expand`方法实现短链恢复到url
* 修改`internal/logic/shortenlogic.go`,如下:
```go
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
// 手动代码开始
trans := transformer.NewTransformer(l.svcCtx.Transformer)
resp, err := trans.Shorten(l.ctx, &transformer.ShortenReq{
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
Url: req.Url,
})
if err != nil {
@@ -341,10 +342,11 @@
// 手动代码结束
}
```
通过调用`transformer`的`Shorten`方法实现url到短链的变换
通过调用`transformer`的`Shorten`方法实现url到短链的变换
至此API Gateway修改完成虽然贴的代码多但是期中修改的是很少的一部分为了方便理解上下文我贴了完整代码接下来处理CRUD+cache
至此API Gateway修改完成虽然贴的代码多但是期中修改的是很少的一部分为了方便理解上下文我贴了完整代码接下来处理CRUD+cache
## 8. 定义数据库表结构并生成CRUD+cache代码
@@ -433,7 +435,7 @@
* 修改`rpc/transform/internal/logic/expandlogic.go`,如下:
```go
func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) {
func (l *ExpandLogic) Expand(in *transform.ExpandReq) (*transform.ExpandResp, error) {
// 手动代码开始
res, err := l.svcCtx.Model.FindOne(in.Shorten)
if err != nil {
@@ -450,7 +452,7 @@
* 修改`rpc/shorten/internal/logic/shortenlogic.go`,如下:
```go
func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) {
func (l *ShortenLogic) Shorten(in *transform.ShortenReq) (*transform.ShortenResp, error) {
// 手动代码开始,生成短链接
key := hash.Md5Hex([]byte(in.Url))[:6]
_, err := l.svcCtx.Model.Insert(model.Shorturl{
@@ -514,6 +516,10 @@
可以看出在我的MacBook Pro上能达到3万+的qps。
## 12. 完整代码
[https://github.com/tal-tech/go-zero/tree/master/example/shorturl](https://github.com/tal-tech/go-zero/tree/master/example/shorturl)
## 12. 总结
我们一直强调**工具大于约定和文档**。

View File

@@ -0,0 +1,33 @@
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 {
@server(
handler: AddHandler
)
get /add(addReq) returns(addResp)
@server(
handler: CheckHandler
)
get /check(checkReq) returns(checkResp)
}

View File

@@ -0,0 +1,27 @@
package main
import (
"bookstore/api/internal/config"
"bookstore/api/internal/handler"
"bookstore/api/internal/svc"
"flag"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest"
)
var configFile = flag.String("f", "etc/bookstore-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
handler.RegisterHandlers(server, ctx)
server.Start()
}

View File

@@ -0,0 +1,13 @@
Name: bookstore-api
Host: 0.0.0.0
Port: 8888
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpc
Check:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc

View File

@@ -0,0 +1,12 @@
package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
)
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf
Check rpcx.RpcClientConf
}

View File

@@ -0,0 +1,28 @@
package handler
import (
"bookstore/api/internal/logic"
"bookstore/api/internal/svc"
"bookstore/api/internal/types"
"net/http"
"github.com/tal-tech/go-zero/rest/httpx"
)
func addHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AddReq
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewAddLogic(r.Context(), ctx)
resp, err := l.Add(req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.WriteJson(w, http.StatusOK, resp)
}
}
}

View File

@@ -0,0 +1,28 @@
package handler
import (
"bookstore/api/internal/logic"
"bookstore/api/internal/svc"
"bookstore/api/internal/types"
"net/http"
"github.com/tal-tech/go-zero/rest/httpx"
)
func checkHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CheckReq
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewCheckLogic(r.Context(), ctx)
resp, err := l.Check(req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.WriteJson(w, http.StatusOK, resp)
}
}
}

View File

@@ -0,0 +1,24 @@
// DO NOT EDIT, generated by goctl
package handler
import (
"bookstore/api/internal/svc"
"net/http"
"github.com/tal-tech/go-zero/rest"
)
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
engine.AddRoutes([]rest.Route{
{
Method: http.MethodGet,
Path: "/add",
Handler: addHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/check",
Handler: checkHandler(serverCtx),
},
})
}

View File

@@ -0,0 +1,38 @@
package logic
import (
"bookstore/api/internal/svc"
"bookstore/api/internal/types"
"bookstore/rpc/add/adder"
"context"
"github.com/tal-tech/go-zero/core/logx"
)
type AddLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) AddLogic {
return AddLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
Book: req.Book,
Price: req.Price,
})
if err != nil {
return nil, err
}
return &types.AddResp{
Ok: resp.Ok,
}, nil
}

View File

@@ -0,0 +1,38 @@
package logic
import (
"bookstore/api/internal/svc"
"bookstore/api/internal/types"
"bookstore/rpc/check/checker"
"context"
"github.com/tal-tech/go-zero/core/logx"
)
type CheckLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) CheckLogic {
return CheckLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
Book: req.Book,
})
if err != nil {
return nil, err
}
return &types.CheckResp{
Found: resp.Found,
Price: resp.Price,
}, nil
}

View File

@@ -0,0 +1,23 @@
package svc
import (
"bookstore/api/internal/config"
"bookstore/rpc/add/adder"
"bookstore/rpc/check/checker"
"github.com/tal-tech/go-zero/rpcx"
)
type ServiceContext struct {
Config config.Config
Adder adder.Adder
Checker checker.Checker
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)),
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)),
}
}

View File

@@ -0,0 +1,20 @@
// DO NOT EDIT, generated by goctl
package types
type AddReq struct {
Book string `form:"book"`
Price int64 `form:"price"`
}
type AddResp struct {
Ok bool `json:"ok"`
}
type CheckReq struct {
Book string `form:"book"`
}
type CheckResp struct {
Found bool `json:"found"`
Price int64 `json:"price"`
}

11
example/bookstore/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module bookstore
go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.11
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

356
example/bookstore/go.sum Normal file
View File

@@ -0,0 +1,356 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gops v0.3.7/go.mod h1:bj0cwMmX1X4XIJFTjR99R5sCxNssNJ8HebFNvoQlmgY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.11 h1:vy5jfTVZZdlrycZS9PVrveKEytz4e5hReT7yuh4Oa6o=
github.com/tal-tech/go-zero v1.0.11/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 h1:jWtjCJX1qxhHISBMLRztWwR+EXkI7MJAF2HjHAE/x/I=
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f h1:ohwtWcCwB/fZUxh/vjazHorYmBnua3NmY3CAjwC7mEA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -0,0 +1,39 @@
// Code generated by goctl. DO NOT EDIT!
// Source: add.proto
package main
import (
"bookstore/rpc/add/internal/config"
"bookstore/rpc/add/internal/server"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"flag"
"fmt"
"log"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rpcx"
"google.golang.org/grpc"
)
var configFile = flag.String("f", "etc/add.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
adderSrv := server.NewAdderServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
add.RegisterAdderServer(grpcServer, adderSrv)
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package add;
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}

View File

@@ -0,0 +1,62 @@
// Code generated by goctl. DO NOT EDIT!
// Source: add.proto
//go:generate mockgen -destination ./adder_mock.go -package adder -source $GOFILE
package adder
import (
add "bookstore/rpc/add/pb"
"context"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
)
type (
Adder interface {
Add(ctx context.Context, in *AddReq) (*AddResp, error)
}
defaultAdder struct {
cli rpcx.Client
}
)
func NewAdder(cli rpcx.Client) Adder {
return &defaultAdder{
cli: cli,
}
}
func (m *defaultAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
var request add.AddReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := add.NewAdderClient(m.cli.Conn())
resp, err := client.Add(ctx, &request)
if err != nil {
return nil, err
}
var ret AddResp
bts, err = jsonx.Marshal(resp)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &ret)
if err != nil {
return nil, errJsonConvert
}
return &ret, nil
}

View File

@@ -0,0 +1,49 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: adder.go
// Package adder is a generated GoMock package.
package adder
import (
context "context"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockAdder is a mock of Adder interface
type MockAdder struct {
ctrl *gomock.Controller
recorder *MockAdderMockRecorder
}
// MockAdderMockRecorder is the mock recorder for MockAdder
type MockAdderMockRecorder struct {
mock *MockAdder
}
// NewMockAdder creates a new mock instance
func NewMockAdder(ctrl *gomock.Controller) *MockAdder {
mock := &MockAdder{ctrl: ctrl}
mock.recorder = &MockAdderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAdder) EXPECT() *MockAdderMockRecorder {
return m.recorder
}
// Add mocks base method
func (m *MockAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", ctx, in)
ret0, _ := ret[0].(*AddResp)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Add indicates an expected call of Add
func (mr *MockAdderMockRecorder) Add(ctx, in interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockAdder)(nil).Add), ctx, in)
}

View File

@@ -0,0 +1,19 @@
// Code generated by goctl. DO NOT EDIT!
// Source: add.proto
package adder
import "errors"
var errJsonConvert = errors.New("json convert error")
type (
AddReq struct {
Book string `json:"book,omitempty"`
Price int64 `json:"price,omitempty"`
}
AddResp struct {
Ok bool `json:"ok,omitempty"`
}
)

View File

@@ -0,0 +1,10 @@
Name: add.rpc
ListenOn: 127.0.0.1:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: add.rpc
DataSource: root:@tcp(localhost:3306)/gozero
Table: book
Cache:
- Host: localhost:6379

View File

@@ -0,0 +1,13 @@
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
)
type Config struct {
rpcx.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf
}

View File

@@ -0,0 +1,38 @@
package logic
import (
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"bookstore/rpc/model"
"context"
"github.com/tal-tech/go-zero/core/logx"
)
type AddLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddLogic {
return &AddLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
_, err := l.svcCtx.Model.Insert(model.Book{
Book: in.Book,
Price: in.Price,
})
if err != nil {
return nil, err
}
return &add.AddResp{
Ok: true,
}, nil
}

View File

@@ -0,0 +1,26 @@
// Code generated by goctl. DO NOT EDIT!
// Source: add.proto
package server
import (
"bookstore/rpc/add/internal/logic"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"context"
)
type AdderServer struct {
svcCtx *svc.ServiceContext
}
func NewAdderServer(svcCtx *svc.ServiceContext) *AdderServer {
return &AdderServer{
svcCtx: svcCtx,
}
}
func (s *AdderServer) Add(ctx context.Context, in *add.AddReq) (*add.AddResp, error) {
l := logic.NewAddLogic(ctx, s.svcCtx)
return l.Add(in)
}

View File

@@ -0,0 +1,20 @@
package svc
import (
"bookstore/rpc/add/internal/config"
"bookstore/rpc/model"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
type ServiceContext struct {
c config.Config
Model *model.BookModel
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table),
}
}

View File

@@ -0,0 +1,167 @@
// Code generated by protoc-gen-go.
// source: add.proto
// DO NOT EDIT!
/*
Package add is a generated protocol buffer package.
It is generated from these files:
add.proto
It has these top-level messages:
AddReq
AddResp
*/
package add
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type AddReq struct {
Book string `protobuf:"bytes,1,opt,name=book" json:"book,omitempty"`
Price int64 `protobuf:"varint,2,opt,name=price" json:"price,omitempty"`
}
func (m *AddReq) Reset() { *m = AddReq{} }
func (m *AddReq) String() string { return proto.CompactTextString(m) }
func (*AddReq) ProtoMessage() {}
func (*AddReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *AddReq) GetBook() string {
if m != nil {
return m.Book
}
return ""
}
func (m *AddReq) GetPrice() int64 {
if m != nil {
return m.Price
}
return 0
}
type AddResp struct {
Ok bool `protobuf:"varint,1,opt,name=ok" json:"ok,omitempty"`
}
func (m *AddResp) Reset() { *m = AddResp{} }
func (m *AddResp) String() string { return proto.CompactTextString(m) }
func (*AddResp) ProtoMessage() {}
func (*AddResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *AddResp) GetOk() bool {
if m != nil {
return m.Ok
}
return false
}
func init() {
proto.RegisterType((*AddReq)(nil), "add.addReq")
proto.RegisterType((*AddResp)(nil), "add.addResp")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Adder service
type AdderClient interface {
Add(ctx context.Context, in *AddReq, opts ...grpc.CallOption) (*AddResp, error)
}
type adderClient struct {
cc *grpc.ClientConn
}
func NewAdderClient(cc *grpc.ClientConn) AdderClient {
return &adderClient{cc}
}
func (c *adderClient) Add(ctx context.Context, in *AddReq, opts ...grpc.CallOption) (*AddResp, error) {
out := new(AddResp)
err := grpc.Invoke(ctx, "/add.adder/add", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Adder service
type AdderServer interface {
Add(context.Context, *AddReq) (*AddResp, error)
}
func RegisterAdderServer(s *grpc.Server, srv AdderServer) {
s.RegisterService(&_Adder_serviceDesc, srv)
}
func _Adder_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AdderServer).Add(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/add.adder/Add",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AdderServer).Add(ctx, req.(*AddReq))
}
return interceptor(ctx, in, info, handler)
}
var _Adder_serviceDesc = grpc.ServiceDesc{
ServiceName: "add.adder",
HandlerType: (*AdderServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "add",
Handler: _Adder_Add_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "add.proto",
}
func init() { proto.RegisterFile("add.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 136 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x4c, 0x49, 0xd1,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0x4c, 0x49, 0x51, 0x32, 0xe2, 0x62, 0x4b, 0x4c,
0x49, 0x09, 0x4a, 0x2d, 0x14, 0x12, 0xe2, 0x62, 0x49, 0xca, 0xcf, 0xcf, 0x96, 0x60, 0x54, 0x60,
0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x44, 0xb8, 0x58, 0x0b, 0x8a, 0x32, 0x93, 0x53, 0x25, 0x98,
0x14, 0x18, 0x35, 0x98, 0x83, 0x20, 0x1c, 0x25, 0x49, 0x2e, 0x76, 0xb0, 0x9e, 0xe2, 0x02, 0x21,
0x3e, 0x2e, 0x26, 0xa8, 0x16, 0x8e, 0x20, 0xa6, 0xfc, 0x6c, 0x23, 0x4d, 0x2e, 0xd6, 0xc4, 0x94,
0x94, 0xd4, 0x22, 0x21, 0x05, 0x2e, 0x90, 0xf1, 0x42, 0xdc, 0x7a, 0x20, 0xfb, 0x20, 0x36, 0x48,
0xf1, 0x20, 0x38, 0xc5, 0x05, 0x49, 0x6c, 0x60, 0x57, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff,
0xe2, 0x6d, 0xb5, 0x91, 0x92, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,39 @@
// Code generated by goctl. DO NOT EDIT!
// Source: check.proto
package main
import (
"bookstore/rpc/check/internal/config"
"bookstore/rpc/check/internal/server"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"flag"
"fmt"
"log"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rpcx"
"google.golang.org/grpc"
)
var configFile = flag.String("f", "etc/check.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
checkerSrv := server.NewCheckerServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
check.RegisterCheckerServer(grpcServer, checkerSrv)
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package check;
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}

View File

@@ -0,0 +1,62 @@
// Code generated by goctl. DO NOT EDIT!
// Source: check.proto
//go:generate mockgen -destination ./checker_mock.go -package checker -source $GOFILE
package checker
import (
check "bookstore/rpc/check/pb"
"context"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
)
type (
Checker interface {
Check(ctx context.Context, in *CheckReq) (*CheckResp, error)
}
defaultChecker struct {
cli rpcx.Client
}
)
func NewChecker(cli rpcx.Client) Checker {
return &defaultChecker{
cli: cli,
}
}
func (m *defaultChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
var request check.CheckReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := check.NewCheckerClient(m.cli.Conn())
resp, err := client.Check(ctx, &request)
if err != nil {
return nil, err
}
var ret CheckResp
bts, err = jsonx.Marshal(resp)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &ret)
if err != nil {
return nil, errJsonConvert
}
return &ret, nil
}

View File

@@ -0,0 +1,49 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: checker.go
// Package checker is a generated GoMock package.
package checker
import (
context "context"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockChecker is a mock of Checker interface
type MockChecker struct {
ctrl *gomock.Controller
recorder *MockCheckerMockRecorder
}
// MockCheckerMockRecorder is the mock recorder for MockChecker
type MockCheckerMockRecorder struct {
mock *MockChecker
}
// NewMockChecker creates a new mock instance
func NewMockChecker(ctrl *gomock.Controller) *MockChecker {
mock := &MockChecker{ctrl: ctrl}
mock.recorder = &MockCheckerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockChecker) EXPECT() *MockCheckerMockRecorder {
return m.recorder
}
// Check mocks base method
func (m *MockChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Check", ctx, in)
ret0, _ := ret[0].(*CheckResp)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Check indicates an expected call of Check
func (mr *MockCheckerMockRecorder) Check(ctx, in interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockChecker)(nil).Check), ctx, in)
}

View File

@@ -0,0 +1,19 @@
// Code generated by goctl. DO NOT EDIT!
// Source: check.proto
package checker
import "errors"
var errJsonConvert = errors.New("json convert error")
type (
CheckReq struct {
Book string `json:"book,omitempty"`
}
CheckResp struct {
Found bool `json:"found,omitempty"`
Price int64 `json:"price,omitempty"`
}
)

View File

@@ -0,0 +1,10 @@
Name: check.rpc
ListenOn: 127.0.0.1:8081
Etcd:
Hosts:
- 127.0.0.1:2379
Key: check.rpc
DataSource: root:@tcp(localhost:3306)/gozero
Table: book
Cache:
- Host: localhost:6379

View File

@@ -0,0 +1,13 @@
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
)
type Config struct {
rpcx.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf
}

View File

@@ -0,0 +1,35 @@
package logic
import (
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"context"
"github.com/tal-tech/go-zero/core/logx"
)
type CheckLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckLogic {
return &CheckLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
resp, err := l.svcCtx.Model.FindOne(in.Book)
if err != nil {
return nil, err
}
return &check.CheckResp{
Found: true,
Price: resp.Price,
}, nil
}

View File

@@ -0,0 +1,26 @@
// Code generated by goctl. DO NOT EDIT!
// Source: check.proto
package server
import (
"bookstore/rpc/check/internal/logic"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"context"
)
type CheckerServer struct {
svcCtx *svc.ServiceContext
}
func NewCheckerServer(svcCtx *svc.ServiceContext) *CheckerServer {
return &CheckerServer{
svcCtx: svcCtx,
}
}
func (s *CheckerServer) Check(ctx context.Context, in *check.CheckReq) (*check.CheckResp, error) {
l := logic.NewCheckLogic(ctx, s.svcCtx)
return l.Check(in)
}

View File

@@ -0,0 +1,20 @@
package svc
import (
"bookstore/rpc/check/internal/config"
"bookstore/rpc/model"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
type ServiceContext struct {
c config.Config
Model *model.BookModel
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table),
}
}

View File

@@ -0,0 +1,167 @@
// Code generated by protoc-gen-go.
// source: check.proto
// DO NOT EDIT!
/*
Package check is a generated protocol buffer package.
It is generated from these files:
check.proto
It has these top-level messages:
CheckReq
CheckResp
*/
package check
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type CheckReq struct {
Book string `protobuf:"bytes,1,opt,name=book" json:"book,omitempty"`
}
func (m *CheckReq) Reset() { *m = CheckReq{} }
func (m *CheckReq) String() string { return proto.CompactTextString(m) }
func (*CheckReq) ProtoMessage() {}
func (*CheckReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *CheckReq) GetBook() string {
if m != nil {
return m.Book
}
return ""
}
type CheckResp struct {
Found bool `protobuf:"varint,1,opt,name=found" json:"found,omitempty"`
Price int64 `protobuf:"varint,2,opt,name=price" json:"price,omitempty"`
}
func (m *CheckResp) Reset() { *m = CheckResp{} }
func (m *CheckResp) String() string { return proto.CompactTextString(m) }
func (*CheckResp) ProtoMessage() {}
func (*CheckResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *CheckResp) GetFound() bool {
if m != nil {
return m.Found
}
return false
}
func (m *CheckResp) GetPrice() int64 {
if m != nil {
return m.Price
}
return 0
}
func init() {
proto.RegisterType((*CheckReq)(nil), "check.checkReq")
proto.RegisterType((*CheckResp)(nil), "check.checkResp")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Checker service
type CheckerClient interface {
Check(ctx context.Context, in *CheckReq, opts ...grpc.CallOption) (*CheckResp, error)
}
type checkerClient struct {
cc *grpc.ClientConn
}
func NewCheckerClient(cc *grpc.ClientConn) CheckerClient {
return &checkerClient{cc}
}
func (c *checkerClient) Check(ctx context.Context, in *CheckReq, opts ...grpc.CallOption) (*CheckResp, error) {
out := new(CheckResp)
err := grpc.Invoke(ctx, "/check.checker/check", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Checker service
type CheckerServer interface {
Check(context.Context, *CheckReq) (*CheckResp, error)
}
func RegisterCheckerServer(s *grpc.Server, srv CheckerServer) {
s.RegisterService(&_Checker_serviceDesc, srv)
}
func _Checker_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CheckerServer).Check(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/check.checker/Check",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CheckerServer).Check(ctx, req.(*CheckReq))
}
return interceptor(ctx, in, info, handler)
}
var _Checker_serviceDesc = grpc.ServiceDesc{
ServiceName: "check.checker",
HandlerType: (*CheckerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "check",
Handler: _Checker_Check_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "check.proto",
}
func init() { proto.RegisterFile("check.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 136 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0xce, 0x48, 0x4d,
0xce, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0xe4, 0xb8, 0x38, 0xc0,
0x8c, 0xa0, 0xd4, 0x42, 0x21, 0x21, 0x2e, 0x96, 0xa4, 0xfc, 0xfc, 0x6c, 0x09, 0x46, 0x05, 0x46,
0x0d, 0xce, 0x20, 0x30, 0x5b, 0xc9, 0x9c, 0x8b, 0x13, 0x2a, 0x5f, 0x5c, 0x20, 0x24, 0xc2, 0xc5,
0x9a, 0x96, 0x5f, 0x9a, 0x97, 0x02, 0x56, 0xc1, 0x11, 0x04, 0xe1, 0x80, 0x44, 0x0b, 0x8a, 0x32,
0x93, 0x53, 0x25, 0x98, 0x14, 0x18, 0x35, 0x98, 0x83, 0x20, 0x1c, 0x23, 0x53, 0x2e, 0x76, 0xb0,
0xc6, 0xd4, 0x22, 0x21, 0x2d, 0x2e, 0x88, 0x65, 0x42, 0xfc, 0x7a, 0x10, 0x17, 0xc0, 0x6c, 0x94,
0x12, 0x40, 0x15, 0x28, 0x2e, 0x48, 0x62, 0x03, 0xbb, 0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff,
0x6e, 0x6f, 0xa7, 0x1d, 0xac, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,6 @@
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;

View File

@@ -0,0 +1,82 @@
package model
import (
"database/sql"
"fmt"
"strings"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
var (
bookFieldNames = builderx.FieldNames(&Book{})
bookRows = strings.Join(bookFieldNames, ",")
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
cacheBookBookPrefix = "cache#Book#book#"
)
type (
BookModel struct {
sqlc.CachedConn
table string
}
Book struct {
Book string `db:"book"` // book name
Price int64 `db:"price"` // book price
}
)
func NewBookModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *BookModel {
return &BookModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
}
}
func (m *BookModel) Insert(data Book) (sql.Result, error) {
query := `insert into ` + m.table + ` (` + bookRowsExpectAutoSet + `) values (?, ?)`
return m.ExecNoCache(query, data.Book, data.Price)
}
func (m *BookModel) FindOne(book string) (*Book, error) {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
var resp Book
err := m.QueryRow(&resp, bookBookKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + bookRows + ` from ` + m.table + ` where book = ? limit 1`
return conn.QueryRow(v, query, book)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *BookModel) Update(data Book) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, data.Book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
return conn.Exec(query, data.Price, data.Book)
}, bookBookKey)
return err
}
func (m *BookModel) Delete(book string) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where book = ?`
return conn.Exec(query, book)
}, bookBookKey)
return err
}

View File

@@ -0,0 +1,5 @@
package model
import "github.com/tal-tech/go-zero/core/stores/sqlx"
var ErrNotFound = sqlx.ErrNotFound

View File

@@ -0,0 +1,40 @@
package main
import (
"log"
"strconv"
"github.com/tal-tech/go-zero/core/mr"
)
type User struct {
Uid int
Name string
}
func main() {
uids := []int{111, 222, 333}
res, err := mr.MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
source <- uid
}
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
uid := item.(int)
user := &User{
Uid: uid,
Name: strconv.Itoa(uid),
}
writer.Write(user)
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
var users []*User
for p := range pipe {
users = append(users, p.(*User))
}
// missing writer.Write(...), should not panic
})
if err != nil {
log.Print(err)
return
}
log.Print(len(res.([]*User)))
}

114
example/mapreduce/mr/mr.go Normal file
View File

@@ -0,0 +1,114 @@
package main
import (
"log"
"time"
"github.com/tal-tech/go-zero/core/mr"
"github.com/tal-tech/go-zero/core/timex"
)
type user struct{}
func (u *user) User(uid int64) (interface{}, error) {
time.Sleep(time.Millisecond * 30)
return nil, nil
}
type store struct{}
func (s *store) Store(pid int64) (interface{}, error) {
time.Sleep(time.Millisecond * 50)
return nil, nil
}
type order struct{}
func (o *order) Order(pid int64) (interface{}, error) {
time.Sleep(time.Millisecond * 40)
return nil, nil
}
var (
userRpc user
storeRpc store
orderRpc order
)
func main() {
start := timex.Now()
_, err := productDetail(123, 345)
if err != nil {
log.Printf("product detail error: %v", err)
return
}
log.Printf("productDetail time: %v", timex.Since(start))
// the data processing
res, err := checkLegal([]int64{1, 2, 3})
if err != nil {
log.Printf("check error: %v", err)
return
}
log.Printf("check res: %v", res)
}
type ProductDetail struct {
User interface{}
Store interface{}
Order interface{}
}
func productDetail(uid, pid int64) (*ProductDetail, error) {
var pd ProductDetail
err := mr.Finish(func() (err error) {
pd.User, err = userRpc.User(uid)
return
}, func() (err error) {
pd.Store, err = storeRpc.Store(pid)
return
}, func() (err error) {
pd.Order, err = orderRpc.Order(pid)
return
})
if err != nil {
return nil, err
}
return &pd, nil
}
func checkLegal(uids []int64) ([]int64, error) {
r, err := mr.MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
source <- uid
}
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
uid := item.(int64)
ok, err := check(uid)
if err != nil {
cancel(err)
}
if ok {
writer.Write(uid)
}
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
var uids []int64
for p := range pipe {
uids = append(uids, p.(int64))
}
writer.Write(uids)
})
if err != nil {
return nil, err
}
return r.([]int64), nil
}
func check(uid int64) (bool, error) {
// do something check user legal
time.Sleep(time.Millisecond * 20)
return true, nil
}

View File

@@ -1,3 +1,8 @@
Name: shorturl-api
Host: 0.0.0.0
Port: 8888
Transform:
Etcd:
Hosts:
- localhost:2379
Key: transform.rpc

View File

@@ -1,7 +1,11 @@
package config
import "github.com/tal-tech/go-zero/rest"
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
)
type Config struct {
rest.RestConf
Transform rpcx.RpcClientConf
}

View File

@@ -5,6 +5,7 @@ import (
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"shorturl/rpc/transform/transformer"
"github.com/tal-tech/go-zero/core/logx"
)
@@ -24,5 +25,14 @@ func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic
}
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
return &types.ExpandResp{}, nil
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
Shorten: req.Shorten,
})
if err != nil {
return nil, err
}
return &types.ExpandResp{
Url: resp.Url,
}, nil
}

View File

@@ -5,6 +5,7 @@ import (
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"shorturl/rpc/transform/transformer"
"github.com/tal-tech/go-zero/core/logx"
)
@@ -24,5 +25,14 @@ func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLog
}
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
return &types.ShortenResp{}, nil
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
Url: req.Url,
})
if err != nil {
return nil, err
}
return &types.ShortenResp{
Shorten: resp.Shorten,
}, nil
}

View File

@@ -1,11 +1,20 @@
package svc
import "shorturl/api/internal/config"
import (
"shorturl/api/internal/config"
"shorturl/rpc/transform/transformer"
"github.com/tal-tech/go-zero/rpcx"
)
type ServiceContext struct {
Config config.Config
Config config.Config
Transformer transformer.Transformer
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{Config: c}
return &ServiceContext{
Config: c,
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)),
}
}

206
readme-en.md Normal file
View File

@@ -0,0 +1,206 @@
English | [简体中文](readme.md)
# go-zero
[![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero)
[![Release](https://img.shields.io/github/v/release/tal-tech/go-zero.svg?style=flat-square)](https://github.com/tal-tech/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## 0. what is go-zero?
go-zero is a web and rpc framework that with lots of engineering practices builtin. Its born to ensure the stability of the busy services with resilience design, and has been serving sites with tens of millions users for years.
go-zero contains simple API description syntax and code generation tool called `goctl`. You can generate Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript from .api files with `goctl`.
Advantages of go-zero:
* improve the stability of the services with tens of millions of daily active users
* builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
* builtin middlewares also can be integrated into your frameworks
* simple API syntax, one command to generate couple different languages
* auto validate the request parameters from clients
* plenty of builtin microservice management and concurrent toolkits
<img src="doc/images/architecture-en.png" alt="Architecture" width="1500" />
## 1. Backgrounds of go-zero
At the beginning of 2018, we heavily suffered from frequent downtime. We decided to re-design our system, from monolithic architecture with Java+MongoDB to microservice architecture. After researches and comparison, we chose to:
* Golang based
* great performance
* simple syntax
* proven engineering efficiency
* extreme deployment experience
* less server resource consumption
* Self-designed microservice architecture
* I have rich experience on designing microservice architectures
* easy to location the problems
* easy to extend the features
## 2. Design considerations on go-zero
By designing the microservice architecture, we expected to ensure the stability, as well as the productivity. And from just the beginning, we have the following design principles:
* keep it simple
* high availability
* stable on high concurrency
* easy to extend
* resilience design, failure-oriented programming
* try best to be friendly to the business logic development, encapsulate the complexity
* one thing, one way
After almost half a year, we finished the transfer from monolithic system to microservice system, and deployed on August 2018. The new system guaranteed the business growth, and the system stability.
## 3. The implementation and features of go-zero
go-zero is a web and rpc framework that integrates lots of engineering practices. The features are mainly listed below:
* powerful tool included, less code to write
* simple interfaces
* fully compatible with net/http
* middlewares are supported, easy to extend
* high performance
* failure-oriented programming, resilience design
* builtin service discovery, load balancing
* builtin concurrency control, adaptive circuit breaker, adaptive load shedding, auto trigger, auto recover
* auto validation of API request parameters
* chained timeout control
* auto management of data caching
* call tracing, metrics and monitoring
* high concurrency protected
As below, go-zero protects the system with couple layers and mechanisms:
![Resilience](doc/images/resilience-en.png)
## 4. Future development plans of go-zero
* auto generate API mock server, make the client debugging eaisier
* auto generate the simple integration test for the server side just from the .api files
## 5. Installation
Run the following command under your project:
```shell
go get -u github.com/tal-tech/go-zero
```
## 6. Quick Start
0. full examples can be checked out from below:
[Rapid development of microservice systems](doc/shorturl-en.md)
[Rapid development of microservice systems - multiple RPCs](doc/bookstore-en.md)
1. install goctl
`goctl`can be read as `go control`. `goctl means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier.
```shell
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
```
make sure goctl is executable.
2. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
```go
type Request struct {
Name string `path:"name,options=you|me"` // parameters are auto validated
}
type Response struct {
Message string `json:"message"`
}
service greet-api {
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response);
}
```
the .api files also can be generate by goctl, like below:
```shell
goctl api -o greet.api
```
3. generate the go server side code
```shell
goctl api go -api greet.api -dir greet
```
the generated files look like:
```
├── greet
│   ├── etc
│   │   └── greet-api.yaml // configuration file
│   ├── greet.go // main file
│   └── internal
│   ├── config
│   │   └── config.go // configuration definition
│   ├── handler
│   │   ├── greethandler.go // get/put/post/delete routes are defined here
│   │   └── routes.go // routes list
│   ├── logic
│   │   └── greetlogic.go // request logic can be written here
│   ├── svc
│   │   └── servicecontext.go // service context, mysql/redis can be passed in here
│   └── types
│   └── types.go // request/response defined here
└── greet.api // api description file
```
the generated code can be run directly:
```shell
cd greet
go run greet.go -f etc/greet-api.yaml
```
by default, its listening on port 8888, while it can be changed in configuration file.
you can check it by curl:
```shell
curl -i http://localhost:8888/greet/from/you
```
the response looks like:
```http
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2020 15:32:35 GMT
Content-Length: 0
```
4. Write the business logic code
* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc.
* add the logic code in logic package according to .api file
5. Generate code like Java, TypeScript, Dart, JavaScript etc. just from the api file
```shell
goctl api java -api greet.api -dir greet
goctl api dart -api greet.api -dir greet
...
```
## 7. Benchmark
![benchmark](doc/images/benchmark.png)
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
## 8. Documents (adding)
* [Rapid development of microservice systems](doc/shorturl-en.md)
* [Rapid development of microservice systems - multiple RPCs](doc/bookstore-en.md)

View File

@@ -1,3 +1,5 @@
[English](readme-en.md) | 简体中文
# go-zero
[![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
@@ -91,12 +93,16 @@ go get -u github.com/tal-tech/go-zero
0. 完整示例请查看
[快速构建高并发微服务](doc/shorturl.md)
[快速构建高并发微服务](doc/shorturl.md)
[快速构建高并发微服务-多RPC版](doc/bookstore.md)
1. 安装goctl工具
`goctl`读作`go control`,不要读成`go C-T-L``goctl`的意思是不要被代码控制,而是要去控制它。其中的`go`不是指`golang`。在设计`goctl`之初,我就希望通过`她`来解放我们的双手👈
```shell
export GO111MODULE=on export GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
```
确保goctl可执行
@@ -196,9 +202,11 @@ Content-Length: 0
## 8. 文档 (逐步完善中)
* [快速构建高并发微服务](doc/shorturl.md)
* [快速构建高并发微服务-多RPC版](doc/bookstore.md)
* [goctl使用帮助](doc/goctl.md)
* [通过MapReduce降低服务响应时间](doc/mapreduce.md)
* [关键字替换和敏感词过滤工具](doc/keywords.md)
## 9. 微信交流群
添加我的微信kevwan请注明go-zero我拉进go-zero社区群🤝
<img src="doc/images/wechat.jpg" alt="wechat" width="300" />

View File

@@ -4,8 +4,6 @@ import (
"fmt"
goformat "go/format"
"io"
"os"
"path"
"path/filepath"
"strings"
@@ -13,32 +11,27 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
goctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/project"
)
func getParentPackage(dir string) (string, error) {
absDir, err := filepath.Abs(dir)
p, err := project.Prepare(dir, false)
if err != nil {
return "", err
}
absDir = strings.ReplaceAll(absDir, `\`, `/`)
rootPath, hasGoMod := goctlutil.FindGoModPath(dir)
if hasGoMod {
return rootPath, nil
if len(p.GoMod.Path) > 0 {
goModePath := filepath.Clean(filepath.Dir(p.GoMod.Path))
absPath, err := filepath.Abs(dir)
if err != nil {
return "", err
}
parent := filepath.Clean(goctlutil.JoinPackages(p.GoMod.Module, absPath[len(goModePath):]))
parent = strings.ReplaceAll(parent, "\\", "/")
return parent, nil
}
gopath := os.Getenv("GOPATH")
parent := path.Join(gopath, "src")
pos := strings.Index(absDir, parent)
if pos < 0 {
fmt.Printf("%s not in go.mod project path, or not in GOPATH of %s directory\n", absDir, gopath)
tempPath := filepath.Dir(absDir)
rootPath = absDir[len(tempPath)+1:]
} else {
rootPath = absDir[len(parent)+1:]
}
return rootPath, nil
return p.Package, nil
}
func writeIndent(writer io.Writer, indent int) {

View File

@@ -3,7 +3,7 @@
## goctl用途
* 定义api请求
* 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter)
* 生成MySQL CURD 详情见[goctl model模块](https://github.com/tal-tech/go-zero/tools/goctl/model)
* 生成MySQL CURD 详情见[goctl model模块](model/sql)
## goctl使用说明
#### goctl参数说明

View File

@@ -9,6 +9,7 @@ import (
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/project"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/urfave/cli"
)
@@ -34,7 +35,7 @@ type RpcContext struct {
func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
log := console.NewConsole(idea)
info, err := prepare(log)
info, err := project.Prepare(targetDir, true)
log.Must(err)
if stringx.From(protoSrc).IsEmptyOrSpace() {

View File

@@ -8,7 +8,7 @@ import (
"runtime"
)
func Run(arg string) (string, error) {
func Run(arg string, dir string) (string, error) {
goos := runtime.GOOS
var cmd *exec.Cmd
switch goos {
@@ -19,7 +19,9 @@ func Run(arg string) (string, error) {
default:
return "", fmt.Errorf("unexpected os: %v", goos)
}
if len(dir) > 0 {
cmd.Dir = dir
}
dtsout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd.Stdout = dtsout

View File

@@ -169,7 +169,7 @@ func (g *defaultRpcGenerator) genCall() error {
// if mockgen is already installed, it will generate code of gomock for shared files
_, err = exec.LookPath("mockgen")
if mockGenInstalled {
execx.Run(fmt.Sprintf("go generate %s", filename))
execx.Run(fmt.Sprintf("go generate %s", filename), "")
}
return nil

View File

@@ -74,7 +74,7 @@ func (g *defaultRpcGenerator) genPb() error {
func (g *defaultRpcGenerator) protocGenGo(target string) error {
src := filepath.Dir(g.Ctx.ProtoFileSrc)
sh := fmt.Sprintf(`protoc -I=%s --go_out=plugins=grpc:%s %s`, src, target, g.Ctx.ProtoFileSrc)
stdout, err := execx.Run(sh)
stdout, err := execx.Run(sh, "")
if err != nil {
return err
}

View File

@@ -1,16 +1,13 @@
package ctx
package project
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
)
const (
@@ -23,45 +20,50 @@ const (
type (
Project struct {
Path string
Name string
GoMod GoMod
Path string // Project path name
Name string // Project name
Package string // The service related package
GoMod GoMod
}
GoMod struct {
Module string
Module string // The gomod module name
Path string // The gomod related path
}
)
func prepare(log console.Console) (*Project, error) {
log.Info("checking go env...")
func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
_, err := exec.LookPath(constGo)
if err != nil {
return nil, err
}
_, err = exec.LookPath(constProtoC)
if err != nil {
return nil, err
}
_, err = exec.LookPath(constProtoCGenGo)
if err != nil {
return nil, err
if checkGrpcEnv {
_, err = exec.LookPath(constProtoC)
if err != nil {
return nil, err
}
_, err = exec.LookPath(constProtoCGenGo)
if err != nil {
return nil, err
}
}
var (
goMod, module string
goPath string
name, path string
pkg string
)
ret, err := execx.Run(constGoMod)
ret, err := execx.Run(constGoMod, projectDir)
if err != nil {
return nil, err
}
goMod = strings.TrimSpace(ret)
ret, err = execx.Run(constGoPath)
ret, err = execx.Run(constGoPath, "")
if err != nil {
return nil, err
}
@@ -81,28 +83,40 @@ func prepare(log console.Console) (*Project, error) {
return nil, err
}
} else {
pwd, err := os.Getwd()
pwd, err := execx.Run("pwd", projectDir)
if err != nil {
return nil, err
}
pwd = filepath.Clean(strings.TrimSpace(pwd))
if !strings.HasPrefix(pwd, src) {
return nil, fmt.Errorf("%s: project is not in go mod and go path", pwd)
absPath, err := filepath.Abs(projectDir)
if err != nil {
return nil, err
}
name = filepath.Clean(filepath.Base(absPath))
path = projectDir
pkg = name
} else {
r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
name = filepath.Dir(r)
if name == "." {
name = r
}
path = filepath.Join(src, name)
pkg = r
}
r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
name = filepath.Dir(r)
if name == "." {
name = r
}
path = filepath.Join(src, name)
module = name
}
return &Project{
Name: name,
Path: path,
Name: name,
Path: path,
Package: pkg,
GoMod: GoMod{
Module: module,
Path: goMod,
},
}, nil
}