By Yucheng
This article introduces Higress, a gateway that leverages WebAssembly (WASM) for edge computing and allows users to create plug-ins in Go, C++, or Rust to expand its functionality. It specifically highlights how Redis plugins can be utilized for implementing sophisticated features such as rate limiting, caching, and session management.
Higress provides excellent extensibility based on the WASM mechanism. Users can write WASM plugins using Go, C++, or Rust and customize the request processing logic to meet their personalized needs. Currently, the plugins have incorporated support for Redis calls, enabling users to write stateful plugins and further enhancing the extensibility of Higress.
The documentation Use a Plug-in to Call ApsaraDB for Redis Services [1] provides a complete example of how to use a gateway to call ApsaraDB for Redis by using plug-ins, including the processes of creating and configuring an ApsaraDB for Redis instance, writing plug-in code, uploading and configuring plug-ins, and testing samples. Next, this article focuses on several plug-ins based on ApsaraDB for Redis.
The gateway has already provided the sentinal throttling [2], which can effectively protect backend business applications. The ApsaraDB for Redis plug-in throttling allows you to implement global quota management for multiple gateways.
The following code provides an example of how to use the plug-in to check the number of requests in the current period at the request header stage. If the number of requests exceeds the quota, the plug-in directly returns a 429 response.
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)
// If "err != nil" is returned by the ApsaraDB for Redis API, the gateway fails to find the ApsaraDB for Redis backend service. In this case, check whether the ApsaraDB for Redis backend service is deleted.
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 {
// Since the calling of the ApsaraDB for Redis service fails, allow the request to pass and log this event.
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
return types.ActionContinue
} else {
// Hold the request to wait for completing the calling of the ApsaraDB for Redis service.
return types.ActionPause
}
}
The following figure shows the plug-in configuration.
Test results:
For developers who provide AI application services, user token quota management is a very critical feature. The following example shows how to use the gateway plug-in to implement token throttling for the backend services of Tongyi Qianwen (Qwen).
First of all, you need to apply for Qwen API access. For more information, see the reference [3]. Then, configure the corresponding services and routes on the MSE gateway as follows:
Write the plug-in code. In the plug-in, write the token quota used by the request during the response to the body stage, and read ApsaraDB for Redis to check the current remaining token quota during the processing of requests stage. If there is no token quota, the response is directly returned and the request is terminated.
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
}
Test results:
In addition to the preceding two throttling examples, more plug-ins can be implemented based on ApsaraDB for Redis to extend the gateway. For example, cookie-based caching, disaster recovery, and session management can be implemented.
• Caching and disaster recovery: caching request responses based on user cookie information can reduce the pressure on backend services and implement disaster recovery when backend services are unavailable.
• Session management: ApsaraDB for Redis is used to store user authentication information. When a request arrives, ApsaraDB for Redis is first accessed to check whether the current user is authorized to access. If the current user is not authorized, the authentication service is then accessed so that the pressure on the authentication service can be reduced.
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)
// Implement your business logic.
}
// Process the cookie as needed.
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 {
// Implement your business logic.
proxywasm.ResumeHttpRequest()
}
})
return types.ActionPause
}
Higress has significantly expanded the capabilities of its plug-ins by enabling ApsaraDB for Redis calls, providing more flexibility to cater to the diverse personalized needs of developers. If you have further ideas or suggestions for Higress, feel free to reach out to us!
[1] Use a Plug-in to Call ApsaraDB for Redis Services
[2] Sentinal Throttling
[3] Link (Currently available in Chinese only)
Better Performance and Cost-effectiveness: Migrating the Self-built ELK to SLS
Serverless Cost Optimization: Knative Supports Preemptible Instances
495 posts | 48 followers
FollowAlibaba Cloud Native Community - April 11, 2024
Alibaba Cloud Native Community - November 15, 2023
Alibaba Cloud Native Community - February 2, 2024
Alibaba Cloud Native Community - September 12, 2023
Alibaba Cloud Native Community - July 20, 2023
Alibaba Cloud Native Community - April 4, 2023
495 posts | 48 followers
FollowA key value database service that offers in-memory caching and high-speed access to applications hosted on the cloud
Learn MoreMSE provides a fully managed registration and configuration center, and gateway and microservices governance capabilities.
Learn MoreApsaraDB RDS for MariaDB supports multiple storage engines, including MySQL InnoDB to meet different user requirements.
Learn MoreApsaraDB Dedicated Cluster provided by Alibaba Cloud is a dedicated service for managing databases on the cloud.
Learn MoreMore Posts by Alibaba Cloud Native Community