Higress 基于自定义插件访问 Redis
简介
基于 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
[2] sentinal 限流
https://help.aliyun.com/zh/mse/user-guide/configure-a-throttling-policy?spm=a2c4g.11186623.0.i4
[3] 链接
作者: 钰诚
本文为阿里云原创内容,未经允许不得转载。
Higress 基于自定义插件访问 Redis的更多相关文章
- 基于jQuery的自定义插件:实现整屏分页转换的功能
		动态创建jQuery插件 一.实现功能: 1.基本功能:自适应式整屏分页功能的实现 2.通过鼠标点击标签页转换分页,支持键盘上下左右键的转换分页,同样支持 鼠标滚轮上下滑动转换分页 3.切屏时的动画效 ... 
- BrnShop开源网上商城第四讲:自定义插件
		重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看.官网地址:www.brnshop.com. 好了现在进入今天的 ... 
- BrnShop:自定义插件
		BrnShop开源网上商城第四讲:自定义插件 重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看.官网地址:www ... 
- vue_简介_渐进式 js 框架_内置指令_自定义指令_自定义插件
		vue 尤雨溪 华裔 Google 工程师 遵循 MVVM 模式 编码简洁,体积小,运行效率高,适合 移动 / PC 端 开发 动态构建用户界面: 异步获取后台数据,展现到页面 渐进式 js 框架 渐 ... 
- Flink去重统计-基于自定义布隆过滤器
		一.背景说明 在Flink中对流数据进行去重计算是常有操作,如流量域对独立访客之类的统计,去重思路一般有三个: 基于Hashset来实现去重 数据存在内存,容量小,服务重启会丢失. 使用状态编程Val ... 
- 使用Rest访问Redis中的数据
		原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com 大家在用Redis保存数据的时候,有不同的序列化方式.用得最多应该还是JSON,有一些场景我们需要以Http请 ... 
- jQuery自定义插件
		jQuery自定义插件 jQuery自定义插件按照功能分类,可以分为三类, 1>封装对象方法的插件,(也就是基于某个DOM元素的jQuery对象,局部的) 2>封装全局函数的插件, ( ... 
- cordova3.X 运用grunt生成plugin自定义插件骨架
		Cordova提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头.麦克风等.Cordova还提供了一组统一的JavaScript类库,以及为这些 ... 
- gulp如何自定义插件
		gulp是基于”流“的构建工具,上层流的输出就是下层流的输入,为了更好的支持链式操作,可以使用through2或者map-stream这两个库来对node stream做一层包装 这里,我们就使用th ... 
- jedis访问redis学习笔记
		最近在学习redis,在网上查了些文章,利用他人已有的知识,总结写下了这篇文章,大部分内容还是引用别人的文章内容.经过测试发现spring-data-redis现在有的版本只能支持reids 2.6和 ... 
随机推荐
- 《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南 - 第7章
			本章勘误: 暂无,等待细心的你告诉我哦. 本章注解: 暂无 本章释疑: 暂无,等待你的提问 致谢: MVP 林德熙 MVP 吕毅 sPhinX 相关链接 试读记录 
- 花了100块大洋搞懂 ipv6的用户如何访问ipv4 服务器
			大家好,今天蓝胖子花了100多块搞懂了 ipv6的用户如何访问ipv4 服务器,将收获与大家分享下. ipv4和ipv6的协议栈不同,这意味着,其对应的ip包的封装和解析不同,那么只支持ipv4的机器 ... 
- Lab2:System Call
			trace 该系统调用程序,可以跟踪其他的系统调用命令,该系统调用的形参为一个整数掩码.其具体实参为1 << sys_call所得到的整数值,sys_call是一个系统调用指令在内核中定义 ... 
- 使用fiddler抓取HTTPS的数据包(抓取App端的数据包)
			众所周知,我们在做接口测试的时候有两种情况: 第一种是先拿到接口测试规范文档,再去做接口测试. 第二种是没有接口文档,只有通过自己抓包. 那么说到抓包,就不得不说抓包工具,对于浏览器web端,我们只需 ... 
- 从 findbugs-maven-plugin 到 spotbugs-maven-plugin 帮你找到代码中的bug
			一.findbugs-maven-plugin 介绍: Status: Since Findbugs is no longer maintained, please use Spotbugs whic ... 
- 冲刺 NOIP2024 之动态规划专题
			专题链接 B - Birds \(3.19\) . 混合背包 \(DP\) . 定义 \(f_{i,j}\) 表示取到鸟巢 \(i\) ,获得 \(j\) 只小鸟时所剩的魔力值. 显然有 \(f_{0 ... 
- Numpy线性计算
			Numpy内置方法以及numpy.linalg模块可实现矩阵乘法.矩阵分解.矩阵行列式等线性代数的计算. In [1]: import numpy as np In [2]: x = np.arang ... 
- HarmonyOS系统级推送服务,打造消息通知新体验
			8月4日,第五届华为开发者大会 2023(HDC.Together)再次启航.在本次大会上,华为为广大用户带来了HarmonyOS 4全新升级的体验,同时,针对HarmonyOS应用的开发,此次也全面 ... 
- 解密方舟的高性能内存回收技术——HPP GC
			原文:https://mp.weixin.qq.com/s/o8uuP1XViIyviveL4m-8ZQ,点击链接查看更多技术内容. 众所周知,内存是操作系统的一项重要资源,直接影响系统性能.而在应用 ... 
- HTTP 使用指南
			0x1 初识 HTTP 协议 网页加载流程 graph LR A(user 输入网址)==>B(browser 进程) B==>C(处理输入信息) B-->D(页面加载完成) C== ... 
