要想作好微服务,咱们须要理解和掌握的知识点很是多,从几个维度上来讲:mysql
基本功能层面git
高阶功能层面github
对于其中每一点,咱们都须要用很长的篇幅来说述其原理和实现,那么对咱们后端开发者来讲,要想把这些知识点都掌握并落实到业务系统里,难度是很是大的,不过咱们能够依赖已经被大流量验证过的框架体系。go-zero微服务框架就是为此而生。redis
另外,咱们始终秉承工具大于约定和文档的理念。咱们但愿尽量减小开发人员的心智负担,把精力都投入到产生业务价值的代码上,减小重复代码的编写,因此咱们开发了goctl
工具。sql
下面我经过书店服务来演示经过go-zero快速的建立微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!shell
为了教程简单,咱们用书店服务作示例,而且只实现其中的增长书目和检查价格功能。数据库
写此书店服务是为了从总体上演示go-zero构建完整微服务的过程,实现细节尽量简化了。json
全部绿色背景的功能模块是自动生成的,按需激活,红色模块是须要本身写的,也就是增长下依赖,编写业务特有逻辑,各层示意图分别以下:后端
下面咱们来一块儿完整走一遍快速构建微服务的流程,Let’s Go
!🏃♂️api
安装etcd, mysql, redis
安装goctl工具
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
在bookstore/api
目录下经过goctl生成api/bookstore.api
:
goctl api -o bookstore.api
编辑bookstore.api
,为了简洁,去除了文件开头的info
,代码以下:
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代码
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端口
go run bookstore.go -f etc/bookstore-api.yaml
测试API Gateway服务
curl -i "http://localhost:8888/check?book=go-zero"
返回以下:
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生成客户端代码给客户端同窗并行开发了,支持多种语言,详见文档
在rpc/add
目录下编写add.proto
文件
能够经过命令生成proto文件模板
goctl rpc template -o add.proto
修改后文件内容以下:
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
目录下执行命令
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
直接能够运行,以下:
$ go run add.go -f etc/add.yaml Starting rpc server at 127.0.0.1:8080...
etc/add.yaml
文件里能够修改侦听端口等配置
在rpc/check
目录下编写check.proto
文件
能够经过命令生成proto文件模板
goctl rpc template -o check.proto
修改后文件内容以下:
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
目录下执行命令
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
服务使用了,直接能够运行,以下:
$ go run check.go -f etc/check.yaml Starting rpc server at 127.0.0.1:8081...
修改配置文件bookstore-api.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服务依赖
type Config struct { rest.RestConf Add rpcx.RpcClientConf // 手动代码 Check rpcx.RpcClientConf // 手动代码 }
修改internal/svc/servicecontext.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
方法,以下:
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
方法,以下:
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系统中查询图书的价格
bookstore下建立rpc/model
目录:mkdir -p rpc/model
在rpc/model目录下编写建立book表的sql文件book.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
create database gozero;
source book.sql;
在rpc/model
目录下执行以下命令生成CRUD+cache代码,-c
表示使用redis cache
goctl model mysql ddl -c -src book.sql -dir .
也能够用datasource
命令代替ddl
来指定数据库连接直接从schema生成
生成后的文件结构以下:
rpc/model ├── bookstore.sql ├── bookstoremodel.go // CRUD+cache代码 └── vars.go // 定义常量和变量
修改rpc/add/etc/add.yaml
和rpc/check/etc/check.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
,以下:
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
,以下:
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
,以下:
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
,以下:
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 // 手动代码结束 }
至此代码修改完成,凡事手动修改的代码我加了标注
add api调用
curl -i "http://localhost:8888/add?book=go-zero&price=10"
返回以下:
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调用
curl -i "http://localhost:8888/check?book=go-zero"
返回以下:
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}
由于写入依赖于mysql的写入速度,就至关于压mysql了,因此压测只测试了check接口,至关于从mysql里读取并利用缓存,为了方便,直接压这一本书,由于有缓存,多本书也是同样的,对压测结果没有影响。
压测以前,让咱们先把打开文件句柄数调大:
ulimit -n 20000
并日志的等级改成error
,防止过多的info影响压测结果,在每一个yaml配置文件里加上以下:
Log: Level: error
能够看出在个人MacBook Pro上能达到3万+的qps。
https://github.com/tal-tech/go-zero/tree/master/example/bookstore
咱们一直强调工具大于约定和文档。
go-zero不仅是一个框架,更是一个创建在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
咱们在保持简单的同时也尽量把微服务治理的复杂度封装到了框架内部,极大的下降了开发人员的心智负担,使得业务开发得以快速推动。
经过go-zero+goctl生成的代码,包含了微服务治理的各类组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,能够轻松部署以承载巨大访问量。
有任何好的提高工程效率的想法,随时欢迎交流!👏
https://github.com/tal-tech/go-zero
添加个人微信:kevwan,请注明go-zero,我拉进go-zero社区群🤝
好将来技术