当前位置: 首页 > 后端技术 > Java

高可用架构限流-拔刀断水、水变流量

时间:2023-04-01 18:49:11 Java

上图是一个大坝泄洪示意图。那么,对于软件系统来说,如何使用最方便的可编程方式来增加业务限流能力呢?接下来结合一个正规的springCloud项目进行实践,希望他山之石可以用于玉石。后台干脆用jmeter,按20并发,访问列表查询接口/worksheet/findInfo,对应的服务就崩溃了。[apprun,common]一类架构复杂性是:保护API和服务端点免受拒绝服务、级联故障或资源超额订阅等攻击。限流是一种控制API或服务消费速度的技术。在分布式系统中,没有比集中配置和管理API消费速度更好的选择了。只有在限定的速度内访问这些请求,才能保证API的正常运行,才会产生更频繁的Http请求错误。交互模型图:SpringCloudGateway是一个简单轻量级的组件,也是管理和限制API消费速度的有效方式。springCloudGateway的限流模型:目标当前企业有600名员工,预计翻倍,即1200人使用,高频接口限时20秒,即20人使用同一接口同时操作数据。需要增加限流和断路器的点:组件增加限流业务说明openresty限流,断路器【统一】保证流量后nginx的处理阈值,参考数据:5W/Sgateway限流,断路器【统一】到保证每个API的访问速度在20/S峰值40;Apprun高频接口限流,每个接口统一分类自定义熔断逻辑限流可复用封装组件,熔断器采用最简单的hystix;devops高频接口限流,每个接口统一分类定制熔断逻辑限流,可复用封装组件。最简单的hystix用于fuse;最简单的hystix;job高频接口限流,每个接口归类到统一的自定义熔断逻辑,feign自定义熔断逻辑限流可以复用封装好的组件,使用最简单的hystix进行熔断;实现路径网关作为整体限流,接口由业务控制增加限流。gatewaygateway有自己的过滤器RequestRateLimiterGatewayFilter工厂使用RateLimiter实现来判断是否允许处理当前的并发请求。如果无法处理,则返回默认状态码429——请求过多;这个过滤器使用了可选的KeyResolver参数和针对速率限制的特殊参数,下面会介绍。keyResolver是一个实现KeyResolver接口的实体,配置为使用SpEL表达式指向bean的名称。#{@myKeyResolver}是一个指向名为myKeyResolver的bean的SPEL表达式。KeyResolver接口如下图;publicinterfaceKeyResolver{Monoresolve(ServerWebExchangeexchange);}keyResolver接口是一个插件化的策略驱动的请求限制,未来的里程碑版本会通过一些KeyResolver来实现。默认实现KeyResolver的类是PrincipalNameKeyResolver,它接受ServerWebExchange的Principal参数,调用Principal.getName()方法。默认情况下,如果KeyResolver没有找到密钥,请求将被拒绝,您可以配置此行为。spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key=truespring.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这个启动器;该算法使用令牌桶。关键业务含义使用redis-rate-limiter.replenishRate一个用户每秒请求多少次不包括丢弃的请求,这个速率就是令牌桶的数量。补充速度redis-rate-limiter.burstCapacity用户允许每秒最大请求数。这个令牌数量就是令牌桶能容纳的数量。设置为0则阻塞所有请求突发容量redis-rate-limiter.requestedTokenssingle请求消耗了多少令牌,这个数量是令牌桶中每次请求获得的令牌数,默认为1次请求消耗令牌如果将replenishRate和burstCapacity的值设置相同,则完成了一个稳定的速度设置。临时突发流量可以设置burstCapacity>replenishRate,在这种情况下,RateLimiter需要在burstCapacity和replenishRate之间留出一些时间。连续两次增加将导致丢弃请求。以下示例配置了redis-rate-limit。速率限制为每秒1个请求,replenishRate=1,requestedTokens=60,burstCapacity=60;spring:cloud:gateway:routes:-id:requestratelimiter_routeuri:https://example.orgfilters:-name:RequestRateLimiterargs:redis-rate-limiter.replenishRate:10redis-rate-limiter.burstCapacity:20redis-rate-limiter.requestedTokens:1上述配置中补充令牌的速度为10,突发容量为20,但在下一秒,只有10个请求可以进入;以下示例配置了一个KeyResolver。只需从请求参数中获取用户(生产环境不推荐),@BeanKeyResolveruserKeyResolver(){returnexchange->Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));}也可以定义你自己的RateLimiter,作为一个bean,只需在下面的配置中实现RateLimiter接口。您可以使用SpEL表达式按名称引用bean。{@myRateLimiter}是一个引用名为myRateLimiter的bean的表达式。以下示例定义了一个rateLimiter并使用自定义KeyResolver.spring:cloud:gateway:routes:-id:requestratelimiter_routeuri:https://example.orgfilters:-name:RequestRateLimiterargs:rate-limiter:"#{@myRateLimiter}"key-resolver:"#{@userKeyResolver}"魔方限流配置如下,限制所有请求。keyvalue设置值replenishRate20的原因是每个用户每秒20burstCapacity4040,每秒请求数突然增加;requestedTokens1为每个连接消耗1个令牌;源码分析:RequestRateLimiterGatewayFilterFactorypublicGatewayFilterapply(Configconfig){KeyResolverresolver=getOrDefault(config.keyResolver,defaultKeyResolver);RateLimiterlimiter=getOrDefault(config.rateLimiter,defaultRateLimiter);booleandenyEmpty=getOrDefault(config.denyEmptyKey,this.denyEmptyKey);HttpStatusHolderemptyKeyStatus=HttpStatusHolder.parse,this.emptyKeyStatusCode));return(exchange,chain)->resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key->{if(EMPTY_KEY.equals(key)){if(denyEmpty){setResponseStatus(exchange,emptyKeyStatus);返回交换.getResponse().setComplete();}返回chain.filter(交换);}StringrouteId=config.getRouteId();如果(routeId==null){Routeroute=exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);routeId=route.getId();}returnlimiter.isAllowed(routeId,key).flatMap(response->{for(Map.Entryheader:response.getHeaders().entrySet()){exchange.getResponse().getHeaders().add(header.getKey(),header.getValue());}if(response.isAllowed()){returnchain.filter(exchange);}setResponseStatus(exchange,config.getStatusCode());返回exchange.getResponse().setComplete();});});}处理流程如下:单个路由的限制配置:spring:cloud:gateway:routes:-id:account-serviceuri:http://localhost:8090predicates:-Path=/account/**过滤器:-RewritePath=/account/(?.*),/$\{path}-name:RequestRateLimiterargs:redis-rate-limiter.replenishRate:1redis-rate-limiter.burstCapacity:60redis-rate-limiter.requestedTokens:15重写429的返回值包com.zengame.cycube.api.gateway.rest.aspect;导入cn.hutool.json.JSONUtil;导入com.zengame.cycube.api.lib.common.bean.R;导入com.zengame.cycube.api。lib.common.util.UUIDUtils;导入lombok.extern.slf4j.Slf4j;导入org.apache.commons.lang3.StringUtils;导入org.springframework.cloud.gateway.filter.GatewayFilter;导入org.springframework.cloud.gateway。filter.factory.RequestRateLimiterGatewayFilterFactory;导入org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;导入org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;导入org.springframework.cloud.gateway.route.Route;导入org.springframework.cloud.gateway.support.ServerWebExchangeUtils;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.stream.Stream;/***魔方自定义限制流*@authorCarter.li*@createtime2022/8/117:30*/@Slf4j@ComponentpublicclassCubeRequestLimiterGatewayFilterFactoryextendsRequestRateLimiterGatewayFilterFactory{privatefinalRateLimiterredisRateLimiter;私有最终KeyResolverkeyResolver;privatefinalbooleandenyEmptyKey=true;privatestaticfinalStringEMPTY_KEY="____EMPTY_KEY__";publicCubeRequestLimiterGatewayFilterFactory(RateLimiterredisRateLimiter,KeyResolverkeyResolver){super(redisRateLimiter,keyResolver);this.redisRateLimiter=redisRateLimiter;this.keyResolver=keyResolver;}@OverridepublicGatewayFilterapply(Configconfig){KeyResolverresolver=getOrDefault(config.getKeyResolver(),keyResolver);RateLimiterlimiter=getOrDefault(config.getRateLimiter(),redisRateLimiter);booleandenyEmpty=getOrDefault(config.getDenyEmptyKey(),this.denyEmptyKey);return(exchange,chain)->resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key->{if(EMPTY_KEY.equals(key)){if(denyEmpty){returnTokenCheckGatewayFilterFactory.generateJson(exchange,R.error(9998,"请申请key为空"));}返回chain.filter(exchange);}StringrouteId=config.getRouteId();如果(routeId==null){Routeroute=exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);routeId=route.getId();}returnlimiter.isAllowed(routeId,key).flatMap(response->{for(Map.Entryheader:response.getHeaders().entrySet()){exchange.getResponse().getHeaders().add(header.getKey(),header.getValue());}if(response.isAllowed()){returnchain.filter(exchange);}Rr=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("太多请求:{}",JSONUtil.toJsonStr(r));returnTokenCheckGatewayFilterFactory.generateJson(exchange,r);});});}privateTgetOrDefault(TconfigValue,TdefaultValue){return(configValue!=null)?configValue:defaultValue;}}测试jmeter脚本线程配置:接口配置:已测试,是的高频接口增加限流能力,限流能力可设置概要网关增加最小保护限流策略企业用户数量有限,可使用最小资源满足软件系统的需要;原创不易,关注有价值,转载价格更高!转载请注明出处,让我们互相交流,共同进步。欢迎交流。