B站微服務框架Kratos詳細教程(2)- HTTP服務

背景

在像微服務這樣的分佈式架構中,常常會有一些需求須要你調用多個服務,可是還須要確保服務的安全性、統一化每次的 請求日誌或者追蹤用戶完整的行爲等等。
你可能須要一個框架來幫助你實現這些功能。好比說幫你在一些關鍵路徑的請求上配置必要的鑑權 或超時策略。那樣服務間的調用會被多層中間件所過濾並檢查,確保總體服務的穩定性。
git

設計目標

  • 性能優異,不該該摻雜太多業務邏輯的成分
  • 方便開發使用,開發對接的成本應該儘量地小
  • 後續鑑權、認證等業務邏輯的模塊應該能夠經過業務模塊的開發接入該框架內
  • 默認配置已是 production ready 的配置,減小開發與線上環境的差別性

kratos的http服務架構-blademaster

blademaster設計的整套HTTP框架參考了gin,去除 gin 中不須要的部分邏輯。
blademaster由幾個很是精簡的內部模塊組成。其中Router用於根據請求的路徑分發請求,Context包含了一個完整的請求信息,Handler則負責處理傳入的ContextHandlers爲一個列表,一個串一個地執行。
全部的middlerware均以Handler的形式存在,這樣能夠保證blademaster自身足夠精簡且擴展性足夠強。

github

bladmaster架構

blademaster處理請求的模式很是簡單,大部分的邏輯都被封裝在了各類Handler中。通常而言,業務邏輯做爲最後一個Handlershell

正常狀況下每一個Handler按照順序一個一個串行地執行下去,可是Handler中也能夠中斷整個處理流程,直接輸出Response。這種模式常被用於校驗登錄的middleware中:一旦發現請求不合法,直接響應拒絕。數據庫

請求處理的流程中也可使用Render來輔助渲染Response,好比對於不一樣的請求須要響應不一樣的數據格式JSONXML,此時可使用不一樣的Render來簡化邏輯。json

快速開始

建立http項目:api

kratos new httpdemo --http

能夠指定名字和目錄:瀏覽器

kratos new kratos-demo -o YourName -d YourPath

建立http項目
建立項目成功後,進入 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:bash

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

如下是 blademasterContext 對象結構體聲明的代碼片斷:

// 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 實例
}
  • 首先 blademasterContext 結構體中會 嵌入一個標準庫中的 Context 實例,bm 中的 Context 也是經過該實例來實現標準庫中的 Context 接口。
  • blademaster 會使用配置的 server timeout (默認1s) 做爲一次請求整個過程當中的超時時間,使用該context調用dao作數據庫、緩存操做查詢時均會將該超時時間傳遞下去,一旦抵達deadline,後續相關操做均會返回context deadline exceeded
  • RequestWriter 字段用於獲取當前請求的與輸出響應。
  • index 和 handlers 用於 handler 的流程控制;handlers 中存儲了當前請求須要執行的全部 handlerindex 用於標記當前正在執行的 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

Handler處理流程
初次接觸blademaster的用戶可能會對其Handler的流程處理產生不小的疑惑,實際上bmHandler對處理很是簡單:

  • Router模塊中預先註冊的middleware與其餘Handler合併,放入Contexthandlers字段,並將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

相關文章
相關標籤/搜索