背景
在像微服務這樣的分布式架構中,經常會有一些需求需要你呼叫多個服務,但是還需要確保服務的安全性、統一化每次的 請求日志或者追蹤用戶完整的行為等等,
你可能需要一個框架來幫助你實作這些功能,比如說幫你在一些關鍵路徑的請求上配置必要的鑒權 或超時策略,那樣服務間的呼叫會被多層中間件所過濾并檢查,確保整體服務的穩定性,
設計目標
- 性能優異,不應該摻雜太多業務邏輯的成分
- 方便開發使用,開發對接的成本應該盡可能地小
- 后續鑒權、認證等業務邏輯的模塊應該可以通過業務模塊的開發接入該框架內
- 默認配置已經是 production ready 的配置,減少開發與線上環境的差異性
kratos的http服務架構-blademaster
blademaster設計的整套HTTP框架參考了gin,去除 gin 中不需要的部分邏輯,
blademaster由幾個非常精簡的內部模塊組成,其中Router用于根據請求的路徑分發請求,Context包含了一個完整的請求資訊,Handler則負責處理傳入的Context,Handlers為一個串列,一個串一個地執行,
所有的middlerware均以Handler的形式存在,這樣可以保證blademaster自身足夠精簡且擴展性足夠強,

blademaster處理請求的模式非常簡單,大部分的邏輯都被封裝在了各種Handler中,一般而言,業務邏輯作為最后一個Handler,
正常情況下每個Handler按照順序一個一個串行地執行下去,但是Handler中也可以中斷整個處理流程,直接輸出Response,這種模式常被用于校驗登陸的middleware中:一旦發現請求不合法,直接回應拒絕,
請求處理的流程中也可以使用Render來輔助渲染Response,比如對于不同的請求需要回應不同的資料格式JSON、XML,此時可以使用不同的Render來簡化邏輯,
快速開始
創建http專案:
kratos new httpdemo --http
可以指定名字和目錄:
kratos new kratos-demo -o YourName -d YourPath

創建專案成功后,進入 internal/server/http 目錄下,默認生成的 server.go 模板:
package http
import (
"net/http"
pb "httpdemo/api"
"httpdemo/internal/model"
"github.com/go-kratos/kratos/pkg/conf/paladin"
"github.com/go-kratos/kratos/pkg/log"
bm "github.com/go-kratos/kratos/pkg/net/http/blademaster"
)
var svc pb.DemoServer
// New new a bm server.
func New(s pb.DemoServer) (engine *bm.Engine, err error) {
var (
cfg bm.ServerConfig
ct paladin.TOML
)
if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil {
return
}
if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {
return
}
svc = s
engine = bm.DefaultServer(&cfg)
pb.RegisterDemoBMServer(engine, s)
initRouter(engine)
err = engine.Start()
return
}
//路由
func initRouter(e *bm.Engine) {
e.Ping(ping) // engine自帶的"/ping"介面,用于負載均衡檢測服務健康狀態
g := e.Group("/httpdemo") // // e.Group 創建一組 "/httpdemo" 起始的路由組
{
g.GET("/start", howToStart) // // g.GET 創建一個 "httpdemo/start" 的路由,使用GET方式請求,默認處理Handle r為howToStart方法
}
}
//engine自帶Ping方法,用于設定 /ping 路由的handler,該路由統一提供于負載均衡服務做健康檢測,服務是否健康,可自 定義 ping handler 進行邏輯判斷,如檢測DB是否正常等,
func ping(ctx *bm.Context) {
if _, err := svc.Ping(ctx, nil); err != nil {
log.Error("ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}
// bm的handler方法.
func howToStart(c *bm.Context) {
k := &model.Kratos{
Hello: "Golang 大法好 !!!",
}
c.JSON(k, nil)
}
默認路由
默認路由有:
- /metrics 用于prometheus資訊采集
- /metadata 可以查看所有注冊的路由資訊
打開瀏覽器訪問:
路徑引數
我們在路由中增加一些內容,增加一個handler方法showParam:
func initRouter(e *bm.Engine) {
e.Ping(ping)
g := e.Group("/httpdemo")
{
g.GET("/start", howToStart)
// 路徑引數有兩個特殊符號":"和"*"
// ":" 跟在"/"后面為引數的key,匹配兩個/中間的值 或 一個/到結尾(其中不再包含/)的值
// "*" 跟在"/"后面為引數的key,匹配從 /*開始到結尾的所有值,所有*必須寫在最后且無法多個
// NOTE:這是不被允許的,會和 /start 沖突
// g.GET("/:xxx")
// NOTE: 可以拿到一個key為name的引數,注意只能匹配到/param1/soul,無法匹配/param1/soul/hao(該路徑會404)
g.GET("/param1/:name", showParam)
// NOTE: 可以拿到多個key引數,注意只能匹配到/param2/soul/male/hello,無法匹配/param2/soul或/param2/soul/hello
g.GET("/param2/:name/:gender/:say", showParam)
// NOTE: 可以拿到一個key為name的引數 和 一個key為action的路徑,
// NOTE: 如/params3/soul/hello,action的值為"/hello"
// NOTE: 如/params3/soul/hello/hi,action的值為"/hello/hi"
// NOTE: 如/params3/soul/hello/hi/,action的值為"/hello/hi/"
g.GET("/param3/:name/*action", showParam)
}
}
func showParam(c *bm.Context) {
name, _ := c.Params.Get("name")
gender, _ := c.Params.Get("gender")
say, _ := c.Params.Get("say")
action, _ := c.Params.Get("action")
path := c.RoutePath // NOTE: 獲取注冊的路由原始地址,如: /httpdemo/param1/:name
c.JSONMap(map[string]interface{}{
"name": name,
"gender": gender,
"say": say,
"action": action,
"path": path,
}, nil)
}
打開瀏覽器訪問:
http://localhost:8000/httpdemo/param2/Soul/male/hello
輸出內容:
{
"action": "",
"code": 0,
"gender": "male",
"message": "0",
"name": "Soul",
"path": "/httpdemo/param2/:name/:gender/:say",
"say": "hello"
}
Context
以下是 blademaster 中 Context 物件結構體宣告的代碼片段:
// Context is the most important part. It allows us to pass variables between
// middleware, manage the flow, validate the JSON of a request and render a
// JSON response for example.
type Context struct {
context.Context //嵌入一個標準庫中的 Context實體,對應bm中的 Context,也是通過該實體來實作標準庫中的 Context 介面
Request *http.Request //獲取當前請求資訊
Writer http.ResponseWriter //輸出回應請求資訊
// flow control
index int8 //標記當前正在執行的 handler 的索引位
handlers []HandlerFunc //中存盤了當前請求需要執行的所有 handler
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{} //在 handler 之間傳遞一些額外的資訊
Error error //存盤整個請求處理程序中的錯誤
method string //檢查當前請求的 Method 是否與預定義的相匹配
engine *Engine //指向當前 blademaster 的 Engine 實體
}
- 首先
blademaster的Context結構體中會 嵌入一個標準庫中的Context實體,bm 中的 Context 也是通過該實體來實作標準庫中的Context介面, - blademaster 會使用配置的 server timeout (默認1s) 作為一次請求整個程序中的超時時間,使用該context呼叫dao做資料庫、快取操作查詢時均會將該超時時間傳遞下去,一旦抵達deadline,后續相關操作均會回傳
context deadline exceeded, Request和Writer欄位用于獲取當前請求的與輸出回應,- index 和 handlers 用于 handler 的流程控制;handlers 中存盤了當前請求需要執行的所有
handler,index用于標記當前正在執行的 handler 的索引位, Keys用于在handler之間傳遞一些額外的資訊,Error用于存盤整個請求處理程序中的錯誤,method用于檢查當前請求的Method是否與預定義的相匹配,engine欄位指向當前blademaster的 Engine 實體,
以下為 Context 中所有的公開的方法:
// 用于 Handler 的流程控制
func (c *Context) Abort()
func (c *Context) AbortWithStatus(code int)
func (c *Context) Bytes(code int, contentType string, data ...[]byte)
func (c *Context) IsAborted() bool
func (c *Context) Next()
// 用戶獲取或者傳遞請求的額外資訊
func (c *Context) RemoteIP() (cip string)
func (c *Context) Set(key string, value interface{})
func (c *Context) Get(key string) (value interface{}, exists bool)
// 用于校驗請求的 payload
func (c *Context) Bind(obj interface{}) error
func (c *Context) BindWith(obj interface{}, b binding.Binding) error
// 用于輸出回應
func (c *Context) Render(code int, r render.Render)
func (c *Context) Redirect(code int, location string)
func (c *Context) Status(code int)
func (c *Context) String(code int, format string, values ...interface{})
func (c *Context) XML(data interface{}, err error)
func (c *Context) JSON(data interface{}, err error)
func (c *Context) JSONMap(data map[string]interface{}, err error)
func (c *Context) Protobuf(data proto.Message, err error)
所有方法基本上可以分為三類:
- 流程控制
- 額外資訊傳遞
- 請求處理
- 回應處理
Handler

初次接觸blademaster的用戶可能會對其Handler的流程處理產生不小的疑惑,實際上bm對Handler對處理非常簡單:
- 將
Router模塊中預先注冊的middleware與其他Handler合并,放入Context的handlers欄位,并將index欄位置0 - 然后通過
Next()方法一個個執行下去,部分middleware可能想要在程序中中斷整個流程,此時可以使用Abort()方法提前結束處理 - 有些
middleware還想在所有Handler執行完后再執行部分邏輯,此時可以在自身Handler中顯式呼叫Next()方法,并將這些邏輯放在呼叫了Next()方法之后
性能分析
啟動時默認監聽了2333埠用于pprof資訊采集,如:
go tool pprof http://127.0.0.1:8000/debug/pprof/profile
改變埠可以使用flag,如:-http.perf=tcp://0.0.0.0:12333
最后附上教程示例專案原始碼:
https://download.csdn.net/download/uisoul/12822504
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/1387.html
標籤:其他
