Gateway
在微服务架构系统中,为系统内部服务提供一道安全屏障,提高系统可用性、安全性等问题。使用 Gateway
可帮助开发人员快速开发应用,而不需要关心安全控制、流量控制、审计日志、版本控制等问题。
简单的说限流就是让控制流量让它们合理、平滑、有效的传递到内部服务,而不是随意想进就进,影响系统运行。比如以下一些情况就会使系统流量在短时间内增加从而导致一些意想不到的问题:
上面列出的这些情况是很难预知的,如果某个事件导致服务的流量在很短时间内突然增长太快,在没有限流的情况下会导致服务不可用、系统压力大最终可能会使服务器自动重启(古时候是要杀头的~~)。相反如果有了限流就会好很多流量会得到有效控制,超过阈值的访问直接在网关层就会被拒绝,这样就可以减少服务压力,达到保护内部服务的目的。
限制流量也不是随意限制的,限制的不好会适得其反,下面列举几种常见的限流策略:
remote地址 + api remote地址 + user
策略有很多,选择最合适的才是最好的,就拿 remote ip
来限制来说,比如有几百个人在局域网内共享一个公网IP,系统每秒限制10个请求那这几百人在使用你们应用的时候经常会被拒绝,那体验也太差了,这种情况如果按 user
来限制每个用户都能每秒发10个请求,再给 api
加个总的限制也许就会比 remote
策略好很多。所以在限制流量的时候也要考虑策略方面的因素,选择最适合业务场景的策略。
使用 Spring Cloud Gateway
后,如果进行流量管理呢?下面我们就来探讨下如何使用 Spring Cloud Gateway
进行流量管理,先简单了解下 Spring Cloud Gateway
的工作原理:
经过上面图不难发现,流量会依次进入:
Gateway Handler Mapping Gateway Web Handler Filter
到达 Proxied Service
后的请求如何处理 Gateway
就不管了,处理完成之后请求又回到 Gateway
做后处理。搞清楚请求的走向,我们就可以在请求处理之前和之后对请求做一些不可描述的事了!!。
Spring Cloud Gateway
很多内置的 Filter
,如: AddRequestHeader
、 AddRequestParameter
等等,这些都可以直接拿来使用,只需要简单配置参数即可。本文只关注 RequestRateLimiter
内置的过滤器来完成限流。首先写一个测试接口:
package com.example.gateway; @RestController @RequestMapping("/traffic") public class TrafficController { @GetMapping public ResponseEntity<String> testTrafficLimit(){ return ResponseEntity.ok("Success"); } } 复制代码
然后再配置下路由:
spring: cloud: gateway: routes: - id: traffic_litmit uri: http://localhost:8080 predicates: - Path=/traffic_route filters: - RewritePath=/traffic_route, /traffic - name: RequestRateLimiter args: rate-limiter: "#{@defaultRateLimiter}" key-resolver: "#{@hostAddKeyResolver}" 复制代码
上面的配置添加一个名称为 RequestRateLimiter
的滤器的, RequestRateLimiter
可以理解为一个过滤器工厂的名称, Spring Cloud Gateway
是通过 GatewayFilter Factories
去实现过滤器的, RequestRateLimiter
与之对应的工厂类为 RequestRateLimiterGatewayFilterFactory
,那 RequestRateLimiterGatewayFilterFactory
是如何工作呢?
结合上面的图,简单的说差不多就二步:
key是通过调用 KeyResolver
实例来获取的,判断是否允许访问是通过调用 RateLimiter
来决定的。分工还是很明确的,所以要使用 Spring Cloud Gateway
限流只需要实现这两个接口再加上过滤器配置就可以啦。
上面已经大致介绍过 Spring Cloud Gateway
限流的一些知识,再结合 Guava
来做个简单的 RateLimiter
来演示下限流的使用,话不多说上代码:
public class DefaultRateLimiter extends AbstractRateLimiter<DefaultRateLimiter.Config> { Logger logger = LoggerFactory.getLogger(DefaultRateLimiter.class.getName()); /** * 默认一秒一个请求,多了不收了~~ */ private final RateLimiter limiter = RateLimiter.create(1); public DefaultRateLimiter() { super(Config.class, "default-rate-limit", null); } @Override public Mono<Response> isAllowed(String routeId, String id) { Config config = getConfig().get(routeId); limiter.setRate(Objects.isNull(config.getPermitsPerSecond()) ? 1 : config.getPermitsPerSecond()); logger.info("Rate: {}", limiter.getRate()); boolean isAllow = limiter.tryAcquire(); logger.info("isAllow: {} , time: {}",isAllow, System.currentTimeMillis()); return Mono.just(new Response(isAllow, new HashMap<>())); } @Validated public static class Config { @DecimalMin("0.1") private Double permitsPerSecond; public Double getPermitsPerSecond() { return permitsPerSecond; } public Config setPermitsPerSecond(Double permitsPerSecond) { this.permitsPerSecond = permitsPerSecond; return this; } } } 复制代码
再修改一下过滤器的配置即可完成限流功能:
filters: - RewritePath=/traffic_route, /traffic - name: RequestRateLimiter args: rate-limiter: "#{@defaultRateLimiter}" key-resolver: "#{@hostAddKeyResolver}" default-rate-limit.permitsPerSecond: 0.5 复制代码
这样就完成一个自定义的 RateLimiter
了, 如果没有什么特殊要求的话可以直接使用 RedisRateLimiter
来实现限流,这个是内置限流器只需要简单配置两个参数就能使用:
filters: - StripPrefix=1 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 2 复制代码
参数说明: