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
触发了
- 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 个不同项目的, 这就导致
edit on github
一直 404, 我已经给开发组提了 PR - 部分 url 404, 这种算是非常低级的错误了, 通常因 年久失修 会比较多, 但是 jupiter 才开源多久
- 部分贴的代码实例有错误, 所以关于代码, 一是要使用源码中提供的 example, 二是一定要自己动手跑起来, 文档贴代码因为 上下文不全, 人为失误等, 一向是重灾区, 受欢迎的开源项目文档有多人参与贡献, 这块要好很多
- 文档在 组织 上对新人并不是特别友好, 或者说文档没有遵循一定的 套路, 导致引起一些不必要的麻烦(我踩了几个, 后面一一列出来)
关于文档中错误的部分, 我也一并提交了一个 PR
最后来几句开源老兵的叨逼叨:
- 希望不是一个 KPI 项目, 虽然多看源码总是有帮助的, 但是, 那感觉会像吃了苍蝇一样
- 时间是开源软件的朋友, 时间稍微拉长一点, 是否 真的开源, 一目了然, 这里并不是 结果导向, 开源确实需要付出很多, 才能做好
写在最后 -- 如何快速上手一个框架?
- 熟悉文档和 api, 勤做笔记和练手
- 生命周期思考法, 了解框架的执行流程
- 翻源码, 很多时候有奇效