依賴
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接口重試