你还在手撕微服务?快试试 go-zero 的微服务自动生成
0. 为什么说做好微服务很难?
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
基本功能层面
- 并发控制&限流,避免服务被突发流量击垮
- 服务注册与服务发现,确保能够动态侦测增减的节点
- 负载均衡,需要根据节点承受能力分发流量
- 超时控制,避免对已超时请求做无用功
- 熔断设计,快速失败,保障故障节点的恢复能力
高阶功能层面
- 请求认证,确保每个用户只能访问自己的数据
- 链路追踪,用于理解整个系统和快速定位特定请求的问题
- 日志,用于数据收集和问题定位
- 可观测性,没有度量就没有优化
对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。go-zero微服务框架就是为此而生。
另外,我们始终秉承工具大于约定和文档的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了goctl工具。
下面我通过书店服务来演示通过go-zero快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!
1. 书店服务示例简介
为了教程简单,我们用书店服务做示例,并且只实现其中的增加书目和检查价格功能。
写此书店服务是为了从整体上演示go-zero构建完整微服务的过程,实现细节尽可能简化了。
2. 书店微服务架构图

3. goctl各层代码生成一览
所有绿色背景的功能模块是自动生成的,按需激活,红色模块是需要自己写的,也就是增加下依赖,编写业务特有逻辑,各层示意图分别如下:
- API Gateway

- RPC

- model

下面我们来一起完整走一遍快速构建微服务的流程,Let’s Go!♂️
4. 准备工作
安装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
5. 编写API Gateway代码
在
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生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档
6. 编写add rpc服务
在
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文件里可以修改侦听端口等配置
7. 编写check rpc服务
在
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...
8. 修改API Gateway代码调用add/check rpc服务
修改配置文件
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系统中查询图书的价格
9. 定义数据库表结构,并生成CRUD+cache代码
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 cachegoctl 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,增加如下内容: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
// 手动代码结束
}
至此代码修改完成,凡事手动修改的代码我加了标注
11. 完整调用演示
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}
12. Benchmark
因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了check接口,相当于从mysql里读取并利用缓存,为了方便,直接压这一本书,因为有缓存,多本书也是一样的,对压测结果没有影响。
压测之前,让我们先把打开文件句柄数调大:
ulimit -n 20000
并日志的等级改为error,防止过多的info影响压测结果,在每个yaml配置文件里加上如下:
Log:
Level: error

可以看出在我的MacBook Pro上能达到3万+的qps。
13. 完整代码
https://github.com/tal-tech/go-zero/tree/master/example/bookstore
14. 总结
我们一直强调工具大于约定和文档。
go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!
15. 项目地址
https://github.com/tal-tech/go-zero
16. 微信交流群
添加我的微信:kevwan,请注明go-zero,我拉进go-zero社区群
你还在手撕微服务?快试试 go-zero 的微服务自动生成的更多相关文章
- 设计数据库 ER 图太麻烦?不妨试试这两款工具,自动生成数据库 ER 图!!!
忙,真忙 点赞再看,养成习惯,微信搜索『程序通事』,关注就完事了! 点击查看更多精彩的文章 这两个星期真是巨忙,年前有个项目因为各种莫名原因,一直拖到这个月才开始真正测试.然后上周又接到新需求,马不停 ...
- 金融数据分析还能这样做?快试试这个BI工具小白也能学会!
说起银行.保险.股票投资等这些金融行业,大多数人都认为它们都是依靠数据驱动的企业,毕竟大数据的诞生本来就是为了金融信息流通而服务的,但是事实真的是这样吗? 事实并非如此,真正在金融行业做数据分析的人, ...
- WCF服务引用之后自动生成的泛型代理类名称太长的解决方案
问题:WCF服务引用之后会将原来的泛型类自动生成一个代理类,但是有时候名称太长怎么办? 解决方案: 1.方案一: 调用客户端同样也引用这个泛型类的类库. 2.方案二: 找到这个泛型类,然后在上面的[D ...
- Re:从0开始的微服务架构--(二)快速快速体验微服务架构?--转
原文地址:https://mp.weixin.qq.com/s/QO1QDQWnjHZp8EvGDrxZvw 这是专题的第二篇文章,看看如何搭建一个简单模式的微服务架构. 记得好久之前看到一个大牛说过 ...
- 微服务理论之二:面向微服务架构与传统架构、SOA对比,以及云化对比
一.Monolith 网上对Microservice进行介绍的文章常常以Monolith作为开头,我也不会例外.原因是,知道了Monolith的不便之后才能更容易地理解Microservice架构模式 ...
- .NET Core微服务之基于Ocelot实现API网关服务
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.啥是API网关? API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端 ...
- go微服务框架go-micro深度学习(三) Registry服务的注册和发现
服务的注册与发现是微服务必不可少的功能,这样系统才能有更高的性能,更高的可用性.go-micro框架的服务发现有自己能用的接口Registry.只要实现这个接口就可以定制自己的服务注册和发现. go- ...
- Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务
在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka ...
- 【微服务】使用spring cloud搭建微服务框架,整理学习资料
写在前面 使用spring cloud搭建微服务框架,是我最近最主要的工作之一,一开始我使用bubbo加zookeeper制作了一个基于dubbo的微服务框架,然后被架构师否了,架构师曰:此物过时.随 ...
随机推荐
- 保存vuex状态刷新不消失
写在App.vue中,所有页面共享此方法 export default { name: "app", components: {}, created() { // 页面每次刷新加载 ...
- 小程序开发-页面导航栏navigation-bar组件
导航栏navigation-bar 页面导航条配置节点,用于指定导航栏的一些属性.只能是 page-meta 组件内的第一个节点,需要配合它一同使用. 通过这个节点可以获得类似于调用 wx.setNa ...
- 2020年1月31日 安装Python的requests包记录
C:\Users\ufo>pip install requests Collecting requests WARNING: Retrying (Retry(total=, connect=No ...
- JVM调优和深入了解性能优化
JVM调优的本质: 并不是显著的提高系统性能,不是说你调了,性能就能提升几倍或者上十倍,JVM调优,主要调的是稳定.如果你的系统出现了频繁的垃圾回收,这个时候系统是不稳定的,所以需要我们来进行JVM调 ...
- apply用法
result.push.apply(result, document.getElementsByTagName(tag)); 但是,这里为什么要用apply呢? 因为document.getEleme ...
- Promise核心实现
核心 构造函数核心 维护状态变量,只能由pending变为resolve或者reject 维护一个存储结果的变量 维护一个回调数组,执行到then,如果我们传入的立即执行函数没有立即执行resolve ...
- Bootstrap一周学习小结
一.响应式布局 1.bootstrap是自适应mobile和web,会根据分辨率大小而自适应的一个代码库,Bootstrap定义了很多js和css的效果,把这些效果集成为多个API,供在使用时调用. ...
- hystrix源码小贴士之调用异常处理
executeCommandAndObserve方法处理onerror异常. return execution.doOnNext(markEmits) .doOnCompleted(markOnCom ...
- Springboot+Vue实现仿百度搜索自动提示框匹配查询功能
案例功能效果图 前端初始页面 输入搜索信息页面 点击查询结果页面 环境介绍 前端:vue 后端:springboot jdk:1.8及以上 数据库:mysql 核心代码介绍 TypeCtrler .j ...
- IoC基础篇(一)--- Spring容器中Bean的生命周期
日出日落,春去秋来,花随流水,北雁南飞,世间万物皆有生死轮回.从调用XML中的Bean配置信息,到应用到具体实例中,再到销毁,Bean也有属于它的生命周期. 人类大脑对图像的认知能力永远高于文字,因此 ...