簡介
基於 wasm 機制,Higress 提供了優秀的可擴充套件性,使用者可以基於 Go/C++/Rust 編寫 wasm 外掛,自定義請求處理邏輯,滿足使用者的個性化需求,目前外掛已經支援 redis 呼叫,使得使用者能夠編寫有狀態的外掛,進一步提高了 Higress 的擴充套件能力。
文件在外掛中呼叫 Redis[1]中提供了完整的閘道器透過外掛呼叫 Redis 的例子,包括阿里雲 Redis 例項建立與配置、外掛程式碼編寫、外掛上傳與配置、測試樣例等流程。接下來本文重點介紹幾個基於 Redis 的外掛。
多閘道器全侷限流
閘道器已經提供了 sentinal 限流[2],能夠有效保護後端業務應用。透過 redis 外掛限流,使用者可以實現多閘道器的全侷限額管理。
以下為外掛程式碼示例,在請求頭階段檢查當前時間內請求次數,如果超出配額,則直接返回 429 響應。
func onHttpRequestHeaders(ctx wrapper.HttpContext, config RedisCallConfig, log wrapper.Log) types.Action {
now := time.Now()
minuteAligned := now.Truncate(time.Minute)
timeStamp := strconv.FormatInt(minuteAligned.Unix(), 10)
// 如果 redis api 返回的 err != nil,一般是由於閘道器找不到 redis 後端服務,請檢查是否誤刪除了 redis 後端服務
err := config.client.Incr(timeStamp, func(response resp.Value) {
if response.Error() != nil {
log.Errorf("call redis error: %v", response.Error())
proxywasm.ResumeHttpRequest()
} else {
ctx.SetContext("timeStamp", timeStamp)
ctx.SetContext("callTimeLeft", strconv.Itoa(config.qpm-response.Integer()))
if response.Integer() == 1 {
err := config.client.Expire(timeStamp, 60, func(response resp.Value) {
if response.Error() != nil {
log.Errorf("call redis error: %v", response.Error())
}
proxywasm.ResumeHttpRequest()
})
if err != nil {
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
proxywasm.ResumeHttpRequest()
}
} else {
if response.Integer() > config.qpm {
proxywasm.SendHttpResponse(429, [][2]string{{"timeStamp", timeStamp}, {"callTimeLeft", "0"}}, []byte("Too many requests\n"), -1)
} else {
proxywasm.ResumeHttpRequest()
}
}
}
})
if err != nil {
// 由於呼叫redis失敗,放行請求,記錄日誌
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
return types.ActionContinue
} else {
// 請求hold住,等待redis呼叫完成
return types.ActionPause
}
}
外掛配置如下:
測試結果如下:
結合通義千問實現 token 限流
對於提供 AI 應用服務的開發者來說,使用者的 token 配額管理是一個非常關鍵的功能,以下例子展示瞭如何透過閘道器外掛實現對通義千問後端服務的 token 限流功能。
首先需要申請通義千問的 API 訪問,可參考此連結[3]。之後在 MSE 閘道器配置相應服務以及路由,如下所示:
編寫外掛程式碼,外掛中,在響應 body 階段去寫入該請求使用的 token 額度,在處理請求頭階段去讀 redis 檢查當前剩餘 token 額度,如果已經沒有 token 額度,則直接返回響應,中止請求。
func onHttpRequestBody(ctx wrapper.HttpContext, config TokenLimiterConfig, body []byte, log wrapper.Log) types.Action {
now := time.Now()
minuteAligned := now.Truncate(time.Minute)
timeStamp := strconv.FormatInt(minuteAligned.Unix(), 10)
config.client.Get(timeStamp, func(response resp.Value) {
if response.Error() != nil {
defer proxywasm.ResumeHttpRequest()
log.Errorf("Error occured while calling redis")
} else {
tokenUsed := response.Integer()
if config.tpm < tokenUsed {
proxywasm.SendHttpResponse(429, [][2]string{{"timeStamp", timeStamp}, {"TokenLeft", fmt.Sprint(config.tpm - tokenUsed)}}, []byte("No token left\n"), -1)
} else {
proxywasm.ResumeHttpRequest()
}
}
})
return types.ActionPause
}
func onHttpResponseBody(ctx wrapper.HttpContext, config TokenLimiterConfig, body []byte, log wrapper.Log) types.Action {
now := time.Now()
minuteAligned := now.Truncate(time.Minute)
timeStamp := strconv.FormatInt(minuteAligned.Unix(), 10)
tokens := int(gjson.ParseBytes(body).Get("usage").Get("total_tokens").Int())
config.client.IncrBy(timeStamp, tokens, func(response resp.Value) {
if response.Error() != nil {
defer proxywasm.ResumeHttpResponse()
log.Errorf("Error occured while calling redis")
} else {
if response.Integer() == tokens {
config.client.Expire(timeStamp, 60, func(response resp.Value) {
defer proxywasm.ResumeHttpResponse()
if response.Error() != nil {
log.Errorf("Error occured while calling redis")
}
})
}
}
})
return types.ActionPause
}
測試結果如下:
基於 cookie 的快取、容災以及會話管理
除了以上兩個限流的例子,基於 Redis 可以實現更多的外掛對閘道器進行擴充套件。例如基於 cookie 來做快取、容災以及會話管理等功能。
- 快取&容災:基於使用者 cookie 資訊快取請求應答,一方面能夠減輕後端服務壓力,另一方面,當後端服務不可用時,能夠實現容災效果。
- 會話管理:使用 Redis 儲存使用者的認證鑑權資訊,當請求到來時,先訪問 redis 檢視當前使用者是否被授權訪問,如果未被授權再去訪問認證鑑權服務,可以減輕認證鑑權服務的壓力。
func onHttpRequestHeaders(ctx wrapper.HttpContext, config HelloWorldConfig, log wrapper.Log) types.Action {
cookieHeader, err := proxywasm.GetHttpRequestHeader("cookie")
if err != nil {
proxywasm.LogErrorf("error getting cookie header: %v", err)
// 實現自己的業務邏輯
}
// 根據自己需要對cookie進行處理
cookie := CookieHandler(cookieHeader)
config.client.Get(cookie, func(response resp.Value) {
if response.Error() != nil {
log.Errorf("Error occured while calling redis")
proxywasm.ResumeHttpRequest()
} else {
// 實現自己的業務邏輯
proxywasm.ResumeHttpRequest()
}
})
return types.ActionPause
}
總結
Higress 透過支援 redis 呼叫,大大增強了外掛的能力,使外掛功能具有更廣闊的想象空間,更加能夠適應開發者多樣的個性化需求,如果大家有更多關於 Higress 的想法與建議,歡迎與我們聯絡!
相關連結:
[1] 在外掛中呼叫 Redis
https://help.aliyun.com/zh/mse/user-guide/develop-gateway-plug-ins-by-using-the-go-language?spm=a2c4g.11186623.0.0.45a53597EVVAC0#5e5a601af18al
[2] sentinal 限流
https://help.aliyun.com/zh/mse/user-guide/configure-a-throttling-policy?spm=a2c4g.11186623.0.i4
[3] 連結
https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.i4#602895ef3dtl1
作者: 鈺誠
原文連結
本文為阿里雲原創內容,未經允許不得轉載。