Github

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

疾速构建高并发微服务

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

  • 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
  • 创立工作目录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 OKContent-Type: application/jsonDate: Thu, 27 Aug 2020 14:31:39 GMTContent-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.yamlStarting 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

    留神:这个网站md反对不敌对,localhost:2379原文为- localhost:2379

  • 批改internal/config/config.go如下,减少transform服务依赖

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

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

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

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

    func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {  // 手动代码开始    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  // 手动代码完结}

通过调用transformerExpand办法实现短链复原到url

  • 批改internal/logic/shortenlogic.go,如下:

    func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {  // 手动代码开始    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  // 手动代码完结}

通过调用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)/gozeroTable: shorturlCache:  Host: localhost:6379

    留神:Host: localhost:6379原文为- Host: localhost:6379,为了防止md反对不敌对问题
    能够应用多个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 OKContent-Type: application/jsonDate: Sat, 29 Aug 2020 10:49:49 GMTContent-Length: 21{"shorten":"f35b2a"}
  • expand api调用

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

    返回如下:

    HTTP/1.1 200 OKContent-Type: application/jsonDate: Sat, 29 Aug 2020 10:51:53 GMTContent-Length: 34{"url":"http://www.xiaoheiban.cn"}

11. Benchmark

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

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

12. 残缺代码

https://github.com/tal-tech/go-zero/tree/master/example/shorturl

12. 总结

咱们始终强调工具大于约定和文档

go-zero不只是一个框架,更是一个建设在框架+工具根底上的,简化和标准了整个微服务构建的技术体系。

咱们在放弃简略的同时也尽可能把微服务治理的复杂度封装到了框架外部,极大的升高了开发人员的心智累赘,使得业务开发得以疾速推动。

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

有任何好的晋升工程效率的想法,随时欢迎交换!????