首页 > 数据库 >Higress 基于自定义插件访问 Redis

Higress 基于自定义插件访问 Redis

时间:2024-04-01 16:01:07浏览次数:37  
标签:插件 自定义 Redis Error redis proxywasm timeStamp response

作者:钰诚

简介

基于 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
}

测试结果如下:

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

标签:插件,自定义,Redis,Error,redis,proxywasm,timeStamp,response
From: https://www.cnblogs.com/alisystemsoftware/p/18108673

相关文章

  • 【吊打面试官系列】Redis篇 -都有哪些办法可以降低 Redis 的内存使用情况呢?
    大家好,我是锋哥。今天分享关于【都有哪些办法可以降低Redis的内存使用情况呢?】面试题,希望对大家有帮助;都有哪些办法可以降低Redis的内存使用情况呢?如果你使用的是32位的Redis实例,可以好好利用Hash,list,sortedset,set等集合类型数据,因为通常情况下很多小的Key......
  • vue 自定义tabs 样式的组件
    大家应该都用过tabs不同状态显示不同的列表 这种东西其实可以自己封装的其实是很简单的做一个这样简单的筛选组件 这样的跟tabs的效果一样上代码<template><divclass="switch-container"><divclass="box"><divclass="box-item"......
  • MogDB/openGauss 自定义snmptrapd告警信息
    MogDB/openGauss自定义snmptrapd告警信息在实际使用中,默认的报警规则信息并不能很好的满足snmp服务端的需求,需要定制化报警信息,这里以添加ip为例,看似一个简单的将IP一行信息单独在报警展示出来,涉及到的配置文件修改还是挺多的。修改prometheus.yml文件首先需要修改......
  • uniapp 开发之原生Android插件
    开发须知在您阅读此文档时,我们假定您已经具备了相应Android应用开发经验,使用AndroidStudio开发过Android原生。也应该对HTML,JavaScript,CSS等有一定的了解,并且熟悉在JavaScript和JAVA环境下的JSON格式数据操作等。为了插件开发者更方便快捷的开发uni原生插件!2.9.8版本......
  • redis自学(26)
    内存淘汰策略内存淘汰:就是当Redis内存使用达到设置的阈值时,redis主动挑选部分key删除以释放更多内存的流程。Redis会在处理客户端命令的方法processCommand()中尝试做内存淘汰:  也就是说,redis是在任何命令执行之前,做内存的检查或者说尝试去淘汰一部分内存。 Redis支持8......
  • 如何保证MySQL和Redis数据一致性?
    背景在高并发的业务场景中,因为MySQL数据库是操作磁盘效率比较低,因此大多数情况下数据库都是高并发系统的瓶颈。因为Redis操作数据是在内存中进行,所以就需要使用Redis做一个缓存。让请求先访问到Redis,而不是直接访问MySQL数据库。效果图如下查询数据上面的业务场景,就是一个典......
  • vscode 必装插件
    AutoCompleteTag集合了AutoCloseTag和AutoRenameTagAutoCloseTag:自动补全结束标签AutoRenameTag:修改前面的htmltag后面</>里面的也会同步修改只需要装一个插件就能得到之前2个插件的能力CommentTranslate鼠标选中能够直接浮窗翻译.(win系统好像是墙的原因......
  • 自定义类型:【结构体】
    我们知道C语言中有许多的类型,比如char,short,int等等类型。像是这些C语言本身就支持的类型叫做内置类型,但是有一些复杂对象,只有这些类型是完全不够的。比如人,或者一本书。那么我们就可以自己定义一些类型来实现。一.结构体的基础知识1.结构体的创建与初始化结构是一些值的集合......
  • C#中的缓存处理方案 (MemoryCache,Redis)
    缓存处理在C#和WPF日常开发中非常重要,可以提高应用程序的性能和响应速度。以下是关于缓存处理方案的知识点,以及可能会在面试中被问到的一些问题和答案:缓存处理方案的知识点:内存缓存:内存缓存是最常见的一种缓存处理方案,它将数据存储在应用程序的内存中,以提高数据的访问速......
  • C#中的消息中间件(RabbitMQ 和 Redis)
    消息中间件是一种用于在分布式系统中进行异步通信的技术,常用于解耦应用程序的不同组件、实现消息传递、提高系统的可伸缩性和可靠性等。以下是关于消息中间件的知识点以及可能会在面试中被问到的一些问题和答案:消息中间件的知识点:消息队列(MessageQueue):消息中间件通常基于消......