依赖
compile 'org.springframework.cloud:spring-cloud-starter-gateway'
compile 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
包结构
actuate中定义了一个叫GatewayControllerEndpoint的类,这个类提供一些对外的接口,可以获取网关的一些信息,比如路由的信息,改变路由地址等等
config中定义了一些启动时去加载的类,配置路由信息和读取你的配置文件就在这里完成
discovery中定义了注册中心相关的内容,包括注册中心的路由等
event定义了一些事件他们都继承自ApplicationEvent,对事件发布不了解的可以去看看spring的代码
filter中定义了spring cloud gateway实现的一些过滤器
handler中定义了很多Predicate相关的Factory
route就是我们路由的相关
support是工具包
启动流程
网关启动第一步加载的就是去加载config包下的几个类
1.> GatewayClassPathWarningAutoConfiguration.class: 如果DispatcherServlet存在,会给与警告,同样的DispatcherHandler不存在也会警告。
2.> GatewayLoadBalancerClientAutoConfiguration.class: gateway负载均衡的过滤器实现的加载,他将LoadBalancerClientFilter 注入到了容器中
注册始初始化配置文件加载器、路由
转发、过滤器、
心跳监测
4.> GatewayDiscoveryClientAutoConfiguration.class 注册DiscoveryClientRouteDefinitionLocator.class,在构造方法中通过discoveryClient获取服务发现中心的服务路由(存疑:容器启动时只有当前服务的路由信息,而不是所有服务路由信息)
5.> RouteDefinitionRouteLocator.class实现RouteLocator接口(内部是使用PropertiesRouteDefinitionLocator.class)读取GatewayProperties配置文件转成路由RouteDefinition
6.> DiscoveryClientRouteDefinitionLocator.class实现RouteDefinitionLocator接口,getRouteDefinitions()时,通过discoveryClient转换服务发现中心的服务路由
工作原理
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。
该处理程序通过特定于请求的过滤器链运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。
所有“前置”过滤器(Pre Filter)逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器(Post Filter)逻辑。
执行顺序类似栈LIFO(后进先出),U型顺序。
HttpWebHandlerAdapter->RoutePredicateHandlerMapping->FilteringWebHandler
谓词工厂/断言
predicates Factory | 配置 | 描述 |
After | - After=2017-01-20T17:42:47.789-07:00[America/Denver] | 在该日期时间之后发生的请求都将被匹配。 |
Before | - Before=2017-01-20T17:42:47.789-07:00[America/Denver] | 在该日期时间之前发生的请求都将被匹配。 |
Between | - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] | 在两个日期时间之间发生的请求都将被匹配。 |
Cookie | - Cookie=chocolate, ch.p | 请求包含次cookie名称且正则表达式为真的将会被匹配。 |
Header | - Header=X-Request-Id, \d+ | 请求包含次header名称且正则表达式为真的将会被匹配。 |
Host | - Host=**.somehost.org,**.anotherhost.org | 使用Ant路径匹配规则,. 作为分隔符 |
Method | - Method=GET | 所有GET请求都将被路由 |
Path | - Path=/foo/{segment},/bar/{segment} | 路径/foo/开头或/bar/开头的请求都将被匹配 |
Query | - Query=baz - Query=foo, ba. |
包含了请求参数 请求参数里包含 |
RemoteAddr | - RemoteAddr=192.168.1.1/24 | 请求的remote address 为 192.168.1.10 则将被路由 |
Weight | routes: - id: weight_high uri: https://weighthigh.org predicates: - Weight=group1, 8 - id: weight_low uri: https://weightlow.org predicates: - Weight=group1, 2 |
将大约80%的流量转发到weighthigh.org,将大约20%的流量转发到weightlow.org |
跨域
@Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*");//支持所有方法 config.addAllowedOrigin("*");//跨域处理 允许所有的域 config.addAllowedHeader("*");//支持所有请求头 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config);//匹配所有请求 return new CorsWebFilter(source); }
过滤器工厂
-
全局过滤器与其他2类过滤器相比,永远是最后执行的;它的优先级只对其他全局过滤器起作用
-
当默认过滤器与自定义过滤器的优先级一样时,优先出发默认过滤器,然后才是自定义过滤器;同类型的过滤器,出发顺序与他们在配置文件中声明的顺序一致
-
默认过滤器与自定义过滤器使用同样的
order
顺序空间,即他们会按照各自的顺序来进行排序
GatewayFilter Factory |
配置 | 描述 |
AddRequestHeader | filters: - AddRequestHeader=X-Request-Foo, Bar |
向下游请求的header头中添加 x-request-foo:bar |
AddRequestParameter | - AddRequestParameter=foo, bar | 向下游请求添加foo=bar请求参数 |
AddResponseHeader | - AddResponseHeader=X-Response-Foo, Bar | 向下游响应的header头添加x-response-foo:bar
|
CircuitBreaker | - Hystrix=myCommandName 或者 filters: - name: CircuitBreaker args: name: myCircuitBreaker fallbackUri: forward:/inCaseOfFailureUseThis - RewritePath=/consumingServiceEndpoint, /backingServiceEndpoint |
断路器过滤器;发生断路时将请求转发或者调用fallback处理 还可以将请求重新路由到外部应用程序中的控制器或处理程序 |
PrefixPath | - PrefixPath=/mypath | 所有匹配请求的路径加前缀/mypath 。因此,向/hello 发送的请求将发送到/mypath/hello |
RequestRateLimiter | filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 redis-rate-limiter.requestedTokens: 1 |
令牌桶限流 每个用户10的请求速率限制。 允许20个突发, 但是在下一秒中,只有10个请求可用 |
RedirectTo | - RedirectTo=302, http://acme.org | 发送一个302状态码和一个Location:http://acme.org header来执行重定向 |
RemoveRequestHeader | - RemoveRequestHeader=X-Request-Foo | 向下游请求的header头中删除X-Request-Foo |
RemoveResponseHeader | - RemoveResponseHeader=X-Request-Foo | 向下游响应的header头中删除X-Request-Foo |
RewritePath | - RewritePath=/foo/(?<segment>.*), /$\{segment} | 使用Java正则表达式重写请求路径 |
RewriteResponseHeader | - RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=*** | 使用Java正则表达式重写响应头的值 |
SetPath | predicates:
- Path=/foo/{segment}
filters:
- SetPath=/{segment} |
对于一个 /foo/bar 请求,在做下游请求前,路径将被设置为/bar |
SetResponseHeader | 同上 | 同上 |
SetStatus | 同上 | 同上 |
StripPrefix | predicates:
- Path=/name/**
filters:
- StripPrefix=1 |
当通过网关发出/name/bar/foo 请求时,向nameservice 发出的请求将是http://nameservice/bar/foo |
Retry | filters: - name: Retry args: retries: 3 statuses: BAD_GATEWAY |
retry filter 不支持body请求的重试,如通过body的POST 或 PUT请求 |
RequestSize | filters:
- name: RequestSize
args:
maxSize: 5000000 |
请求大小大于限制 |
例如:
@Component public class IpFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 如果在忽略的url里,则跳过 String path = replacePrefix(exchange.getRequest().getURI().getPath()); String requestUrl = exchange.getRequest().getURI().getRawPath(); if (ignore(path) || ignore(requestUrl)) { return chain.filter(exchange); } // 验证token是否有效 ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); try { String token = request.getHeaders().getFirst("token"); //没有token返回未认证 if (token == null || token == "") { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } JWTUtils.verifyToken(token); return chain.filter(exchange); } catch (Exception e) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } } private boolean ignore(String path) { return Arrays.stream(IGNOREURL).map(url -> url.replace("/**", "")).anyMatch(path::startsWith); } @Override public int getOrder() { return 1; } }
负载均衡
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 启用自动根据服务ID生成路由 lower-case-service-id: true # 设置路由的路径为小写的服务ID routes: - id: sso-service # 路由ID(一个路由配置一个ID) uri: lb://sso # 通过注册中心来查找服务(lb代表从注册中心获取服务,并且自动开启负载均衡) predicates: - Path=/auth/** # 匹配到的以/product开头的路径都转发到product的服务,相当于访问 lb://PRODUCT-SERVICE/** filters: - StripPrefix=1 # 去掉匹配到的路径的第一段
LoadBalancerClientFilter
:实现负载均衡的全局过滤器,内部实现是ribbon
权重负载
routes:
- id: spring-cloud-client-demo uri: lb://spring-cloud-client-demo predicates: - Path=/client/** - Weight=group1, 2 filters: - StripPrefix=1 - id: spring-cloud-client-demo1 uri: lb://spring-cloud-client-demo predicates: - Path=/client/** - Weight=group1, 8 filters: - StripPrefix=1
限流
默认:RequestRateLimiterGatewayFilterFactory限流过滤器和限流的实现类RedisRateLimiter使用令牌桶限流;
常用的限流算法有几种:计数器算法、漏桶算法和令牌桶算法
·计数算法适合流量突发情况(瞬间突发)
·令牌桶适合均速,无法获取令牌的请求直接拒绝
·漏桶算法适合均速并且可以让请求进行等待,不需要直接拒绝请求
计数器算法:
维护一个单位时间内的计数器(例如:设置1s内允许请求次数10次),表示为时间单位1秒内允许计数次数最高为10,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值(10),则之后的请求都被拒绝,直到单位时间(1s)已经过去,再将计数器重置为零,缺点:如果在单位时间1s内允许100个请求,在10ms已经通过了100个请求,那后面的990ms所接收到的请求都会被拒绝,我们把这种现象称为“突刺现象”。
漏桶算法:
水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法:
随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。
自定义限流过滤:
public class UriKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getURI().getPath()); } } //将这个类的Bean注册到Ioc容器中 @Bean public UriKeyResolver uriKeyResolver() { return new UriKeyResolver(); }
或者
//自定义限流 @Bean public KeyResolver hostAddrKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); }
spring:
cloud:
gateway:
routes:
- id: test-service
uri: lb://test
predicates:
- Path=/test/**
filters:
- StripPrefix= 1
- name: RequestRateLimiter #请求数限流 名字不能随便写
args: key-resolver: "#{@hostAddrKeyResolver}" #使用SpEL按名称引用bean
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 1 #令牌桶的容量,允许在一秒钟内完成的最大请求数
降级
spring: cloud: gateway: routes: - id: test-service uri: lb://test predicates: - Path=/test/** filters: - StripPrefix= 1 - name: Hystrix args: name: fallback # Hystrix的bean名称 fallbackUri: 'forward:/fallback' # Hystrix超时降级后调用uri地址
@RestController @Slf4j public class FallbackController { @RequestMapping(value = "/fallback") @ResponseStatus public Mono<Map<String, Object>> fallback(ServerWebExchange exchange) { Map<String, Object> result = new HashMap<>(3); result.put("code", 7002); result.put("data", null); Exception exception = exchange.getAttribute(ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR); ServerWebExchange delegate = ((ServerWebExchangeDecorator) exchange).getDelegate(); log.error("接口调用失败,URL={}", delegate.getRequest().getURI(), exception); if (exception instanceof HystrixTimeoutException) { result.put("msg", "接口调用超时"); } else if (exception != null && exception.getMessage() != null) { result.put("msg", "接口调用失败: " + exception.getMessage()); } else { result.put("msg", "接口调用失败"); } return Mono.just(result); } }
重试
spring: cloud: gateway: routes: - id: test-service uri: lb://test predicates: - Path=/test/** filters: - StripPrefix= 1 - name: Retry #重试 args: retries: 1 #重试次数 series: #不指定错误码系列 statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR,SERVICE_UNAVAILABLE #500,502状态重试 methods: GET,POST # 只有get和post接口重试