1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Go 單體服務(wù)開發(fā)最佳實踐

        共 4267字,需瀏覽 9分鐘

         ·

        2022-05-14 08:23

        單體最佳實踐的由來

        • 對于很多初創(chuàng)公司來說,業(yè)務(wù)的早期我們更應(yīng)該關(guān)注于業(yè)務(wù)價值的交付,并且此時用戶體量也很小,QPS 也非常低,我們應(yīng)該使用更簡單的技術(shù)架構(gòu)來加速業(yè)務(wù)價值的交付,此時單體的優(yōu)勢就體現(xiàn)出來了。
        • 正如我直播分享時經(jīng)常提到,我們在使用單體快速交付業(yè)務(wù)價值的同時,也需要為業(yè)務(wù)的發(fā)展預(yù)留可能性,我們可以在單體里面清晰的拆分業(yè)務(wù)模塊。
        • go-zero 社區(qū)里也有很多小伙伴在問,咱們單體開發(fā)的最佳實踐應(yīng)該是怎樣的。

        go-zero 作為一個被廣泛使用的漸進式微服務(wù)框架來說,也是我在多個大型項目完整發(fā)展過程中沉淀出來的,自然我們也充分考慮了單體服務(wù)開發(fā)的場景。

        如圖所示的使用 go-zero 的單體架構(gòu),也可以支撐很大體量的業(yè)務(wù)規(guī)模,其中 Service 是單體服務(wù)的多個 Pod

        我就通過本文詳細跟大家分享一下如何使用 go-zero 快速開發(fā)一個有多個模塊的單體服務(wù)。

        單體示例

        我們用一個上傳下載的單體服務(wù)來講解 go-zero 單體服務(wù)開發(fā)的最佳實踐,為啥用這么個示例呢?

        • go-zero 社區(qū)里經(jīng)常有同學(xué)會問上傳文件怎么定義 API 文件,然后用 goctl 自動生成。初見此類問題會覺得比較奇怪,為啥不用 OSS 之類的服務(wù)呢?發(fā)現(xiàn)很多場景是用戶需要上傳一個excel,然后服務(wù)端解析完也就丟棄此文件了。一是文件較小,二是用戶量也不大,就不用那么復(fù)雜的通過 OSS 來繞一圈了,我覺得也挺合理的。

        • go-zero 社區(qū)也有同學(xué)問下載文件怎么通過定義一個 API 文件然后 goctl 自動生成。此類問題之所以通過 Go 來做,問下來一般兩個原因,一是業(yè)務(wù)剛開始,能簡單點布一個服務(wù)搞定就一個吧;二是希望能吃上 go-zero 的內(nèi)置 JWT 自動鑒權(quán)。

        僅以此為示例,無需深入探討上傳下載是否應(yīng)該通過 Go 來實現(xiàn)。那么接下來我們就看看我們怎么通過 go-zero 來解決這么一個單體服務(wù),我們稱之為文件(file)服務(wù)。架構(gòu)如下圖:

        單體實現(xiàn)

        API 定義

        使用過 go-zero 的同學(xué)都知道,我們提供了一個 API 格式的文件來描述 RESTful API,然后可以通過 goctl 一鍵生成對應(yīng)的代碼,我們只需要在 logic 文件里填寫對應(yīng)的業(yè)務(wù)邏輯即可。我們就來看看 downloadupload 服務(wù)怎么定義 API.

        Download 服務(wù)定義

        示例需求如下:

        • 通過 ?/static/ 路徑下載名為 的文件
        • 直接返回文件內(nèi)容即可

        我們在 api 目錄下創(chuàng)建一個名為 download.api 的文件,內(nèi)容如下:

        syntax = "v1"

        type DownloadRequest {
        File string `path:"file"`
        }

        service file-api {
        @handler DownloadHandler
        get /static/:file(DownloadRequest)
        }

        zero-api 的語法還是比較能自解釋的,含義如下:

        1. syntax = “v1” 表示這是 zero-apiv1 語法
        2. type DownloadRequest 定義了 Download 的請求格式
        3. service file-api 定義了 Download 的請求路由

        Upload 服務(wù)定義

        示例需求如下:

        • 通過 /upload 路徑上傳文件
        • 通過 json 返回上傳狀態(tài),其中的 code 可用于表達比 HTTP code 更豐富的場景

        我們在 api 目錄下創(chuàng)建一個名為 upload.api 的文件,內(nèi)容如下:

        syntax = "v1"

        type UploadResponse {
        Code int `json:"code"`
        }

        service file-api {
        @handler UploadHandler
        post /upload returns (UploadResponse)
        }

        解釋如下:

        1. syntax = “v1” 表示這是 zero-apiv1 語法
        2. type UploadResponse 定義了 Upload 的返回格式
        3. service file-api 定義了 Upload 的請求路由

        問題來了

        DownloadUpload 服務(wù)我們都定義好了,那怎么才能放到一個服務(wù)里給用戶提供服務(wù)呢?

        不知道細心的你有沒注意到一些細節(jié):

        1. 不管是 Download 還是 Upload 我們在 requestresponse 數(shù)據(jù)定義的時候都加了前綴,并沒有直接使用諸如 RequestResponse 這樣的
        2. 我們在 download.apiupload.api 里面定義 service 的時候都是用的 file-api 這個 service name,并沒有分別用 download-apiupload-api

        這么做的目的其實就是為了我們接下來把這兩個服務(wù)放到同一個單體里自動生成對應(yīng)的 Go 代碼。讓我們來看看怎么把 DownloadUpload 合并起來~

        定義單體服務(wù)接口

        出于簡單考慮,goctl 只支持接受單一 API 文件作為參數(shù),同時接受多個 API 文件的問題不在此討論,如有簡單高效的方案,后續(xù)可能支持。

        我們在 api 目錄下創(chuàng)建一個新的 file.api 的文件,內(nèi)容如下:

        syntax = "v1"

        import "download.api"
        import "upload.api"

        這樣我們就像 C/C++#include 一樣把 DownloadUpload 服務(wù)都導(dǎo)入進來了。但其中有幾點需要注意的:

        1. 定義的結(jié)構(gòu)體不能重名
        2. 所有文件里包含的 service name 必須是同一個

        最外層的 API 文件也可以包含同一個 service 的部分定義,但我們推薦保持對稱,除非這些 API 確實屬于父層級,比如跟 DownloadUpload 屬于同一個邏輯層次,那么就不應(yīng)該放到 file.api 里面定義。

        至此,我們的文件結(jié)構(gòu)如下:

        .
        └──?api
        ????├──?download.api
        ????├──?file.api
        ????└──?upload.api

        生成單體服務(wù)

        既然已經(jīng)有了 API 接口定義,那么對于 go-zero 來說,接下來的事情就很簡單直接了(當然,定義 API 也挺簡單的,不是嗎?),讓我們來使用 goctl 生成單體服務(wù)代碼。

        $?goctl?api?go?-api?api/file.api?-dir?.

        我們來看看生成后的文件結(jié)構(gòu):

        .
        ├──?api
        │???├──?download.api
        │???├──?file.api
        │???└──?upload.api
        ├──?etc
        │???└──?file-api.yaml
        ├──?file.go
        ├──?go.mod
        ├──?go.sum
        └──?internal
        ????├──?config
        ????│???└──?config.go
        ????├──?handler
        ????│???├──?downloadhandler.go
        ????│???├──?routes.go
        ????│???└──?uploadhandler.go
        ????├──?logic
        ????│???├──?downloadlogic.go
        ????│???└──?uploadlogic.go
        ????├──?svc
        ????│???└──?servicecontext.go
        ????└──?types
        ????????└──?types.go

        我們來按目錄解釋一下項目代碼的構(gòu)成:

        • api 目錄:我們前面定義的 API 接口描述文件,無需多言
        • etc 目錄:這個是用來放置 yaml 配置文件的,所有的配置項都可以寫在 file-api.yaml 文件里
        • file.gomain 函數(shù)所在文件,文件名跟 service 同名,去掉了后綴 -api
        • internal/config 目錄:服務(wù)的配置定義
        • internal/handler 目錄:API 文件里定義的路由對應(yīng)的 handler 實現(xiàn)
        • internal/logic 目錄:用來放每個路由對應(yīng)的業(yè)務(wù)處理邏輯,之所以區(qū)分 handlerlogic 是為了讓業(yè)務(wù)處理部分盡可能減少依賴,把 HTTP requests 和邏輯處理代碼隔離開,便于后續(xù)按需拆分成 RPC service
        • internal/svc 目錄:用來定義業(yè)務(wù)邏輯處理的依賴,我們可以在 main 里面創(chuàng)建依賴的資源,然后通過 ServiceContext 傳遞給 handlerlogic
        • internal/types 目錄:定義了 API 請求和返回數(shù)據(jù)結(jié)構(gòu)

        咱們什么也不改,先來跑一下看看效果。

        $?go?run?file.go?-f?etc/file-api.yaml
        Starting?server?at?0.0.0.0:8888...

        實現(xiàn)業(yè)務(wù)邏輯

        接下來我們需要實現(xiàn)相關(guān)的業(yè)務(wù)邏輯,但是這里的邏輯其實只是一個演示用途,無需過于關(guān)注實現(xiàn)細節(jié),只需要理解我們應(yīng)該把業(yè)務(wù)邏輯寫在 logic 層即可。

        這里一共做了以幾件事:

        • 增加配置項里的 Path 設(shè)置,用來放置上傳文件,默認值我寫了當前目錄,因為是示例,如下:

          type?Config?struct?{
          ??rest.RestConf
          ??//?新增
          ??Path?string?`json:",default=."`
          }
        • 調(diào)整了請求體的大小限制,如下:

          Name:?file-api
          Host:?localhost
          Port:?8888
          #?新增
          MaxBytes:?1073741824
        • 由于 Download 需要寫文件給客戶端,所以我們把 ResponseWriter 當成 io.Writer 傳遞給了 logic 層,修改后的代碼如下:

          func?(l?*DownloadLogic)?Download(req?*types.DownloadRequest)?error?{
          ??logx.Infof("download?%s",?req.File)
          ??body,?err?:=?ioutil.ReadFile(req.File)
          ??if?err?!=?nil?{
          ????return?err
          ??}

          ??n,?err?:=?l.writer.Write(body)
          ??if?err?!=?nil?{
          ????return?err
          ??}

          ??if?n?len(body)?{
          ????return?io.ErrClosedPipe
          ??}

          ??return?nil
          }
        • 由于 Upload 需要讀取用戶上傳的文件,所以我們把 http.Request 傳遞給了 logic ?層,修改后的代碼如下:

          func?(l?*UploadLogic)?Upload()?(resp?*types.UploadResponse,?err?error)?{
          ??l.r.ParseMultipartForm(maxFileSize)
          ??file,?handler,?err?:=?l.r.FormFile("myFile")
          ??if?err?!=?nil?{
          ????return?nil,?err
          ??}
          ??defer?file.Close()

          ??logx.Infof("upload?file:?%+v,?file?size:?%d,?MIME?header:?%+v",
          ????handler.Filename,?handler.Size,?handler.Header)

          ??tempFile,?err?:=?os.Create(path.Join(l.svcCtx.Config.Path,?handler.Filename))
          ??if?err?!=?nil?{
          ????return?nil,?err
          ??}
          ??defer?tempFile.Close()
          ??io.Copy(tempFile,?file)

          ??return?&types.UploadResponse{
          ????Code:?0,
          ??},?nil
          }

        完整代碼:https://github.com/zeromicro/zero-examples/tree/main/monolithic

        我們可以通過啟動 file 單體服務(wù):

        $?go?run?file.go?-f?etc/file-api.yaml

        可以通過 curl 來驗證 Download 服務(wù):

        $?curl?-i?"http://localhost:8888/static/file.go"
        HTTP/1.1?200?OK
        Traceparent:?00-831431c47d162b4decfb6b30fb232556-dd3b383feb1f13a9-00
        Date:?Mon,?25?Apr?2022?01:50:58?GMT
        Content-Length:?584
        Content-Type:?text/plain;?charset=utf-8

        ...

        示例倉庫里包含了 upload.html,瀏覽器打開這個文件就可以嘗試 Upload 服務(wù)了。

        單體開發(fā)的總結(jié)

        我把用 go-zero 開發(fā)單體服務(wù)的完整流程歸納如下:

        1. 定義各個子模塊的 API 文件,比如:download.apiupload.api
        2. 定義總的 API 文件,比如:file.api。用來 import 步驟一定義的各個子模塊的 API 文件
        3. 通過 goctl api go 命令生成單體服務(wù)框架代碼
        4. 增加和調(diào)整配置,實現(xiàn)對應(yīng)的子模塊的業(yè)務(wù)邏輯

        另外,goctl 可以根據(jù) SQL 一鍵生成 CRUD 以及 cache 代碼,可以幫助大家更快速的開發(fā)單體服務(wù)。

        項目地址

        https://github.com/zeromicro/go-zero

        歡迎使用 go-zerostar 支持我們!




        往期推薦


        我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。


        堅持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

        瀏覽 34
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            污秽视频免费看 | 男人的天堂青青草视频 | 无码狠狠躁久久久久久久网址 | tubechina露脸高潮 | 色国产精品女五丁香五月五月 | 四虎性爱 | 免费黄色在线观看 | 99肏屄 | 亚洲图片小说视频 | 蜜臀久久99精品久久久老牛影视 |