一、前言
1、SpringCloudGateway是SpringCloud新推出的網關框架,比較於上一代Zuul,功能和性能有很大的提升。Zuul1.x采用的是阻塞多線程方式,也就是一個線程處理一個連接請求,高並發情況下性能較差,即使是Zuul2.x雖然做到了非阻塞,但是面對連續跳票,看起來Zuul要被拋棄了。取而代之的是SpringCloudGateway,SpringCloudGateway是基於Webflux,是一個非阻塞異步的框架,性能上有很大提升,而且包含了Zuul的所有功能,可以從Zuul無縫切換到SpringCloudGateway
2、SpringCloud環境版本:Greenwich.RELEASE
3、SpringBoot環境版本:2.1.3.RELEASE
二、環境搭建
1、在父工程下新建一個網關模塊
2、引入SpringCloudGateway需要的POM,記得引入actuator組件,否則服務發現中心會認為服務不在線,導致網關無法路由到服務,並且加入熔斷組件Hystrix
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 健康檢查 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
3、主函數很簡單,加入必要的注解,比如服務發現等
@SpringBootApplication @EnableDiscoveryClient public class GatewayCarfacApplication { public static void main(String[] args) { SpringApplication.run(GatewayCarfacApplication.class, args); } }
4、進行SpringCloudGateway的配置,配置可以使用Java代碼進行配置或者yml配置,這里使用yml進行配置
spring: cloud: gateway: discovery: locator: enabled: true # 設置可以路由到其他服務 routes: # 可以配置多個路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id> predicates: - Path=/media/** # 路由規則 filters: - StripPrefix=1 # 不填則無法路由到其他服務 - AddRequestHeader=X-Request-Foo, Bar - name: Hystrix # 添加熔斷 args: name: fallbackcmd fallbackUri: forward:/test/fallback # 熔斷跳轉地址 # 熔斷超時時間 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000
5、上面我們配置了熔斷的配置,一旦發生熔斷就會跳轉到/test/fallback這個地址,下面我們實現一下這個接口,這里簡單的返回了error,我們可以自定義處理熔斷的邏輯
@Controller @RequestMapping(value = "/test") public class TestController { @RequestMapping(value = "/fallback", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<String> fallback() { return new ResponseEntity<>("error.", HttpStatus.OK); } }
6、網關配置好后,再啟動一個其他的服務模塊,記得添加引入actuator,否則無法被路由到
<!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 健康檢查 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
7、編寫一個HTTP接口,來測試網關的調用,為了檢測熔斷的效果,這里通過參數來控制接口的響應時間
@Controller @RequestMapping(value = "/test") public class TestController { @Value("${server.port}") private String port; @RequestMapping(value = "/get/{time}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<String> get(@PathVariable("time") long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return new ResponseEntity<>(port + " get ok.", HttpStatus.OK); } }
8、在同一注冊中心下啟動網關和服務,根據我們配置的路由規則/media/**,我們可以這樣調用http://127.0.0.1:8501/media/get/100,可以看到正確的路由到了服務
9、我們將響應時間改成5秒http://127.0.0.1:8501/media/get/5000,超高熔斷的檢測時間,可以發現接口熔斷,並且跳轉到了指定的鏈接
三、高級特性---斷言
1、以上我們實現了SpringCloudGateway的基本使用辦法,可以應付大部分應用場景了,我們同時可以細粒度的去改造路由,就使用到了斷言Predict,如果不滿足我們設置的斷言條件,則無法被路由
2、設置斷言,我們只需要在predicates下面加入配置即可,要注意如果設置了多個斷言,則請求必須滿足所有斷言才可以被正確路由到
spring: cloud: gateway: routes: # 可以配置多個路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id> predicates: # 加入斷言
3、常用的一些斷言,還有很多其他斷言就不一一列舉了,當然最常用的可能是Path斷言,我們需要通過路徑來指定路由
(1)- Header=<key>, <value>(必須有指定的HTTP Header才能路由)
(2)- Cookie=<key>, <value>(必須有指定的Cookie才能路由)
(3)- Host=aaa.bbb.com(請求域名必須是aaa.bbb.com才能路由)
(4)- Method=GET(請求方式必須是Get請求才能路由)
四、高級特性---過濾器
1、通過設置網關的過濾器,我們可以在用戶訪問的入口增加一些處理,比如鑒權、接口監控等,下面介紹一些常用的過濾器
2、常用的過濾器
(1)- AddRequestHeader=<key>, <value>(增加自定義HTTP請求頭)
(2)- SetStatus=401(設置響應的HTTP錯誤碼)
(3)- RedirectTo=302, http://acme.org(重定向到指定鏈接)
3、自定義過濾器,可以通過代碼定制更加靈活的過濾器,下面實現一個簡單的接口耗時統計的過濾器,思路:設置兩個過濾器,一個前置過濾器用來收集接口開始調用的時間,一個后置過濾器來將結束時間減去開始時間得到接口的耗時時間,並打印出來
(1)前置過濾器工廠,將開始時間寫到attributes里面
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> { public PreGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { exchange.getAttributes().put("requestTime", System.currentTimeMillis()); return chain.filter(exchange); }; } public static class Config { } }
(2)后置過濾器工廠,獲取attribute里面的開始時間,並用當前時間減去,得到耗時時間,打印
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config> { public PostGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { return chain.filter(exchange).then(Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute("requestTime"); long time = System.currentTimeMillis() - startTime; System.out.println("接口耗時時間(ms):"+time); })); }; } public static class Config { } }
(3)接下來將兩個過濾器注入到Spring里面
@Configuration public class FilterConfig { @Bean public PreGatewayFilterFactory preGatewayFilterFactory() { return new PreGatewayFilterFactory(); } @Bean public PostGatewayFilterFactory postGatewayFilterFactory() { return new PostGatewayFilterFactory(); } }
(4)前往application.yml進行過濾器的配置,在filter屬性下配置上我們的自定義過濾器,根據框架的約定引用過濾器:比如我們的過濾器名稱是PreGatewayFilterFactory,那我們引用的名稱就是去掉GatewayFilterFactory這個后綴,也就是Pre,具體配置如下
spring: cloud: gateway: discovery: locator: enabled: true # 設置可以路由到其他服務 routes: # 可以配置多個路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id> predicates: - Path=/media/** # 路由規則 filters: - StripPrefix=1 # 不填則無法路由到其他服務 - Pre # 自定義過濾器 - Post # 自定義過濾器
(5)接下來我們調用接口,可以看到耗時時間打在公屏上
五、網關限流
1、我們可以在網關層面做限流的功能,防止高並發時把服務器搞崩,或者應對一些網絡攻擊等情況
2、SpringCloudGateway為我們提供了一個很方便使用的令牌桶限流,思路:我們設置一個固定大小的令牌桶,如果令牌桶不滿,則根據一定的頻率向桶里放入令牌,每當有客戶端的請求發來,會先從令牌桶里面取令牌,取到了則繼續執行,取不到請求則會被拒絕,這樣就達到了限流的作用,我們可以靈活地調整令牌補充速率和令牌桶大小,來細粒度的控制限流,我們使用redis來存儲令牌,下面是一個簡單的demo
(1)引入redis依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
(2)接下來自定義限流的維度,我們可以從域名、uri等維度進行限流
public class HostAddrKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { // 域名維度限流 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); // uri維度限流 // return Mono.just(exchange.getRequest().getURI().getPath()); } }
(3)將解析器注入到Spring
@Configuration public class TokenLimitConfig { @Bean public HostAddrKeyResolver hostAddrKeyResolver() { return new HostAddrKeyResolver(); } }
(4)接下來配置限流器的配置,將我們編寫的解析器引用,並且配置令牌桶的屬性,這里我配置的是,每秒補充一個令牌,令牌桶的大小為3,最后配置redis的參數,用起來非常簡單
spring: cloud: gateway: discovery: locator: enabled: true # 設置可以路由到其他服務 routes: # 可以配置多個路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id> predicates: - Path=/media/** # 路由規則 filters: - StripPrefix=1 # 不填則無法路由到其他服務 - name: RequestRateLimiter # 配置限流器 args: key-resolver: '#{@hostAddrKeyResolver}' # 自定義限流過濾器 redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率 redis-rate-limiter.burstCapacity: 3 # 令牌桶總容量 redis: host: 127.0.0.1 port: 4000 password: 123456
(5)配置好后,使用JMeter來壓測一下接口,可以發現某些請求返回429錯誤碼,被限流