learn| jupiter 學習筆記一

date: 2020-08-11 23:52:19
title: learn| jupiter 學習筆記一

生命不息, 學習不止, 這次我們來折騰 jupiter 框架

【鬥魚】沒人比我更懂微服務-Go 微服務框架Jupiter

helloworld

把官網的 example 都實現了一遍, 才發現 helloworld 應該是這樣的:

  • 最簡單版
package main

import (
    "github.com/douyu/jupiter"
    "github.com/douyu/jupiter/pkg/xlog"
)

func main() {
    var app jupiter.Application
    app.Startup() // 啓動框架, 可以使用框架的各種功能了
    xlog.Info("hello world")
}
  • 稍微來點封裝
package main

import (
    "github.com/douyu/jupiter"
    "github.com/douyu/jupiter/pkg/xlog"
)

func main() {
    var app jupiter.Application
    app.Startup(testLog) // 支持在框架初始化後, 執行特定的方法
}

func testLog() error { // 封裝成方法
    xlog.Info("hello world")
    return nil
}
  • 更復雜點, 套個殼
package main

import (
    "fmt"
    "github.com/douyu/jupiter"
    "github.com/douyu/jupiter/pkg/xlog"
)

func main() {
    eng := NewEngine()
    fmt.Println(eng)
}

type Engine struct {
    jupiter.Application
}

func NewEngine() *Engine {
    eng := &Engine{}
    eng.Startup(testLog)
    return eng
}

func testLog() error {
    xlog.Info("hello world")
    return nil
}

PS: 爲了下面講解代碼方便, 均不使用套殼版

  • 再深入點, 看看 Startup 幹了些啥
func (app *Application) Startup(fns ...func() error) error {
    app.initialize() // 初始化 app
    if err := app.startup(); err != nil { // 初始化 falg/log/config/trace/governor 等模塊
        return err
    }
    return xgo.SerialUntilError(fns...)() // 這是爲啥支持傳入多個方法
}

生命週期

  • 直接上完整的例子
package main

import (
    "github.com/douyu/jupiter"
    "github.com/douyu/jupiter/pkg/conf" // conf 模塊
    "github.com/douyu/jupiter/pkg/registry/compound"
    "github.com/douyu/jupiter/pkg/registry/etcdv3" // 除了 registry, 還是 client 的使用例子
    "github.com/douyu/jupiter/pkg/server"
    "github.com/douyu/jupiter/pkg/server/xecho"
    "github.com/douyu/jupiter/pkg/server/xgin"
    "github.com/douyu/jupiter/pkg/worker"
    "github.com/douyu/jupiter/pkg/worker/xcron"
    "github.com/douyu/jupiter/pkg/xlog" // log 模塊
    "github.com/gin-gonic/gin"
    "github.com/labstack/echo/v4"
    "time"
)

func main() {
    var app jupiter.Application
    
    // 初始化框架的功能, 這裏額外傳入了
    app.Startup(fileWatcher)

    // 修改 xlog.DefaultLogger, 從而改變 xlog 的行爲
    // 後面會具體講解 config/log 模塊
    xlog.DefaultLogger = xlog.StdConfig("default").Build()

    // 可以啓動多個 server
    app.Serve(startEcho())
    app.Serve(startGin())
    
    // 可以設置註冊中心, server 啓動是會自動註冊進去, 這裏使用 etcd 作爲註冊中心
    app.SetRegistry(compound.New(etcdv3.StdConfig("etcd").Build()))

    // 設置 worker
    app.Schedule(startWorker())

    // 啓動應用
    app.Run()
}

func fileWatcher() error {
    go func() {
        peopleName := conf.GetString("people.name")
        xlog.Info(peopleName)
        time.Sleep(time.Second*10)
    }()
    return nil
}

func startEcho() server.Server {
    s := xecho.DefaultConfig().Build()
    s.GET("/hello", func(c echo.Context) error {
        return c.JSON(200, "echo")
    })
    return s
}

func startGin() server.Server {
    s := xgin.StdConfig("http").Build()
    s.GET("/gin", func(c *gin.Context) {
        c.JSON(200, "hello")
    })
    return s
}

func startWorker() worker.Worker {
    cron := xcron.DefaultConfig().Build()
    cron.Schedule(xcron.Every(time.Second*10), xcron.FuncJob(func() error {
        xlog.Info("cron")
        return nil
    }))
    return cron
}
  • 對應的配置
# jupiter 默認提供, governor 用於服務治理
[jupiter.server.governor]
enable = false
port = 2345

# server 配置
# http server: echo gin goframe
# grpc server
[jupiter.server.http]
#enable = false
port = 1234

# registry: registry + 具體實現(這裏是 etcd)
[jupiter.registry.etcd]
configKey = "jupiter.etcdv3.default"
timeout = "1s"
[jupiter.etcdv3.default]
endpoints = ["127.0.0.1:2379"]
secure = false

[jupiter.cron.test]
withSeconds = false
concurrentDelay= -1
immediatelyRun = false

[jupiter.logger.default]
debug = true
enableConsole = true
async = false

# 自定義配置
[people]
name = "daydaygo"

框架的執行流程如下

  • app.Startup(fileWatcher): 上一步講到, 初始化框架的功能, 這裏傳入了 fileWatcher, 可以使用動態更新配置, 後面會詳細講 -watch 功能
  • app.Serve(): 設置 server
  • app.Schedule(): 設置 worker
  • app.run(): 啓動 app, 執行 server/worker 等內容

看一下 app.run() 源碼就明白了

func (app *Application) Run(servers ...server.Server) error {
    app.smu.Lock()
    app.servers = append(app.servers, servers...) // app.Serve() 其實就是設置 app.servers 變量
    app.smu.Unlock()

    app.waitSignals() //start signal listen task in goroutine
    defer app.clean()

    // todo jobs not graceful
    app.startJobs()

    // start servers and govern server
    app.cycle.Run(app.startServers) // 這裏完成 server + server 註冊到註冊中心
    // start workers
    app.cycle.Run(app.startWorkers) // 這裏執行 worker

    //blocking and wait quit
    if err := <-app.cycle.Wait(); err != nil {
        app.logger.Error("jupiter shutdown with error", xlog.FieldMod(ecode.ModApp), xlog.FieldErr(err))
        return err
    }
    app.logger.Info("shutdown jupiter, bye!", xlog.FieldMod(ecode.ModApp))
    return nil
}

jupiter 的幾大模塊

  • config

默認配置文件使用 toml 格式, 使用 --config flag 來使用本地配置文件

go run main.go --conifg=config.toml

屬於 jupiter 的模塊, 使用 [jupiter.模塊名.名字] 來使用, 比如 [jupiter.server.http], 則是一個 jupiter server 的配置, 這個 server 名字爲 http

jupiter 中通過 2 類配置來初始化模塊:

// 使用默認配置
xlog.DefaultConfig().Build()

// 使用配置文件: [jupiter.logger.default]
xlog.xlog.StdConfig("default").Build()

理解了上面這些, 就掌握了配置的核心用法, 使用 Apollo/etcd 等配置中心, 配置文件的 filewatch 都是在此基礎之上

  • log

上面其實已經看到 log 的模塊的用法了, 需要修改 log 的行爲, 只需要修改配置, 並且使用如下代碼設置生效即可:

// 設置 DefaultLogger 即可
xlog.DefaultLogger = xlog.StdConfig("default").Build()

// 看一下 xlog.info 的源碼就能知道答案
func Info(msg string, fields ...Field) {
    DefaultLogger.Info(msg, fields...)
}

只要理解了這一點, 就已經理解了日誌的核心用法, 日誌 level, 日誌輸出到 stdout/file 都在此基礎之上

  • server registry governor

server 這部分內容是 jupiter 的重中之中, jupiter 增加了對 echo/gin/frame/grpc 等 server 的適配使用 xecho/xgin/xframe/xgrpc 等進行配置和使用, 非常的簡潔方便

使用 registry 適配配置中心, 目前適配了 etcd

使用 governor 進行服務治理(在 app.startuUp 階段就設置好了, 在 app.run 階段啓動)

理解了這幾個模塊之間的關係, 就很容易理解 server 模塊的核心用法

  • worker

worker 比較簡單, 對應 [jupiter.cron.xxx] 下的配置, 按需設置即可

jupiter 其他內容

  • jupiter 默認支持一些 flag(命令行參數), 可以使用 go run main.go -h 查看
  • -watch 的場景:
    • 修改 log level, info -> debug, 方便線上有問題時蒐集更多日誌進行分析
    • 修改自定義配置, 可以實時生效
  • 自己遇到的一些問題
    • log 如果沒有配置 async, 在 server 啓動後, 每隔 30s 輸出一次, 這導致我通過 log 來驗證的場景, 以爲是遇到 bug 了
    • 我測試代碼的時候喜歡忽略 err, 雖然代碼看起來 簡單很多, 給 debug 增加了難度, 同時不利於養成好的工程習慣
    • debug 遇到 context timeout, 這個屬於沒有經驗, context timout 不會因爲 debug 的單步調試停止計時, 導致我繞進去了很久, 才發現是 context timeout 觸發了

快速配置 etcd 開發環境

jupiter 很多功能都需要 etcd 支持, 可以使用 docker-compose, 本地快速起起來:

version: '3'
services:
    etcd:
        image: quay.io/coreos/etcd
        environment: 
            ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379"
            ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
            ETCDCTL_API: "3"
        ports: 
            - 12379:2379 # http, 本地的端口自己設置
            # - 2380:2380 # 節點間
            # - 4001:4001
    etcda: # 簡單的管理界面
        image: evildecay/etcdkeeper
        environment: 
            HOST: 0.0.0.0
        ports:
            - 10280:8080 # 本地的端口自己設置
        links: 
            - etcd

也可以直接使用 etcdctl 來測試:

# install
brew install etcd

# use
etcdctl --endpoints=127.0.0.1:12379 get '/hello'

開源逗逼嘮

jupiter 的這次開源在我這個開源老兵(github star 4k+ 和 star 3k+ 框架的核心開發者)看來看來確實有些倉促, 主要集中在文檔這塊, 至於源碼, 目前 實力不允許, 總得多看看多寫寫, 能拿出足夠多的乾貨時再 BB

從目前文檔看到的幾個問題:

  • 文檔基於 vuepress, 簡單實用上手快, 不過 jupiter 源碼和文檔是分 2 個不同項目的, 這就導致 edit on github 一直 404, 我已經給開發組提了 PR
  • 部分 url 404, 這種算是非常低級的錯誤了, 通常因 年久失修 會比較多, 但是 jupiter 纔開源多久
  • 部分貼的代碼實例有錯誤, 所以關於代碼, 一是要使用源碼中提供的 example, 二是一定要自己動手跑起來, 文檔貼代碼因爲 上下文不全, 人爲失誤等, 一向是重災區, 受歡迎的開源項目文檔有多人蔘與貢獻, 這塊要好很多
  • 文檔在 組織 上對新人並不是特別友好, 或者說文檔沒有遵循一定的 套路, 導致引起一些不必要的麻煩(我踩了幾個, 後面一一列出來)

關於文檔中錯誤的部分, 我也一併提交了一個 PR

最後來幾句開源老兵的叨逼叨:

  • 希望不是一個 KPI 項目, 雖然多看源碼總是有幫助的, 但是, 那感覺會像吃了蒼蠅一樣
  • 時間是開源軟件的朋友, 時間稍微拉長一點, 是否 真的開源, 一目瞭然, 這裏並不是 結果導向, 開源確實需要付出很多, 才能做好

寫在最後 -- 如何快速上手一個框架?

  • 熟悉文檔和 api, 勤做筆記和練手
  • 生命週期思考法, 瞭解框架的執行流程
  • 翻源碼, 很多時候有奇效
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章