你还在手撕微服务?快试试 go-zero 的微服务自动生成 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
kevinwan
V2EX    Go 编程语言

你还在手撕微服务?快试试 go-zero 的微服务自动生成

  •  4  
  •   kevinwan 2020-09-04 15:23:26 +08:00 2660 次点击
    这是一个创建于 1877 天前的主题,其中的信息可能已经有所发展或是发生改变。

    0. 为什么说做好微服务很难?

    要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:

    • 基本功能层面

      1. 并发控制&限流,避免服务被突发流量击垮
      2. 服务注册与服务发现,确保能够动态侦测增减的节点
      3. 负载均衡,需要根据节点承受能力分发流量
      4. 超时控制,避免对已超时请求做无用功
      5. 熔断设计,快速失败,保障故障节点的恢复能力
    • 高阶功能层面

      1. 请求认证,确保每个用户只能访问自己的数据
      2. 链路追踪,用于理解整个系统和快速定位特定请求的问题
      3. 日志,用于数据收集和问题定位
      4. 可观测性,没有度量就没有优化

    对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。go-zero 微服务框架就是为此而生。

    另外,我们始终秉承工具大于约定和文档的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了goctl工具。

    下面我通过短链微服务来演示通过go-zero快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!

    1. 什么是短链服务?

    短链服务就是将长的 URL 网址,通过程序计算等方式,转换为简短的网址字符串。

    写此短链服务是为了从整体上演示 go-zero 构建完整微服务的过程,算法和实现细节尽可能简化了,所以这不是一个高阶的短链服务。

    2. 短链微服务架构图

    架构图

    • 这里只用了Transform RPC一个微服务,并不是说 API Gateway 只能调用一个微服务,只是为了最简演示 API Gateway 如何调用 RPC 微服务而已
    • 在真正项目里要尽可能每个微服务使用自己的数据库,数据边界要清晰

    3. goctl 各层代码生成一览

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

    • API Gateway

      api gateway

    • RPC

    rpc

    • model

    model

    下面我们来一起完整走一遍快速构建微服务的流程,Let’s Go!♂

    4. 准备工作

    • 安装 etcd, mysql, redis

    • 安装 goctl 工具

      GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl 
    • 创建工作目录shorturl

    • shorturl目录下执行go mod init shorturl初始化go.mod

    5. 编写 API Gateway 代码

    • 通过 goctl 生成api/shorturl.api并编辑,为了简洁,去除了文件开头的info,代码如下:

      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) } 

      type 用法和 go 一致,service 用来定义 get/post/head/delete 等 api 请求,解释如下:

      • service shorturl-api {这一行定义了 service 名字
      • @server部分用来定义 server 端用到的属性
      • handler定义了服务端 handler 名字
      • get /shorten(shortenReq) returns(shortenResp)定义了 get 方法的路由、请求参数、返回参数等
    • 使用 goctl 生成 API Gateway 代码

      goctl api go -api shorturl.api -dir . 

      生成的文件结构如下:

      . ├── api │ ├── etc │ │ └── shorturl-api.yaml // 配置文件 │ ├── internal │ │ ├── config │ │ │ └── config.go // 定义配置 │ │ ├── handler │ │ │ ├── expandhandler.go // 实现 expandHandler │ │ │ ├── routes.go // 定义路由处理 │ │ │ └── shortenhandler.go // 实现 shortenHandler │ │ ├── logic │ │ │ ├── expandlogic.go // 实现 ExpandLogic │ │ │ └── shortenlogic.go // 实现 ShortenLogic │ │ ├── svc │ │ │ └── servicecontext.go // 定义 ServiceContext │ │ └── types │ │ └── types.go // 定义请求、返回结构体 │ ├── shorturl.api │ └── shorturl.go // main 入口定义 ├── go.mod └── go.sum 
    • 启动 API Gateway 服务,默认侦听在 8888 端口

      go run shorturl.go -f etc/shorturl-api.yaml 
    • 测试 API Gateway 服务

      curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn" 

      返回如下:

      HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 27 Aug 2020 14:31:39 GMT Content-Length: 15 {"shortUrl":""} 

      可以看到我们 API Gateway 其实啥也没干,就返回了个空值,接下来我们会在 rpc 服务里实现业务逻辑

    • 可以修改internal/svc/servicecontext.go来传递服务依赖(如果需要)

    • 实现逻辑可以修改internal/logic下的对应文件

    • 可以通过goctl生成各种客户端语言的 api 调用代码

    • 到这里,你已经可以通过 goctl 生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档

    6. 编写 transform rpc 服务

    • rpc/transform目录下编写transform.proto文件

      可以通过命令生成 proto 文件模板

      goctl rpc template -o transform.proto 

      修改后文件内容如下:

      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); } 
    • goctl生成 rpc 代码,在rpc/transform目录下执行命令

      goctl rpc proto -src transform.proto 

      文件结构如下:

      rpc/transform ├── etc │ └── transform.yaml // 配置文件 ├── internal │ ├── config │ │ └── config.go // 配置定义 │ ├── logic │ │ ├── expandlogic.go // expand 业务逻辑在这里实现 │ │ └── shortenlogic.go // shorten 业务逻辑在这里实现 │ ├── server │ │ └── transformerserver.go // 调用入口, 不需要修改 │ └── svc │ └── servicecontext.go // 定义 ServiceContext,传递依赖 ├── pb │ └── transform.pb.go ├── transform.go // rpc 服务 main 函数 ├── transform.proto └── transformer ├── transformer.go // 提供了外部调用方法,无需修改 ├── transformer_mock.go // mock 方法,测试用 └── types.go // request/response 结构体定义 

      直接可以运行,如下:

      $ go run transform.go -f etc/transform.yaml Starting rpc server at 127.0.0.1:8080... 

      etc/transform.yaml文件里可以修改侦听端口等配置

    7. 修改 API Gateway 代码调用 transform rpc 服务

    • 修改配置文件shorturl-api.yaml,增加如下内容

      Transform: Etcd: Hosts: - localhost:2379 Key: transform.rpc 

      通过 etcd 自动去发现可用的 transform 服务

    • 修改internal/config/config.go如下,增加 transform 服务依赖

      type Config struct { rest.RestConf Transform rpcx.RpcClientConf // 手动代码 } 
    • 修改internal/svc/servicecontext.go,如下:

      type ServiceContext struct { Config config.Config Transformer rpcx.Client // 手动代码 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, Transormer: rpcx.MustNewClient(c.Transform), // 手动代码 } } 

      通过 ServiceContext 在不同业务逻辑之间传递依赖

    • 修改internal/logic/expandlogic.go里的Expand方法,如下:

      func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) { // 手动代码开始 trans := transformer.NewTransformer(l.svcCtx.Transformer) resp, err := trans.Expand(l.ctx, &transformer.ExpandReq{ Shorten: req.Shorten, }) if err != nil { return nil, err } return &types.ExpandResp{ Url: resp.Url, }, nil // 手动代码结束 } 

      通过调用transformerExpand方法实现短链恢复到 url

    • 修改internal/logic/shortenlogic.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{ Url: req.Url, }) if err != nil { return nil, err } return &types.ShortenResp{ Shorten: resp.Shorten, }, nil // 手动代码结束 } 

      通过调用transformerShorten方法实现 url 到短链的变换

      至此,API Gateway 修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理 CRUD+cache

    8. 定义数据库表结构,并生成 CRUD+cache 代码

    • shorturl 下创建rpc/transform/model目录:mkdir -p rpc/transform/model

    • 在 rpc/transform/model 目录下编写创建 shorturl 表的 sql 文件shorturl.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; 
    • 创建 DB 和 table

      create database gozero; 
      source shorturl.sql; 
    • rpc/transform/model目录下执行如下命令生成 CRUD+cache 代码,-c表示使用redis cache

      goctl model mysql ddl -c -src shorturl.sql -dir . 

      也可以用datasource命令代替ddl来指定数据库链接直接从 schema 生成

      生成后的文件结构如下:

      rpc/transform/model ├── shorturl.sql ├── shorturlmodel.go // CRUD+cache 代码 └── vars.go // 定义常量和变量 

    9. 修改 shorten/expand rpc 代码调用 crud+cache 代码

    • 修改rpc/transform/etc/transform.yaml,增加如下内容:

      DataSource: root:@tcp(localhost:3306)/gozero Table: shorturl Cache: - Host: localhost:6379 

      可以使用多个 redis 作为 cache,支持 redis 单点或者 redis 集群

    • 修改rpc/transform/internal/config.go,如下:

      type Config struct { rpcx.RpcServerConf DataSource string // 手动代码 Table string // 手动代码 Cache cache.CacheConf // 手动代码 } 

      增加了 mysql 和 redis cache 配置

    • 修改rpc/transform/internal/svc/servicecontext.go,如下:

      type ServiceContext struct { c config.Config Model *model.ShorturlModel // 手动代码 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ c: c, Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码 } } 
    • 修改rpc/transform/internal/logic/expandlogic.go,如下:

      func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) { // 手动代码开始 res, err := l.svcCtx.Model.FindOne(in.Shorten) if err != nil { return nil, err } return &transform.ExpandResp{ Url: res.Url, }, nil // 手动代码结束 } 
    • 修改rpc/shorten/internal/logic/shortenlogic.go,如下:

      func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) { // 手动代码开始,生成短链接 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 // 手动代码结束 } 

      至此代码修改完成,凡事手动修改的代码我加了标注

    10. 完整调用演示

    • shorten api 调用

      curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn" 

      返回如下:

      HTTP/1.1 200 OK Content-Type: application/json Date: Sat, 29 Aug 2020 10:49:49 GMT Content-Length: 21 {"shorten":"f35b2a"} 
    • expand api 调用

      curl -i "http://localhost:8888/expand?shorten=f35b2a" 

      返回如下:

      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

    因为写入依赖于 mysql 的写入速度,就相当于压 mysql 了,所以压测只测试了 expand 接口,相当于从 mysql 里读取并利用缓存,shorten.lua 里随机从 db 里获取了 100 个热 key 来生成压测请求

    benchmark

    可以看出在我的 MacBook Pro 上能达到 3 万+的 qps 。

    12. 总结

    我们一直强调工具大于约定和文档

    go-zero 不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。

    我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。

    通过 go-zero+goctl 生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。

    有任何好的提升工程效率的想法,随时欢迎交流!

    13. 项目地址

    https://github.com/tal-tech/go-zero

    14. 微信交流群

    wechat

    4 条回复    2020-09-05 11:34:10 +08:00
    zhoushuguangking
        1
    zhoushuguangking  
       2020-09-04 15:26:10 +08:00
    简洁易用,在本地跑了个 demo,非常容易上手
    server
        2
    server  
       2020-09-04 15:36:53 +08:00
    哎, 这个 @ server 我是迷了, 话说二群我还带动了几个小伙伴.
    sjlzz1221
        3
    sjlzz1221  
       2020-09-04 21:19:51 +08:00
    有没有 java-zero
    naturalaw
        4
    naturalaw  
       2020-09-05 11:34:10 +08:00
    为啥不用 QQ 群,微信群有点鸡肋。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2592 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 03:02 PVG 11:02 LAX 20:02 JFK 23:02
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86