還在手擼Go微服務?快來試試go-zero,超乎你的想象!

Github

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

快速構建高併發微服務

0. 爲何說作好微服務很難?

要想作好微服務,咱們須要理解和掌握的知識點很是多,從幾個維度上來講:git

  • 基本功能層面github

    1. 併發控制&限流,避免服務被突發流量擊垮
    2. 服務註冊與服務發現,確保可以動態偵測增減的節點
    3. 負載均衡,須要根據節點承受能力分發流量
    4. 超時控制,避免對已超時請求作無用功
    5. 熔斷設計,快速失敗,保障故障節點的恢復能力
  • 高階功能層面redis

    1. 請求認證,確保每一個用戶只能訪問本身的數據
    2. 鏈路追蹤,用於理解整個系統和快速定位特定請求的問題
    3. 日誌,用於數據收集和問題定位
    4. 可觀測性,沒有度量就沒有優化

對於其中每一點,咱們都須要用很長的篇幅來說述其原理和實現,那麼對咱們後端開發者來講,要想把這些知識點都掌握並落實到業務系統裏,難度是很是大的,不過咱們能夠依賴已經被大流量驗證過的框架體系。go-zero微服務框架就是爲此而生。算法

另外,咱們始終秉承工具大於約定和文檔的理念。咱們但願儘量減小開發人員的心智負擔,把精力都投入到產生業務價值的代碼上,減小重複代碼的編寫,因此咱們開發了goctl工具。sql

下面我經過短鏈微服務來演示經過go-zero快速的建立微服務的流程,走完一遍,你就會發現:原來編寫微服務如此簡單!shell

1. 什麼是短鏈服務?

短鏈服務就是將長的URL網址,經過程序計算等方式,轉換爲簡短的網址字符串。數據庫

寫此短鏈服務是爲了從總體上演示go-zero構建完整微服務的過程,算法和實現細節儘量簡化了,因此這不是一個高階的短鏈服務。json

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

    注意:這個網站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)/gozero
    Table: shorturl
    Cache:
      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 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. 完整代碼

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

12. 總結

咱們一直強調工具大於約定和文檔

go-zero不僅是一個框架,更是一個創建在框架+工具基礎上的,簡化和規範了整個微服務構建的技術體系。

咱們在保持簡單的同時也儘量把微服務治理的複雜度封裝到了框架內部,極大的下降了開發人員的心智負擔,使得業務開發得以快速推動。

經過go-zero+goctl生成的代碼,包含了微服務治理的各類組件,包括:併發控制、自適應熔斷、自適應降載、自動緩存控制等,能夠輕鬆部署以承載巨大訪問量。

有任何好的提高工程效率的想法,隨時歡迎交流!👏

相關文章
相關標籤/搜索