其實這個項目在 4 年前就已經開始了,因為所有的功能都是基於日常工作中的需求來的,斷斷續續的補充和完善功能,之前都是在自己公司這邊的各種 Go 項目和我開源的一些項目中使用。很早之前就想把它開源出來,但是一直懶得寫文檔(感覺寫文檔是最難得事兒了),所以一直讓它靜靜地躺 Github 。今天終於補充了個簡版的文檔,是時候把它拿出來了😀。
感興趣的朋友們歡迎來看看啊,有磚拍磚,有需求提需求,一定虛心向大家學習!
Glacier 是一款支持依賴注入的模塊化的應用開發框架,它以 go-ioc 依賴注入容器核心,為 Go 應用開發解決了依賴傳遞和模塊化的問題。
- 特性
- 使用
- 執行流程
-
核心概念
-
依賴注入
- Binder
- Resolver
-
Provider
- ProviderBoot
- DaemonProvider
- ProviderAggregate
- Service
- ModuleLoadPolicy
- Priority
-
-
Web 框架
- Usage
- 控制器
-
事件管理
- 本地內存作為事件存儲後端
- Redis 作為事件存儲後端
- 定時任務
- 日誌
- Eloquent ORM
- 平滑退出
- 第三方框架集成
- 示例項目
特性
- 依賴注入:通過依賴注入的方式來管理對象的依賴,支持單例、原型對象創建
- 模塊化:通過 Provider 特性,輕鬆實現應用的模塊化
- 內置 Web 開發支持:Glacier 內置了對 Web 應用開發的支持,提供了功能豐富的 API 簡化 web 開發
使用
創建一個新的項目,使用下面的命令安裝 Glacier 開發框架
go get github.com/mylxsw/glacier
為了簡化應用的創建過程,我們一般可以通過 starter 模板來創建應用
import "github.com/mylxsw/glacier/starter/app"
...
// 方法一:快捷啓動應用
app.MustStart("1.0", 3, func(app *app.App) error {
// 這裏完成應用的初始化
// ...
return nil
})
// 方法二: 分步驟啓動應用
ins := app.Create("1.0", 3)
// 應用初始化
// ...
app.MustRun(ins)
示例:
app.MustStart("1.0", 3, func(ins *app.App) error {
ins.AddStringFlag("listen", ":8080", "http listen address")
ins.Provider(web.Provider(
listener.FlagContext("listen"),
web.SetRouteHandlerOption(func(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) {
router.Get("/", func(ctx web.Context) web.Response {
return ctx.JSON(web.M{})
})
}),
))
return nil
})
代碼示例可以參考當前項目的 example 目錄。
執行流程
核心概念
依賴注入
Glacier 框架充分利用了 go-ioc 提供的依賴注入能力,為應用提供了功能強大的依賴注入特性。
在使用依賴注入特性時,首先要理解以下兩個接口的作用
infra.Binder該接口用於對象創建實例方法的綁定,簡單説就是向go-ioc容器註冊對象的創建方法infra.Resolver該接口用於對象的實例化,獲取對象實例
無論是 Binder 還是 Resolver,都會有一個 interface{} 類型的參數,它的類型為符合一定規則的函數,後面在 Binder 和 Resolver 部分將會詳細説明。
Binder
infra.Binder 是一個對象定義接口,用於將實例的創建方法綁定到依賴注入容器,提供了以下常用方法
Prototype(initialize interface{}) error原型綁定,每次訪問綁定的實例都會基於initialize函數重新創建新的實例Singleton(initialize interface{}) error單例綁定,每次訪問綁定的實例都是同一個,只會在第一次訪問的時候創建初始實例BindValue(key string, value interface{}) error將一個具體的值綁定到key
Prototype 和 Singleton 方法參數 initialize interface{} 支持以下兩種形式
-
形式1:
func(依賴參數列表...) (綁定類型定義, error)// 這裏使用單例方法定義了數據庫連接對象的創建方法 binder.Singleton(func(conf *Config) (*sql.DB, error) { return sql.Open("mysql", conf.MySQLURI) }) binder.Singleton(func(c infra.FlagContext) *Config { ... return &Config{ Listen: c.String("listen"), MySQLURI: c.String("mysql_uri"), APIToken: c.String("api_token"), ... } }) -
形式2:
func(注入參數列表...) 綁定類型定義binder.Singleton(func() UserRepo { return &userRepoImpl{} }) binder.Singleton(func(db *sql.DB) UserRepo { // 這裏我們創建的 userRepoImpl 對象,依賴 sql.DB 對象,只需要在函數 // 參數中,將依賴列舉出來,容器會自動完成這些對象的創建 return &userRepoImpl{db: db} })
Resolver
infra.Resolver 是對象實例化接口,通過依賴注入的方式獲取實例,提供了以下常用方法
Resolve(callback interface{}) error執行 callback 函數,自動為 callback 函數提供所需參數Call(callback interface{}) ([]interface{}, error)執行 callback 函數,自動為 callback 函數提供所需參數,支持返回值,返回參數為Call的第一個數組參數-
AutoWire(object interface{}) error自動對結構體對象進行依賴注入,object 必須是結構體對象的指針。自動注入字段(公開和私有均支持)需要添加autowiretag,支持以下兩種- autowire:"@" 根據字段的類型來注入
- autowire:"自定義key" 根據自定義的key來注入(查找名為 key 的綁定)
Get(key interface{}) (interface{}, error)直接通過 key 來查找對應的對象實例
// Resolve
resolver.Resolve(func(db *sql.DB) {...})
err := resolver.Resolve(func(db *sql.DB) error {...})
// Call
resolver.Call(func(userRepo UserRepo) {...})
// Call 帶有返回值
// 這裏的 err 是依賴注入過程中的錯誤,比如依賴對象創建失敗
// results 是一個類型為 []interface{} 的數組,數組中按次序包含了 callback 函數的返回值,以下面的代碼為例,其中
// results[0] - string
// results[1] - error
results, err := resolver.Call(func(userRepo UserRepo) (string, error) {...})
// 由於每個返回值都是 interface{} 類型,因此在使用時需要執行類型斷言,將其轉換為具體的類型再使用
returnValue := results[0].(string)
returnErr := results[1].(error)
// AutoWire
// 假設我們有一個 UserRepo,創建該結構體時需要數據庫的連接實例
type UserRepo struct {
db *sql.DB `autowire:"@"`
}
userRepo := UserRepo{}
resolver.AutoWire(&userRepo)
// 現在 userRepo 中的 db 參數已經自動被設置為了數據庫連接對象,可以繼續執行後續的操作了
Provider
在 Glacier 應用開發框架中,Provider 是應用模塊化的核心,每個獨立的功能模塊通過 Provider 完成實例初始化,每個 Provider 都需要實現 infra.Provider 接口。 在每個功能模塊中,我們通常會創建一個名為 provider.go 的文件,在該文件中創建一個 provider 實現
type Provider struct{}
func (Provider) Register(binder infra.Binder) {
... // 這裏可以使用 binder 向 IOC 容器註冊當前模塊中的實例創建方法
}
Provider 接口只有一個必須實現的方法 Register(binder infra.Binder),該方法用於註冊當前模塊的對象到 IOC 容器中,實現依賴注入的支持。
例如,我們實現一個基於數據庫的用户管理模塊 repo,該模塊包含兩個方法
package repo
type UserRepo struct {
db *sql.DB
}
func (repo *UserRepo) Login(username, password string) (*User, error) {...}
func (repo *UserRepo) GetUser(username string) (*User, error) {...}
為了使該模塊能夠正常工作,我們需要在創建 UserRepo 時,提供 db 參數,在 Glacier 中,我們可以這樣實現
package repo
type Provider struct {}
func (Provider) Register(binder infra.Binder) {
binder.Singleton(func(db *sql.DB) *UserRepo { return &UserRepo {db: db} })
}
在我們的應用創建時,使用 ins.Provider 方法註冊該模塊
ins := app.Default("1.0")
...
ins.MustSingleton(func() (*sql.DB, error) {
return sql.Open("mysql", "user:pwd@tcp(ip:3306)/dbname")
})
// 在這裏加載模塊的 Provider
ins.Provider(repo.Provider{})
...
app.MustRun(ins)
ProviderBoot
在我們使用 Provider 時,默認只需要實現一個接口方法 Register(binder infra.Binder) 即可,該方法用於將模塊的實例創建方法註冊到 Glacier 框架的 IOC 容器中。
在 Glaicer 中,還提供了一個 ProviderBoot 接口,該接口包含一個 Boot(resolver Resolver) 方法,實現該方法的模塊,可以在 Glacier 框架啓動過程中執行一些模塊自有的業務邏輯,該方法在所有的模塊全部加載完畢後執行(所有的模塊的 Register 方法都已經執行完畢),因此,系統中所有的對象都是可用的。
Boot(resolver Resolver) 方法中適合執行一些在應用啓動過程中所必須完成的一次性任務,任務應該儘快完成,以避免影響應用的啓動。
type Provider struct{}
func (Provider) Register(binder infra.Binder) {
binder.MustSingleton(func(conf *configs.Config) *grpc.Server { return ... })
}
func (Provider) Boot(resolver infra.Resolver) {
resolver.MustResolve(func(serv *grpc.Server) {
protocol.RegisterMessageServer(serv, NewEventService())
protocol.RegisterHeartbeatServer(serv, NewHeartbeatService())
})
}
DaemonProvider
模塊 Provider 的 Boot 方法是阻塞執行的,通常用於執行一些在應用啓動時需要執行的一些初始化任務,在一個應用中,所有的 Provider 的 Boot 方法是串行執行的。
而 DaemonProvider 接口則為模塊提供了異步執行的能力,模塊的 Daemon(ctx context.Context, resolver infra.Resolver) 方法是異步執行的,我們可以在這裏執行創建 web 服務器等操作。
func (Provider) Daemon(_ context.Context, app infra.Resolver) {
app.MustResolve(func(
serv *grpc.Server, conf *configs.Config, gf graceful.Graceful,
) {
listener, err := net.Listen("tcp", conf.GRPCListen)
...
gf.AddShutdownHandler(serv.GracefulStop)
...
if err := serv.Serve(listener); err != nil {
log.Errorf("GRPC Server has been stopped: %v", err)
}
})
}
ProviderAggregate
ProviderAggregate 接口為應用提供了一種能夠聚合其它模塊 Provider 的能力,在 Aggregate() []Provider方法中,我們可以定義多個我們當前模塊所依賴的其它模塊,在 Glacier 框架啓動過程中,會優先加載這裏定義的依賴模塊,然後再加載我們的當前模塊。
我們可以通過 ProviderAggregate 來創建我們自己的模塊, Aggregates() []infra.Provider 方法中返回依賴的子模塊,框架會先初始化子模塊,然後再初始化當前模塊。
// 創建自定義模塊,初始化了 Glacier 框架內置的 Web 框架
type Provider struct{}
func (Provider) Aggregates() []infra.Provider {
return []infra.Provider{
// 加載了 web 模塊,為應用提供 web 開發支持
web.Provider(
listener.FlagContext("listen"), // 從命令行參數 listen 獲取監聽端口
web.SetRouteHandlerOption(s.routes), // 設置路由規則
web.SetExceptionHandlerOption(func(ctx web.Context, err interface{}) web.Response {
log.Errorf("error: %v, call stack: %s", err, debug.Stack())
return nil
}), // Web 異常處理
),
}
}
func (Provider) routes(cc infra.Resolver, router web.Router, mw web.RequestMiddleware) {
router.Controllers(
"/api",
// 這裏添加控制器
controller.NewWelcomeController(cc),
controller.NewUserController(cc),
)
}
func (Provider) Register(app infra.Binder) {}
Service
在 Glacier 框架中,Service 代表了一個後台模塊,Service 會在框架生命週期中持續運行。要實現一個 Service,需要實現 infra.Service 接口,該接口只包含一個方法
Start() error用於啓動 Service
除了 Start 方法之外,還支持以下控制方法,不過它們都是可選的
Init(resolver Resolver) error用於 Service 的初始化,注入依賴等Stop()觸發 Service 的停止運行Reload()觸發 Service 的重新加載
以下為一個示例
type DemoService struct {
resolver infra.Resolver
stopped chan interface{}
}
// Init 可選方法,用於在 Service 啓動之前初始化一些參數
func (s *DemoService) Init(resolver infra.Resolver) error {
s.resolver = resolver
s.stopped = make(chan interface{})
return nil
}
// Start 用於 Service 的啓動
func (s *DemoService) Start() error {
for {
select {
case <-s.stopped:
return nil
default:
... // 業務代碼
}
}
}
// Stop 和 Reload 都是可選方法
func (s *DemoService) Stop() { s.stopped <- struct{}{} }
func (s *DemoService) Reload() { ... }
在我們的應用創建時,使用 app.Service 方法註冊 Service
ins := app.Create("1.0")
...
ins.Service(&service.DemoService{})
...
app.MustRun(ins)
ModuleLoadPolicy
Provider 和 Service 支持按需加載,要使用此功能,只需要讓 Provider 和 Service 實現 ShouldLoad(...) bool 方法。ShouldLoad 方法用於控制 Provider 和 Service 是否加載,支持以下幾種形式
func (Provider) ShouldLoad(...依賴) boolfunc (Provider) ShouldLoad(...依賴) (bool, error)
示例
type Provider struct{}
func (Provider) Register(binder infra.Binder) {...}
// 只有當 config.AuthType == ldap 的時候才會加載當前 Provider
func (Provider) ShouldLoad(config *config.Config) bool {
return str.InIgnoreCase(config.AuthType, []string{"ldap"})
}
注意:
ShouldLoad方法在執行時,Provider並沒有完成Register方法的執行,因此,在ShouldLoad方法的參數列表中,只能使用在應用創建時全局注入的對象實例。ins := app.Create("1.0") ... ins.Singleton(func(c infra.FlagContext) *config.Config { return ... }) ... app.MustRun(ins)
Priority
實現 infra.Priority 接口的 Provider 、 Service ,會按照 Priority() 方法的返回值大小依次加載,值越大,加載順序越靠後,默認的優先級為 1000。
type Provider struct {}
func (Provider) Register(binder infra.Binder) {...}
func (Provider) Priority() int {
return 10
}
Web 框架
Glacier 是一個應用框架,為了方便 Web 開發,也內置了一個靈活的 Web 應用開發框架。
Usage
Glaicer Web 在 Glacier 框架中是一個內置的 DaemonProvider,與其它的模塊並無不同。我們通過 web.Provider(builder infra.ListenerBuilder, options ...Option) infra.DaemonProvider 方法創建 Web 模塊。
參數 builder 用於創建 Web 服務的 listener(用於告知 Web 框架如何監聽端口),在 Glaicer 中,有以下幾種方式來創建 listener:
listener.Default(listenAddr string) infra.ListenerBuilder該構建器使用固定的 listenAddr 來創建 listenerlistener.FlagContext(flagName string) infra.ListenerBuilder該構建器根據命令行選項 flagName 來獲取要監聽的地址,以此來創建 listenerlistener.Exist(listener net.Listener) infra.ListenerBuilder該構建器使用應存在的 listener 來創建
參數 options 用於配置 web 服務的行為,包含以下幾種常用的配置
web.SetRouteHandlerOption(h RouteHandler) Option設置路由註冊函數,在該函數中註冊 API 路由規則web.SetExceptionHandlerOption(h ExceptionHandler) Option設置請求異常處理器web.SetIgnoreLastSlashOption(ignore bool) Option設置路由規則忽略最後的/,默認是不忽略的web.SetMuxRouteHandlerOption(h MuxRouteHandler) Option設置底層的 gorilla Mux 對象,用於對底層的 Gorilla 框架進行直接控制web.SetHttpWriteTimeoutOption(t time.Duration) Option設置 HTTP 寫超時時間web.SetHttpReadTimeoutOption(t time.Duration) Option設置 HTTP 讀超時時間web.SetHttpIdleTimeoutOption(t time.Duration) Option設置 HTTP 空閒超時時間web.SetMultipartFormMaxMemoryOption(max int64)設置表單解析能夠使用的最大內存web.SetTempFileOption(tempDir, tempFilePattern string) Option設置臨時文件存儲規則web.SetInitHandlerOption(h InitHandler) Option初始化階段,web 應用對象還沒有創建,在這裏可以更新 web 配置web.SetListenerHandlerOption(h ListenerHandler) Option服務初始化階段,web 服務對象已經創建,此時不能再更新 web 配置了
最簡單的使用 Web 模塊的方式是直接創建 Provider,
// Password 該結構體時 /complex 接口的返回值定義
type Password struct {
Password string `json:"password"`
}
// Glacier 框架初始化
ins := app.Default("1.0")
...
// 添加命令行參數 listen,指定默認監聽端口 :8080
ins.AddStringFlag("listen", ":8080", "http listen address")
...
ins.Provider(web.Provider(
// 使用命令行 flag 的 listener builder
listener.FlagContext("listen"),
// 設置路由規則
web.SetRouteHandlerOption(func(resolver infra.Resolver, r web.Router, mw web.RequestMiddleware) {
...
r.Get("/simple", func(ctx web.Context, gen *password.Generator) web.Response {
...
return ctx.JSON(web.M{"password": pass})
})
r.Get("/complex", func(ctx web.Context, gen *password.Generator) Password {...})
}),
))
app.MustRun(ins)
更好的方式是使用模塊化,編寫一個獨立的 Provider
type Provider struct{}
// Aggregates 實現 infra.ProviderAggregate 接口
func (Provider) Aggregates() []infra.Provider {
return []infra.Provider{
web.Provider(
confListenerBuilder{},
web.SetRouteHandlerOption(routes),
web.SetMuxRouteHandlerOption(muxRoutes),
web.SetExceptionHandlerOption(exceptionHandler),
),
}
}
// Register 實現 infra.Provider 接口
func (Provider) Register(binder infra.Binder) {}
// exceptionHandler 異常處理器
func exceptionHandler(ctx web.Context, err interface{}) web.Response {
return ctx.JSONWithCode(web.M{"error": fmt.Sprintf("%v", err)}, http.StatusInternalServerError)
}
// routes 註冊路由規則
func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) {
mws := make([]web.HandlerDecorator, 0)
// 添加 web 中間件
mws = append(mws,
mw.AccessLog(log.Module("api")),
mw.CORS("*"),
)
// 註冊控制器,所有的控制器 API 都以 `/api` 作為接口前綴
router.WithMiddleware(mws...).Controllers(
"/api",
controller.NewServerController(resolver),
controller.NewClientController(resolver),
)
}
func muxRoutes(resolver infra.Resolver, router *mux.Router) {
resolver.MustResolve(func() {
// 添加 prometheus metrics 支持
router.PathPrefix("/metrics").Handler(promhttp.Handler())
// 添加健康檢查接口支持
router.PathPrefix("/health").Handler(HealthCheck{})
})
}
// 創建自定義的 listener 構建器,從配置對象中讀取 listen 地址
type confListenerBuilder struct{}
func (l confListenerBuilder) Build(resolver infra.Resolver) (net.Listener, error) {
return listener.Default(resolver.MustGet((*config.Server)(nil)).(*config.Server).HTTPListen).Build(resolver)
}
控制器
控制器必須實現 web.Controller 接口,該接口只有一個方法
Register(router Router)用於註冊當前控制器的路由規則
type UserController struct {...}
// NewUserController 控制器創建方法,返回 web.Controller 接口
func NewUserController() web.Controller { return &UserController{...} }
// Register 註冊當前控制器關聯的路由規則
func (ctl UserController) Register(router web.Router) {
router.Group("/users/", func(router web.Router) {
router.Get("/", u.Users).Name("users:all")
router.Post("/", u.Add)
router.Post("/{id}/", u.Update)
router.Get("/{id}/", u.User).Name("users:one")
router.Delete("/{id}/", u.Delete).Name("users:delete")
})
router.Group("/users-helper/", func(router web.Router) {
router.Get("/names/", u.UserNames)
})
}
// 讀取 JSON 請求參數,直接返回實例,會以 json 的形式返回給客户端
func (ctl UserController) Add(ctx web.Context, userRepo repository.UserRepo) (*repository.User, error) {
var userForm *UserForm
if err := ctx.Unmarshal(&userForm); err != nil {
return nil, web.WrapJSONError(fmt.Errorf("invalid request: %v", err), http.StatusUnprocessableEntity)
}
ctx.Validate(userForm, true)
...
return ...
}
// 直接返回錯誤,如果 error 不為空,則返回錯誤給客户端
func (ctl UserController) Delete(ctx web.Context, userRepo repository.UserRepo) error {
userID := ctx.PathVar("id")
...
return userRepo.DeleteID(userID)
}
// 返回 web.Response,可以使用多種格式返回,如 ctx.Nil, ctx.API, ctx.JSON, ctx.JSONWithCode, ctx.JSONError, ctx.YAML, ctx.Raw, ctx.HTML, ctx.HTMLWithCode, ctx.Error 等
func (u UserController) Users(ctx web.Context, userRepo repository.UserRepo, roleRepo repository.RoleRepo) web.Response {
page := ctx.IntInput("page", 1)
perPage := ctx.IntInput("per_page", 10)
...
return ctx.JSON(web.M{
"users": users,
"next": next,
"search": web.M{
"name": name,
"phone": phone,
"email": email,
},
})
}
使用 web.Router 實例的 Controllers 方法註冊控制器。
// routes 註冊路由規則
func routes(resolver infra.Resolver, router web.Router, mw web.RequestMiddleware) {
mws := make([]web.HandlerDecorator, 0)
// 添加 web 中間件
mws = append(mws,
mw.AccessLog(log.Module("api")),
mw.CORS("*"),
)
// 註冊控制器,所有的控制器 API 都以 `/api` 作為接口前綴
router.WithMiddleware(mws...).Controllers(
"/api",
controller.NewUserController(),
)
}
事件管理
Glacier 框架提供了一個簡單的事件管理模塊,可以用於發佈和監聽應用運行中的事件,進行響應的業務處理。
通過 event.Provider(handler func(resolver infra.Resolver, listener Listener), options ...Option) infra.Provider 來初始化事件管理器。
ins.Provider(event.Provider(
func(cc infra.Resolver, listener event.Listener) {
listener.Listen(func(event CronEvent) {
log.Debug("a new cron task executed")
// 執行監聽到定時任務執行事件後要觸發的操作
})
},
// 設置事件管理器選項
event.SetStoreOption(func(cc infra.Resolver) event.Store {
// 設置使用默認的內存事件存儲
return event.NewMemoryEventStore(true, 100)
}),
))
發佈事件時,使用 Glacier 框架的依賴注入能力,獲取 event.Publisher 接口實現
ins.Async(func(publisher event.Publisher) {
for i := 0; i < 10; i++ {
publisher.Publish(CronEvent{GoroutineID: uint64(i)})
}
})
本地內存作為事件存儲後端
Glacier 內置了基於內存的事件存儲後端,説有事件的監聽器都是同步執行的。
// 設置事件管理器選項
event.SetStoreOption(func(cc infra.Resolver) event.Store {
// 設置使用默認的內存事件存儲
return event.NewMemoryEventStore(true, 100)
})
Redis 作為事件存儲後端
使用內存作為事件存儲後端時,當應用異常退出的時候,可能會存在事件的丟失,你可以使用這個基於 Redis 的事件存儲後端 redis-event-store 來獲得事件的持久化支持。
定時任務
Glacier 提供了內置的定時任務支持,使用 scheduler.Provider 來實現。
type Provider struct{}
func (Provider) Register(binder infra.Binder) {...}
func (Provider) Aggregates() []infra.Provider {
return []infra.Provider{
// 加載 scheduler 定時任務模塊
scheduler.Provider(
func(resolver infra.Resolver, creator scheduler.JobCreator) {
// 添加一個名為 test-job 的任務,每隔 10s 執行一次
_ = cr.Add("test-job", "@every 10s", TestJob)
// 添加一個名稱為 test-timeout-job 的任務,每隔 5s 執行一次
// 通過 AddAndRunOnServerReady 添加的任務會在服務啓動時先執行一次
_ = creator.AddAndRunOnServerReady(
"test-timeout-job",
"@every 5s",
// 使用 scheduler.WithoutOverlap 包裝的函數,當前一次調度還沒有執行完畢,本次調度的時間已到,本次調度將會被取消
scheduler.WithoutOverlap(TestTimeoutJob).SkipCallback(func() {
... // 當前一個任務還沒有執行完畢時,當前任務會被跳過,跳過時會觸發該函數的執行
}),
)
},
),
}
}
scheduler.Provider 支持分佈式鎖,通過 SetLockManagerOption 選項可以指定分佈式鎖的實現,以滿足任務在一組服務器中只會被觸發一次的邏輯。
scheduler.Provider(
func(resolver infra.Resolver, creator scheduler.JobCreator) {...},
// 設置分佈式鎖
scheduler.SetLockManagerOption(func(resolver infra.Resolver) scheduler.LockManagerBuilder {
// get redis instance
redisClient := resolver.MustGet(&redis.Client{}).(*redis.Client)
return func(name string) scheduler.LockManager {
// create redis lock
return redisLock.New(redisClient, name, 10*time.Minute)
}
}),
)
注意:Glacier 框架沒有內置分佈式鎖的實現,在 mylxsw/distribute-locks 實現了一個簡單的基於 Redis 的分佈式鎖實現,可以參考使用。
日誌
在 Glacier 中,默認使用 asteria 作為日誌框架,asteria 是一款功能強大、靈活的結構化日誌框架,支持多種日誌輸出格式以及輸出方式,支持為日誌信息添加上下文信息。
最簡單的方式是通過 log.SetDefaultLogger(logger infra.Logger) 方法為 Glacier 框架設置默認的日誌處理器,
// import "github.com/mylxsw/glacier/log"
// 默認設置,使用 asteria 日誌框架
// import asteria "github.com/mylxsw/asteria/log"
log.SetDefaultLogger(asteria.Module("glacier"))
// 使用標準庫中的日誌包,Glacier 對標準庫日誌包進行了簡單封裝
log.SetDefaultLogger(log.StdLogger())
當然,如果使用了 starter 模板項目創建的應用,也可以使用 WithLogger(logger infra.Logger) 方法來設置日誌處理器。
ins := app.Default("1.0")
...
// 設置使用標準庫日誌包,不輸出 DEBUG 日誌
ins.WithLogger(log.StdLogger(log.DEBUG))
...
除了默認的 asteria 日誌庫以及 Glacier 自帶的 StdLogger 之外,還可以使用其它第三方的日誌包,只需要簡單的封裝,實現 infra.Logger 接口即可。
type Logger interface {
Debug(v ...interface{})
Debugf(format string, v ...interface{})
Info(v ...interface{})
Infof(format string, v ...interface{})
Error(v ...interface{})
Errorf(format string, v ...interface{})
Warning(v ...interface{})
Warningf(format string, v ...interface{})
// Critical 關鍵性錯誤,遇到該日誌輸出時,應用直接退出
Critical(v ...interface{})
// Criticalf 關鍵性錯誤,遇到該日誌輸出時,應用直接退出
Criticalf(format string, v ...interface{})
}
Eloquent ORM
Eloquent ORM 是為 Go 開發的一款數據庫 ORM 框架,它的設計靈感來源於著名的 PHP 開發框架 Laravel,支持 MySQL 等數據庫。
項目地址為 mylxsw/eloquent,可以配合 Glacier 框架使用。
平滑退出
Glacier 支持平滑退出,當我們按下鍵盤的 Ctrl+C 時(接收到 SIGINT, SIGTERM, Interrupt 等信號), Glacier 將會接收到關閉的信號,然後觸發應用的關閉行為。默認情況下,我們的應用會立即退出,我們可以通過 starter 模板創建的應用上啓用平滑支持選項 WithShutdownTimeoutFlagSupport(timeout time.Duration) 來設置默認的平滑退出時間
ins := app.Create("1.0")
ins.WithShutdownTimeoutFlagSupport(5 * time.Second)
...
// Provider 中獲取 `gf.Graceful` 實例,註冊關閉時的處理函數
resolver.MustResolve(func(gf graceful.Graceful) {
gf.AddShutdownHandler(func() {
...
})
})
第三方框架集成
- giris: Iris Web Framework 適配
示例項目
- Example 使用示例
- WebDAV Server 一款支持 LDAP 作為用户數據庫的 WebDAV 服務器
- Adanos Alert 一個功能強大的開源告警平台,通過事件聚合機制,為監控系統提供釘釘、郵件、HTTP、JIRA、語音電話等告警方式的支持
- Healthcheck 為應用服務提供健康檢查告警支持
- Sync 跨服務器文件同步服務
- Tech Share 一個用於中小型團隊內部技術分享管理的 Web 應用
- Universal Exporter 一個通用的 Prometheus 維度工具,目前支持從數據庫中查詢生成 Metric 數據
- Graphviz Server 一個 Web 服務,封裝了對 Graphviz 的接口調用,實現通過 Web API 的方式生成 Graphviz 圖形
- MySQL Guard 用於 MySQL 長事務檢測殺死和死鎖告警
- Password Server 一個生成隨機密碼的簡單 web 服務器