Higress 基于自定义插件访问 Redis

简介

基于 wasm 机制,Higress 提供了优秀的可扩展性,用户可以基于 Go/C++/Rust 编写 wasm 插件,自定义请求处理逻辑,满足用户的个性化需求,目前插件已经支持 redis 调用,使得用户能够编写有状态的插件,进一步提高了 Higress 的扩展能力。

文档在插件中调用 Redis1中提供了完整的网关通过插件调用 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
}

测试结果如下:

除了以上两个限流的例子,基于 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

作者: 钰诚

原文链接

本文为阿里云原创内容,未经允许不得转载。

相关推荐
吳所畏惧4 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
张忠琳4 小时前
【NVIDIA】 NVIDIA Container Toolkit v1.19.1 — OCI 模块超深度分析之三
云原生·容器·架构·kubernetes·nvidia
赵丙双4 小时前
Redis 概率相关的数据类型
redis
阿里云云原生5 小时前
让 AI Agent 看见正在发生的业务,阿里云 EventHouse 正式商业化
云原生
AOwhisky7 小时前
Python 学习笔记(第十五期)——运维自动化(下·后篇):堡垒机实战——paramiko高阶篇
运维·python·学习·云原生·自动化·运维开发
REST_30278 小时前
告别Excel孤岛:一款任务链路可视化工具带来的真实改变
大数据·微服务·云计算·编辑器·excel·动态规划
风曦Kisaki8 小时前
#企业级docker私有仓库构建:harbor仓库与阿里云镜像仓库
阿里云·docker·容器
Y3815326629 小时前
SERP API + Redis 缓存层:4 种方案对比与选型
数据库·redis·缓存
微三云 - 廖会灵 (私域系统开发)9 小时前
电商系统从单体到微服务拆分实践:拆什么、怎么拆、拆完后怎么办
微服务·云原生·架构
在水一缸9 小时前
苹果AI国行版过审背后的技术架构深度解析:端侧模型与私有云计算的融合实践
人工智能·架构·云计算·技术架构·苹果ai·端侧模型·私有云计算