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
-
: 設定 serverapp.Serve()
-
: 設定 workerapp.Schedule()
-
: 啟動 app, 執行 server/worker 等内容app.run()
看一下 app.run()
源碼就明白了
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 如果沒有配置
, 在 server 啟動後, 每隔 30s 輸出一次, 這導緻我通過 log 來驗證的場景, 以為是遇到 bug 了async
- 我測試代碼的時候喜歡忽略 err, 雖然代碼看起來 簡單很多, 給 debug 增加了難度, 同時不利于養成好的工程習慣
- debug 遇到 context timeout, 這個屬于沒有經驗, context timout 不會因為 debug 的單步調試停止計時, 導緻我繞進去了很久, 才發現是
觸發了context timeout
- log 如果沒有配置
快速配置 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 個不同項目的, 這就導緻
一直 404, 我已經給開發組提了 PRedit on github
- 部分 url 404, 這種算是非常低級的錯誤了, 通常因 年久失修 會比較多, 但是 jupiter 才開源多久
- 部分貼的代碼執行個體有錯誤, 是以關于代碼, 一是要使用源碼中提供的 example, 二是一定要自己動手跑起來, 文檔貼代碼因為 上下文不全, 人為失誤等, 一向是重災區, 受歡迎的開源項目文檔有多人參與貢獻, 這塊要好很多
- 文檔在 組織 上對新人并不是特别友好, 或者說文檔沒有遵循一定的 套路, 導緻引起一些不必要的麻煩(我踩了幾個, 後面一一列出來)
關于文檔中錯誤的部分,
我也一并送出了一個 PR最後來幾句開源老兵的叨逼叨:
- 希望不是一個 KPI 項目, 雖然多看源碼總是有幫助的, 但是, 那感覺會像吃了蒼蠅一樣
- 時間是開源軟體的朋友, 時間稍微拉長一點, 是否 真的開源, 一目了然, 這裡并不是 結果導向, 開源确實需要付出很多, 才能做好
寫在最後 -- 如何快速上手一個架構?
- 熟悉文檔和 api, 勤做筆記和練手
- 生命周期思考法, 了解架構的執行流程
- 翻源碼, 很多時候有奇效