架构高可用之限流-抽刀断水水更流

news2024/11/30 6:39:00

file

上图中是一个水坝泄洪的图,那么,对于软件系统,如何使用最方便的可编程的方式增加服务限流能力呢?

下面我结合一个常规的springCloud项目实践了一把,希望他山之石可以攻玉。

背景

简单使用jmeter,压20个并发,访问 列表查询接口 /worksheet/findInfo, 对应的服务崩溃。【apprun,common】

架构复杂度的一个种类是: 保护API和服务端点免受攻击,

比如:拒绝服务,级联失败,或者 超额使用资源。

限流是一种技术,来控制API或者服务的消费速度,在分布式系统中,没有比集中式的配置和管理API的消费速度更好的选择,

只有这些请求在限定的速度内访问,才能保证API的正常,更多的将会产生Http的 请求频繁错误。

交互模型图:

file

SpringCloudGateway是一个简单和轻量级的组件,也是一种管理限制API的消费速度有效的方式。

springCloudGateway的限流模型:

file

目标

当前企业600人,按照两倍估算,即1200人使用,高频接口秒并发限制为20, 即有20个人同时使用同一个接口操作数据。

file

需要增加限流和熔断的点:

组件增加限制业务说明
openresty限流,熔断 【统一】保证流量再nginx的处理阈值,参考数据:5W/S
gateway限流,熔断 【统一】保证每个API的访问速度在20/S 峰值40 ;
apprun高频接口限流,每个接口统一分类定制熔断逻辑限流可以复用封装的组件,熔断采用最简单的hystix ;
devops高频接口限流,每个接口统一分类定制熔断逻辑限流可以复用封装的组件,熔断采用最简单的hystix ;
common高频接口限流,每个接口统一分类定制熔断逻辑,feign定制熔断逻辑限流可以复用封装的组件,熔断采用最简单的hystix ;
job高频接口限流,每个接口统一分类定制熔断逻辑,feign定制熔断逻辑限流可以复用封装的组件,熔断采用最简单的hystix ;

实现路径

网关做整体限制,接口由业务来增加限流。

gateway

gateway自带过滤器

RequestRateLimiter GatewayFilter工厂使用了RateLimiter实现来决定当前的并发请求是否允许处理,

如果不能处理,默认返回状态码 429 - 太多请求;

这个过滤器采用了可选的KeyResolver参数和对于速度限制的特殊参数,下面会介绍。

keyResolver是一个实体实现了KeyResolver接口,配置指向一个bean的名字,

使用SpEL表达式。 #{@myKeyResolver} 是一个SPEL表达式指向了一个叫做myKeyResolver的bean,下面展示了 KeyResolver接口;

public interface KeyResolver {
    Mono<String> resolve(ServerWebExchange exchange);
}

keyResolver接口是的插件策略驱动请求限制,再未来的里程碑版本,将会由一些KeyResolver的实现。

默认实现KeyResolver的类是 PrincipalNameKeyResolver, 会接受ServerWebExchange的Principal参数, 并且会调用 Principal.getName()方法。

默认的,如果KeyResolver没有找到key, 请求会被拒绝,你可以配置这个行为。

spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key=true
spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code=xxxx

注意: RequestRateLimiter没有配置短注解,下面的例子是非法的。

spring.cloud.gateway.routes[0].filters[0]=RequestRateLimiter=2, 2, #{@userkeyresolver}

RedisLimiter介绍

Redis实现是基于Stripe . 它需要使用 spring-boot-starter-data-redis-reactive 这个starter ;

算法使用的是令牌桶。

key业务含义用途
redis-rate-limiter.replenishRate一个用户每秒多少请求数,不包含丢弃的请求,这个速度就是令牌桶的数量。补充速度
redis-rate-limiter.burstCapacity用户每秒允许最大的请求数量,这个令牌数量就是令牌桶可以持有的数量,设置为0标识阻塞所有请求突增容量
redis-rate-limiter.requestedTokens单个请求消耗多少令牌,这个数量就是从令牌桶中每个请求获取令牌的数量,默认是1请求消耗令牌数量

如果你把 replenishRate 和 burstCapacity值设置为一样,则完成了一个稳定的速度设置。

临时突增流量可以允许设置 burstCapacity > replenishRate ,

这种场景下,RateLimiter需要允许一些时间在 burstCapacity和 replenishRate 之间 。

两种连续的徒增会导致丢弃请求,下面的例子配置了一个 redis-rate-limit.

速度限制在1个请求每秒, replenishRate=1, requestedTokens=60,burstCapacity=60 ;

spring:
  cloud:
    gateway:
      routes:
      - id: requestratelimiter_route
        uri: https://example.org
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 20
            redis-rate-limiter.requestedTokens: 1

上面的配置补充令牌的速度是10, 突增容量是20,但是在下一秒,只有10个请求是可以进入的;

下面的例子配置了一个KeyResolver。简单的从请求参数中获取user(在生产环境不推荐使用),

@Bean
KeyResolver userKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}

你也可以定义自己的RateLImiter,作为一个bean,实现RateLimiter接口即可,

在下面的配置中。你可以引用一个bean通过名字,使用SpEL表达式。

#{@myRateLimiter} 是一个表达式,引用了一个名字叫做 myRateLimiter的bean ,

下面的例子定义了一个rateLimite并且使用自定义的KeyResolver.

spring:
  cloud:
    gateway:
      routes:
      - id: requestratelimiter_route
        uri: https://example.org
        filters:
        - name: RequestRateLimiter
          args:
            rate-limiter: "#{@myRateLimiter}"
            key-resolver: "#{@userKeyResolver}"

魔方的限流配置

对所有的请求,限制如下。

keyvalue设置值原因
replenishRate20每个用户每秒处理请求速度 为20
burstCapacity4040,每秒处理请求数量突增容量 ;
requestedTokens1每个连接耗费1个令牌;

源代码分析: RequestRateLimiterGatewayFilterFactory

public GatewayFilter apply(Config config) {
        KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
        RateLimiter<Object> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
        boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
        HttpStatusHolder emptyKeyStatus = HttpStatusHolder
                .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));

        return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
            if (EMPTY_KEY.equals(key)) {
                if (denyEmpty) {
                    setResponseStatus(exchange, emptyKeyStatus);
                    return exchange.getResponse().setComplete();
                }
                return chain.filter(exchange);
            }
            String routeId = config.getRouteId();
            if (routeId == null) {
                Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                routeId = route.getId();
            }
            return limiter.isAllowed(routeId, key).flatMap(response -> {

                for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
                    exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
                }

                if (response.isAllowed()) {
                    return chain.filter(exchange);
                }

                setResponseStatus(exchange, config.getStatusCode());
                return exchange.getResponse().setComplete();
            });
        });
    }

处理流程如下:

file

单个路由的限流配置:

spring:
  cloud:
    gateway:
      routes:
        - id: account-service
          uri: http://localhost:8090
          predicates:
            - Path=/account/**
          filters:
            - RewritePath=/account/(?<path>.*), /$\{path}
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 60
                redis-rate-limiter.requestedTokens: 15

重写429的返回值。

package com.zengame.cycube.api.gateway.rest.aspect;

import cn.hutool.json.JSONUtil;
import com.zengame.cycube.api.lib.common.bean.R;
import com.zengame.cycube.api.lib.common.util.UUIDUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.stream.Stream;

/**
 * 魔方自定义限流
 * @author Carter.li
 * @createtime 2022/8/1 17:30
 */
@Slf4j
@Component
public class CubeRequestLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {

    private final RateLimiter redisRateLimiter;
    private final KeyResolver keyResolver;
    private final boolean denyEmptyKey = true;
    private static final String EMPTY_KEY = "____EMPTY_KEY__";


    public CubeRequestLimiterGatewayFilterFactory(RateLimiter redisRateLimiter, KeyResolver keyResolver) {
        super(redisRateLimiter, keyResolver);
        this.redisRateLimiter = redisRateLimiter;
        this.keyResolver = keyResolver;
    }

    @Override
    public GatewayFilter apply(Config config) {
        KeyResolver resolver = getOrDefault(config.getKeyResolver(), keyResolver);
        RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), redisRateLimiter);
        boolean denyEmpty = getOrDefault(config.getDenyEmptyKey(), this.denyEmptyKey);

        return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
            if (EMPTY_KEY.equals(key)) {
                if (denyEmpty) {
                    return TokenCheckGatewayFilterFactory.generateJson(exchange, R.error(9998, "请求key为空"));
                }
                return chain.filter(exchange);
            }
            String routeId = config.getRouteId();
            if (routeId == null) {
                Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                routeId = route.getId();
            }
            return limiter.isAllowed(routeId, key).flatMap(response -> {

                for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
                    exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
                }

                if (response.isAllowed()) {
                    return chain.filter(exchange);
                }

                R<String> r = R.error(9998, "请求太频繁");
                r.setData(key);
                r.setGuid("请控制请求速度");
                r.setTraceId(Stream.of(exchange.getRequest().getHeaders().getFirst("requestId"), exchange.getRequest().getQueryParams().getFirst("requestId")).filter(StringUtils::isNotBlank).findFirst().orElse(UUIDUtils.uuid()));
                log.warn("too many requests: {}", JSONUtil.toJsonStr(r));
                return TokenCheckGatewayFilterFactory.generateJson(exchange, r);

            });
        });
    }

    private <T> T getOrDefault(T configValue, T defaultValue) {
        return (configValue != null) ? configValue : defaultValue;
    }
}

测试

jmeter脚本

线程配置: file

接口配置:

file

经过测试,对高频接口增加了限流能力,而且限流能力是可以设定的。

小结

在网关添加了最低限度的保护限流策略。

企业用户数量有限,可以使用最小的资源满足软件系统的需求;

原创不易,关注诚可贵,转发价更高!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/84914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FL Studio21.0.0完整版最高版本升级功能有哪些?

支持苹果 Silicon 芯片 – 对苹果 Silicon 芯片&#xff08;M1 芯片以及相关 CPU&#xff09;的原生 ARM 代码支持&#xff0c;但请注意&#xff1a; NewTime、NewTone 和一些 DirectWave 采样格式的导入功能尚未完全重构可能会有问题。 FL Studio-win21中文更新下载如下: htt…

新通药物被暂缓审议:科创属性遭质疑,招股书“数据打架”

12月12日&#xff0c;上海证券交易所披露的信息显示&#xff0c;西安新通药物研究股份有限公司&#xff08;下称“新通药物”&#xff09;的首发申请被暂缓审议。据贝多财经了解&#xff0c;新通药物于2021年12月6日在科创板递交招股书&#xff0c;计划募资12.79亿元。 科创板上…

识破贷后资金归集——关联网络

近几年&#xff0c;金融机构为了扩大信贷规模&#xff0c;抢占市场份额&#xff0c;通过贷款将贷款发放给无法直接通过金融机构获得贷款的个人或者企业&#xff0c;但这也给金融机构带来了多重风险。 首先&#xff0c;我们来看下资金归集是什么。所谓资金归集&#xff0c;是银…

GCSE英语语言考试-语言和结构

Language语言 Example of a simile from The Hunger Games, Suzanne Collins 《饥饿游戏》中的比喻例子&#xff0c;苏珊娜-柯林斯的作品 When talking about language in prose fiction, there are a number of things you could look for: 在谈论散文小说的语言时&#xff0c…

如何在XMLMap端口修改字段映射?

在使用知行EDI系统的过程中&#xff0c;我们经常会用到XMLMap端口进行数据转化&#xff0c;XMLMap端口可以通过拖拽方式进行字段取值映射&#xff0c;同时也可以写代码添加字段对应的取值及判断条件。有时在完成映射后&#xff0c;发现源文件/目标文件待映射的字段和段落需要添…

安卓玩机搞机技巧综合资源-----闲置手机当摄像头 当监控 上网课必备 多软件评测【十四】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

为什么不能使用bigdecimal的equals比较大小

BigDecimal&#xff0c;相信对于很多人来说都不陌生&#xff0c;很多人都知道他的用法&#xff0c;这是一种 java.math 包中提供的一种可以用来进行精确运算的类型。 很多人都知道&#xff0c;在进行金额表示、金额计算等场景&#xff0c;不能使用 double、float 等类型&#…

一键登陆了解一下

我们先来看一下目前的一些登录方式&#xff1a; 账号、密码登陆 使用账号加密码是最传统的登录方式&#xff0c;可以说是简单粗暴的&#xff0c;一般也不会出现什么问题。 缺点 但这种方式要求用户要记住自己的账号和密码&#xff0c;也就是有一个记忆成本。用户为了降低记忆…

【Tryhackme】KoTH Food CTF(前端验证绕过,图片隐写,SUID提权:vim.basic)

免责声明 本文渗透的主机经过合法授权。本文使用的工具和方法仅限学习交流使用&#xff0c;请不要将文中使用的工具和渗透思路用于任何非法用途&#xff0c;对此产生的一切后果&#xff0c;本人不承担任何责任&#xff0c;也不对造成的任何误用或损害负责。 服务发现 ┌──(r…

学习管理系统五大好处

正如我们先前提到过的&#xff0c;对于公司来说&#xff0c;建立“学习型文化”可以带来许许多多的好处。然而&#xff0c;企业规模会越来越大&#xff0c;员工的培训学习需求并不会减少&#xff0c;这也会为企业的员工培训带来压力。学习管理系统&#xff08;LMS&#xff09;可…

GCSE英语语言考试-对虚构小说的问题作答

How to analyse a fiction extract 如何分析虚构小说节选 In an analytical response, you should show how language and structure create meaning. You could also explore the effect on the reader. An analytical response uses evidence from the text to make clear po…

教育培训机构教学课程内容视频加密是如何做的?

阿酷TONY / 2022-12-13 / 长沙 / 原创组图 / 内容含实测链接可测效果 教育培训机构教学课程内容视频加密是如何做的&#xff1f;教育机构的web课程视频加密是如何实现的&#xff1f;主要通过以下的一些方式来实现&#xff1a; 目录&#xff1a; 1、VRM加密 2、播放器加密…

年终总结才是职场人的天花板,学会这4个技巧,让绩效轻松翻倍

作为2022年的最后一个月&#xff0c;不少人正在或即将的年终总结怎么写愁掉了头发。今天老李就给大家分享几个如何做出领导爱看&#xff0c;绩效翻倍的年终总结的方法或套路。 方法一&#xff1a;列完大纲再填充 很多人一上来就罗列工作内容&#xff0c;写到最后看着字数不少…

在小程序中如何使用svg图标

1.首先找到一个能够下载svg图标的网站&#xff0c;例如iconfont或iconpark。 Tip:图标网址iconpark&#xff1a;ByteDance IconPark 2.选择好点击批量下载&#xff0c;下载一个压缩包。将下载后的压缩包解压之后就是我们选择下载的svg文件。如下图 3.打开将svg文件转成base64的…

RabbitMQ的发布确认

文章目录前言&#xff1a;为什么要用发布确认一、发布确认原理二、发布确认的策略2.1 开启发布确认的方法2.2 单个确认发布2.3 批量确认发布2.4 异步确认发布如何处理异步未确认消息以上 3 种发布确认速度对比前言&#xff1a;为什么要用发布确认 答&#xff1a;持久化章节中&…

在word文档表中插入图片不变形

在word文档表中插入图片不变形 目录 在word文档表中插入图片不变形 1、点击左上角【全选图标 】选中表格&#xff0c;鼠标右键点击【表格属性】 2、点击【选项】点击 取消勾选【自动重调尺寸以适应内容】&#xff0c;最后点击【确定】 ​3、依次点击【插入】【图片】点击图片…

【TSP问题】基于蜜蜂算法求解旅行商问题附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

安全数据资产统一管理

安全数据资产 统一管理DataOps&#xff0c;即 Data 和 Operations 的集成&#xff0c;于 2014 年首次提出。Gartner 将 DataOps 定义为“一种协作性的数据管理 实践&#xff0c;专注于改进组织内数据管道的通信、集成和自动化”[7]。DataOps 是一种面向流程的自动化方法&#x…

U盘被写保护怎么解除?解决方案只需这几个

U盘写保护是一种物理开关保护功能&#xff0c;用于防止存储介质上的数据被错误删除或者写入。如果你想去掉“写保护”&#xff0c;u盘被写保护怎么解除&#xff1f;看看下面的解决方案是怎么说的&#xff0c;赶紧跟随下面去掉U盘写保护的步骤来操作吧&#xff01; 方案一&#…

基于opencv传统数字图像处理实现车道线检测详细过程(附源码)

车道线检测 &#xff08;Lane Detection&#xff09; 1、实验内容 本实验使用数字图像处理的基本方法&#xff0c;构建了一个车道线检测模型。该模型可以识别图像中所有的车道线&#xff0c;并得到完整的车道线信息。模型在tuSimple Lane Dataset大小为100的数据子集进行了测…