天天看點

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, 勤做筆記和練手
  • 生命周期思考法, 了解架構的執行流程
  • 翻源碼, 很多時候有奇效