开放平台的限流研究

2023-05-16

文章目录

    • 需求
    • 需求分析
    • 技术选型
  • 技术实现
    • Redis(计数器)
    • Spring Cloud Gateway限流
    • Sentinel
    • 参考
    • 参考代码

需求

1.每日配额:新增每日配额信息,申请通过后默认分配50万次每日配额数据;
2.并发配额:新增并发配额信息,申请通过后默认分配500次/分钟并发配额数据;

需求分析

对于开放平台来说,有一个功能是必须要有的,那就是API的流控。

对于每一个接入开放平台的应用,都会分配一个Appkey,这个Appkey下面会关联你申请了哪些API。然后接入的API有些是不限量,可以一直调用。有些是需要申请调用包,比如一天10万次的调用量。

除了总量的限制,还有对频率的限制。比如每秒每个API调用频率的限制,每秒每个Appkey调用频率的限制。

要实现这些流控的功能,最好的方式就是已经有一个流量治理平台,里面提供了限流的功能。像开放平台这种还不属于普通的系统限流,有点偏业务限流了,因为有具体的数量指标。

技术选型

1.MySQL+Redis(计数器)去实现。 存在数据一致性的问题。 强依赖redis影响性能
2.Spring Cloud Gateway原生限流(令牌桶算法-redis)
3.Sentinel

流量整形技术方案
1.若调用方配额设置为无限制,则不执行网关限流策略;
2.基于spring-gateway默认redis+lua脚本技术方案实施;(一秒为单位,不支持小数)
3.网关层通过zookeeper接收到管理端配额配置信息后,为每个调用方涉及的资源生成流量令牌桶;
4.每个经过网关的请求,根据请求方信息+资源信息判断内存限流配置信息,若无限流配置则直接放行;
5.若存在配置信息,则基于流量桶进行流量管理,超额则返回预设错误码及报错信息;

技术实现

Redis(计数器)

  • 请求过滤器
@Component
public class FlowLimitFilter implements GlobalFilter, Ordered {
    @Autowired
    private AsyncTaskService asyncTaskService;
    @Autowired
    private CommonService commonService;//获取应用信息

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String appId = request.getHeaders().getFirst(APPID);//获取应用id
        String requesttimemills = request.getHeaders().getFirst(REQUESTTIMEMILLS);//请求时间
        try{
            if(!StringUtils.isEmpty(appId)){
                //获取应用信息
                AppInfo appInfo = commonService.getAppInfoByAppId(appId);
                //时间转化
                String dateStr = DateUtil.timestampToTimeStr(requesttimemills);
                String minu = dateStr.substring(0,12);
                String day = dateStr.substring(0,8);
                //1、累计调用次数
                Future futureMinu = asyncTaskService.appMinuCounter("gateway:"+appId+":"+minu,"req");
                Future futureDay = asyncTaskService.appDayCounter("gateway:"+appId+":"+day,"req");
                //2、限流判断
                if(appInfo!=null && appInfo.getMinuFlag()!=0){
                    // 限流开关打开才会进行判断
                    long minuNum = (long)futureMinu.get();
                    if(minuNum>appInfo.getMinuQuota()){
                        return commonService.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS,"当前请求数量超过并发配额",exchange.getResponse());
                    }
                }
                if(appInfo!=null && appInfo.getFreqFlag()!=0){
                    long dayNum = (long)futureDay.get();
                    if(dayNum>appInfo.getAppFreq()){
                        return commonService.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS,"当前请求数量超过当日配额",exchange.getResponse());
                    }
                }

            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
        return chain.filter(exchange);//放行请求
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

  • 异步redis计数器
@Service
@Slf4j
public class AsyncTaskService {
    @Autowired
    private RedisUtils redisUtils;
    /**
     * 请求计数器
     */
    @Async
    public void reqNumCounter(String appId, String requesttimemills, String hkey){
        try{
            String dateStr = DateUtil.timestampToTimeStr(requesttimemills);
            String minu = dateStr.substring(0,12);
            String day = dateStr.substring(0,8);
            String minuKey = appId+":"+minu;
            String dayKey = appId+":"+day;
            long dayCount = redisUtils.hincr(dayKey,hkey);
            long minuCount = redisUtils.hincr(minuKey,hkey);
            if("req".equals(hkey)&&dayCount==1){
                redisUtils.expire(dayKey,26,TimeUnit.HOURS);
            }
            if("req".equals(hkey)&&minuCount==1){
                redisUtils.expire(minuKey,2,TimeUnit.HOURS);
            }
            log.info("应用:{} 状态:{} redis计数+1",appId,hkey);
        }catch (Exception e){
            log.error("redis计数失败:"+e.getMessage(),e);
        }
    }

    @Async
    public Future<Long> appMinuCounter(String key, String hkey){

        long count = 0;
        try{
            count = redisUtils.hincr(key,hkey);
            if("req".equals(hkey)&&count==1){
                redisUtils.expire(key,2,TimeUnit.HOURS);
            }
        }catch (Exception e){
            log.error("redis计数失败(分钟):"+e.getMessage(),e);
        }
        return new AsyncResult<Long>(count);
    }

    @Async
    public Future<Long> appDayCounter(String key, String hkey){
        long count = 0;
        try{
            count = redisUtils.hincr(key,hkey);
            if("req".equals(hkey)&&count==1){
                redisUtils.expire(key,26,TimeUnit.HOURS);
            }
        }catch (Exception e){
            log.error("redis计数失败(日):"+e.getMessage(),e);
        }
        return new AsyncResult<Long>(count);
    }
}

Spring Cloud Gateway限流

Sentinel

参考

推荐阅读
开放平台的限流通常都是怎么实现的?

—redis操作
https://blog.csdn.net/lydms/article/details/105224210
https://blog.csdn.net/sdrfengmi/article/details/103693212
—RedisRateLimiter源码解析
https://blog.csdn.net/weixin_42073629/article/details/106934827
–Gateway 网关限流
https://juejin.cn/post/7005060165892309022#heading-10
https://juejin.cn/post/7012076662955180068#heading-16
https://blog.csdn.net/weixin_42073629/article/details/106934827
https://www.cnblogs.com/yinjihuan/p/10514534.html?ivk_sa=1024320u
https://www.cnblogs.com/qianwei/p/10127700.html


参考代码

        <!-- redis依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.0.1.RELEASE</version>
        </dependency>
  • Redis工具类
@Component
public class RedisUtils {

    private final static Long LOCK_EXP = 15l;

    @Autowired
    private RedisTemplate redisTemplate;

    public Map hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void setex(String key, Object value,long seconds ,TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, seconds, timeUnit);
    }

    public Long incr(String key) {
        return redisTemplate.opsForValue().increment(key,1);
    }

    public Long hincr(String key,String hkey) {
        return redisTemplate.opsForHash().increment(key,hkey,1);
    }

    public void expire(String key,long timeout, TimeUnit unit) {
        redisTemplate.expire(key,timeout,unit);
    }

    public void del(String key) {
        redisTemplate.delete(key);
    }

    public void lock(String key) {
        setex(key, LOCK_EXP, 0,TimeUnit.SECONDS);
    }

    public void unlock(String key) {
        del(key);
    }

    public boolean sismember(String key, String member) {
        return redisTemplate.opsForSet().isMember(key, member);
    }

}

  • RedisConfig配置类
@Configuration
public class RedisConfig {
    @Resource
    private LettuceConnectionFactory lettuceConnectionFactory;
    /**
     * RedisTemplate配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        lettuceConnectionFactory.setValidateConnection(false);
        lettuceConnectionFactory.setShareNativeConnection(false);
        // 设置序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
                Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);// key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
        redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

开放平台的限流研究 的相关文章

随机推荐

  • pixhawk:如何飞控出厂配置

    飞控如何恢复出厂配置 打开 配置调试 全部参数表 xff0c 右边 xff0c 点击 重置为默认
  • VR游戏设计之三大特性

    2016 年 9 月 23 24 日 xff0c 由 CSDN 和创新工场联合主办的 MDCC 2016 移动开发者大会 中国 xff08 Mobile Developer Conference China xff09 将在北京 国家会议中
  • 校验和

    校验和是用于检测传输过程中可能产生的错误 xff0c 将其置于数据后 xff0c 随数据一同发送 xff0c 接收端通过同样的算法进行检查 xff0c 若正确就接受 xff0c 错误就丢弃 校验和C源代码 xff1a unsigned sh
  • 用指针做函数输入形参的原因!是因为这样能在函数中改变实参的值,现在才明白。

    看了华清的视频才明白了 xff0c 强烈推荐去看1 8 2 函数之间的参数传递方式 全局变量 值传递方式 xff08 也叫复制传递 xff09 地址传递方式 xff08 也叫指针传递 xff09 形参是新开辟的存储空间 xff0c 因此在函
  • C语言的return用法

    C语言return的用法详解 xff0c C语言函数返回值详解 摘自C语言中文网 xff1a http c biancheng net view 1855 html 我觉得对return讲得不错 xff0c 终于把return弄明白了 函数
  • 使用 free() 释放内存的同时要将指针置为NULL

    free之后要给指针赋值null么 xff1f 还有就是free我传入的只是一个地址 xff0c 那么它怎么知道要释放多少空间 xff1f 是不是通过前面malloc得到的 xff0c 我觉得应该是free和malloc是配对使用的 xff
  • 目录流

    部分摘自 xff1a https blog csdn net hou512504317 article details 46381641 我觉得写得很不错 xff0c 一下子弄懂了为什么有目录流 一切都是文件 xff0c 目录也是文件 xf
  • 《多旋翼飞行器系统设计与控制》这本书似乎是很多人推荐的

    多旋翼飞行器系统设计与控制 这本书似乎是很多人推荐的 这本我现在鼎力推荐 xff0c 这本是目前我见过的讲无人机讲得最全面的 xff0c 自主控制半自主控制都讲到了 xff0c 遥控对吧 也是理论性最强的无人机书 这本无人机书要好好读
  • 别下载CAJ了!用这个插件,轻松在知网下载PDF

    使用方法 首先为你的浏览器安装油猴扩展 各大主流浏览器安装油猴扩展的方法见下方官网 https www tampermonkey net 安装扩展后 xff0c 进入我们的 知网PDF下载助手 脚本主页 xff0c 点击安装 https g
  • 无人机那几个控制环是怎么一起控制的

    去看我的另外一篇文章 xff0c 总结好了 xff1a https blog csdn net sinat 16643223 article details 106347709 是叠加在一起的还是串级的 最终还是得回归到一个电机的输出上 平
  • 多旋翼无人机控制之完整闭环控制设计(转载)

    摘自 xff1a https blog csdn net qq 39554681 article details 90214610 这里可以看到姿态环的频率是远高于位置环的 xff0c 就好像平衡车的角度环的频率远高于速度环 xff0c 内
  • freertos似乎并不可怕,其实就是几个C文件

    我看实际freertos的代码并不多 xff0c 怪不得说可以自己重写 你看正点原子的无人机代码 xff0c 是基于freertos的 xff0c 实际相比于普通的STM32工程文件多加进去的freertos代码文件其实好像就那几个 fre
  • 基于视觉的自主循迹系统与无人机姿态控制研究

    搜一下会发现知网上有不少相关论文 xff0c 学习一下 xff0c 这比那些无人机的书籍更进一步 感觉可以达到像当初自己弄无人车到进智能车实验室之后的质变 xff0c 从业余到顶尖水平的质变 看京东上的无人机的书只能达到业余水平 xff0c
  • APM、PIXHAWK、PX4的关系

    摘自 xff1a https bbs amovlab com forum php mod 61 viewthread amp tid 61 1202 amp extra 61 page 3D1 Pixhawk PX4 APM傻傻分不清 xf
  • pixhawk+TX2+(双目视觉+激光雷达)

    这可能是做SLAM 43 无人机的标配 我在想双目视觉上面是不是还要加个云台 xff1f 这种组合的话pixhawk的二次开发怎么弄 xff0c 是不是TX2只需要把当前的和期望的三维坐标传给pixhawk就行了 xff1f 我们看GAAS
  • 目前有几个可用资源,信号量就为几。

    拍自 多旋翼无人飞行器嵌入式飞控开发指南
  • 多本无人机的书已经讲了SLAM+无人机

    其实北航的那本无人机书 多旋翼飞行器设计与控制 上已经讲了SLAM 43 无人机 所以这方面的研究应该挺多了 我看这两本书都说到了PTAM这个方法 我看到的这方面的论文里面也是说到了PTAM 而且这书里面也说了有现成的PTAM的代码可以直接
  • 无人机用光流模块单纯定点悬停其实水平XY方向上控制的是它的速度环,并没有位置环。光流是进行速度估计的。

    无人机用光流模块定点悬停其实水平方向上控制的是它的速度环 xff0c 并没有位置环 除非你让它识别跟踪一个物体或者标志 xff0c 那应该才有位置环 xff0c 单纯光流应该只是反馈它的水平的速度 xff0c 控制的也是水平的速度 xff0
  • PX4-Vision

    PX4 Vision似乎是GAAS发布的一款避障无人机 xff0c 不对似乎是阿木实验室推出的 最后发现时PX4官方推出的 xff0c GAAS视频里面有讲 xff0c GAAS只是弄了个开箱测试视频 https space bilibil
  • 开放平台的限流研究

    文章目录 需求需求分析技术选型 技术实现Redis 计数器 xff09 Spring Cloud Gateway限流Sentinel参考参考代码 需求 1 每日配额 xff1a 新增每日配额信息 xff0c 申请通过后默认分配50万次每日配