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, 勤做笔记和练手
  • 生命周期思考法, 了解框架的执行流程
  • 翻源码, 很多时候有奇效
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352