介紹
Spring Cloud Gateway爲Spring生態系統上的一個API網關組件,主要提供一種簡單而有效的方式路由映射到指定的API,併爲他們提供安全性、監控和限流等等。
創建項目
創建一個gmaya-gateway 項目。
修改pom文件
<!--gateway網關,內置webflux 依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--hystrix容錯降級-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--健康監控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
啓動類修改
@SpringBootApplication
@EnableDiscoveryClient
配置文件修改
server:
port: 9200
spring:
application:
name: gmaya-geteway
cloud:
gateway:
discovery:
locator:
# 開啓服務註冊和發現
# 如果爲true,訪問路徑有兩個:
# 1.ip:9200/gmaya-service-admin/user/test
# 2.ip:9200/admin/user/test (這個是Path自己定義的/admin/**)
# 如果爲false,訪問路徑有一個ip:9200/admin/user/test
enabled: false
# 服務名配置爲小寫
lower-case-service-id: true
routes:
# 系統服務
- id: gmaya-service-admin # 不重複即可
uri: lb://gmaya-service-admin # 需要轉發到的服務名稱
predicates:
# 以 /admin/開頭的路徑全部轉發到lb://gmaya-service-admin的服務上,此時gmaya-service-admin可以負載均衡
- Path=/admin/**
filters:
# 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
- StripPrefix=1
# 註冊服務中心
eureka:
instance:
# 心跳時間,即服務續約間隔時間(缺省爲30s)
lease-renewal-interval-in-seconds: 5
# 發呆時間,即服務續約到期時間(缺省爲90s)
lease-expiration-duration-in-seconds: 10
client:
service-url:
defaultZone: http://localhost:8000/eureka/
healthcheck:
enabled: true # 開啓健康檢查
# 表示eureka client間隔多久去拉取服務註冊信息,默認爲30秒
registry-fetch-interval-seconds: 5
# 監控
management:
endpoints:
web:
exposure:
# 通過HTTP公開所有的端點, 默認是info,health
include: '*'
endpoint:
health:
# 顯示完整信息,#默認是never(簡要信息)
show-details: always
簡單測試
註冊中心:gmaya-service-center :8000
系統服務:gmaya-service-admin :9001
網關服務:gmaya-gateway :9200
啓動三個項目,訪問:
http://localhost:9200/admin/user/test
沒問題!
測一下gateway 的負載均衡,現在再把系統服務啓動一個9011端口。
-Dserver.port=9011
啓動之後查看效果
此時已經有了一個服務名一樣,但是端口不一樣的兩個服務了。
再次訪問接口。
查看後端打印情況,確實是輪詢方式實現了負載均衡。
添加hystrix熔斷降級
在分佈式系統中,網關作爲流量的入口,因此會有大量的請求進入網關,向其他服務發起調用,其他服務不可避免的會出現調用失敗(超時、異常),失敗時不能讓請求堆積在網關上,需要快速失敗並返回給客戶端,想要實現這個要求,就必須在網關上做熔斷、降級操作。
有兩種方式進行熔斷、降級
webflux方式
添加HystrixFallbackHandler
package top.gmaya.gmayagateway.handler;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
/**
* 熔斷、降級類
* @author GMaya
* @dateTime 2020/5/13 16:19
*/
@Component
@Slf4j
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse>
{
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest)
{
Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
originalUris.ifPresent(originalUri -> log.error("網關執行請求:{}失敗,hystrix服務降級處理", originalUri));
// 可以根據自己的工具類返回統一格式
Map<String,String> map = new HashMap();
map.put("code","500");
map.put("msg","服務出現異常,降級操作。");
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(JSON.toJSONString(map)));
}
}
添加GatewayRoutesConfig
package top.gmaya.gmayagateway.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import top.gmaya.gmayagateway.handler.HystrixFallbackHandler;
/**
* 路由配置信息
* @author GMaya
* @dateTime 2020/5/13 16:19
*/
@Configuration
// 全參構造方法
@AllArgsConstructor
public class GatewayRoutesConfig {
private final HystrixFallbackHandler hystrixFallbackHandler;
@Bean
public RouterFunction<?> routerFunction(){
return RouterFunctions.route(RequestPredicates.path("/defaultFallback").and(RequestPredicates.accept(
MediaType.TEXT_PLAIN)),hystrixFallbackHandler);
}
}
修改配置文件
大部分都省略了,和上面的一樣。
spring:
cloud:
gateway:
routes:
# 系統服務
- id: gmaya-service-admin # 不重複即可
filters:
# 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
- StripPrefix=1
# 降級配置
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/defaultFallback'
hystrix:
command:
default: #default全局有效
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3000 #斷路器超時時間,默認1000ms
測試
系統服務接口中添加延遲,模擬超時
此時訪問,三秒後返回設置好的熔斷信息
或者將系統服務直接關閉。
web請求方式
這種方式和正常controller中方法一樣
新建DefaultFallbackController
package top.gmaya.gmayagateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* web方式進行熔斷降級
* @author GMaya
* @dateTime 2020/5/13 17:05
*/
@RestController
@Slf4j
public class DefaultFallbackController {
@GetMapping("defaultFallback")
public Map<String,String> defaultFallback(){
// 可以根據自己的工具類返回統一格式
Map<String,String> map = new HashMap();
map.put("code","500");
map.put("msg","服務出現異常,降級操作。2");
log.error("網關執行請求失敗,web方式記錄");
return map;
}
}
將第一種方式配置先去掉,重啓項目
測試
瀏覽器訪問
注意: 如果兩個都開啓了, 就會默認使用第一種方式。
添加限流
在高併發的系統中,往往需要在系統中做限流,一方面是爲了防止大量的請求使服務器過載,導致服務不可用,另一方面是爲了防止網絡攻擊。
常見的限流緯度有比如通過Ip來限流、通過uri來限流、通過用戶訪問頻次來限流。
採用springcloud gateway 爲我們提供了限流過濾器RequestRateLimiterGatewayFilterFactory。使用Redis和lua腳本實現了令牌桶的方式限流。
修改pom
引入
<!-- redis配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--使用redis的lettuce連接池使用到,如果不用可不加-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
添加配置類
創建KeyResolverConfig類
package top.gmaya.gmayagateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* 路由限流配置
* 配置文件中使用的那種限流 就是真正的限流方式,三種任選其一,不能同時存在
* @author GMaya
* @dateTime 2020/5/14 9:19
*/
@Configuration
public class KeyResolverConfig {
/**
* 根據ip限流
* @return
*/
@Bean(value = "hostAddrKeyResolver")
public KeyResolver hostAddrKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
/**
* 根據路徑限流
* @return
*/
/* @Bean(value = "uriKeyResolver")
public KeyResolver uriKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}*/
/**
* 根據用戶限流,參數中必須有user字段
* @return
*/
/*@Bean(value = "userKeyResolver")
public KeyResolver userKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}*/
}
修改配置文件
spring:
cloud:
gateway:
routes:
# 系統服務
- id: gmaya-service-admin # 不重複即可
uri: lb://gmaya-service-admin # 需要轉發到的服務名稱
predicates:
# 以 /admin/開頭的路徑全部轉發到lb://gmaya-service-admin的服務上,此時gmaya-service-admin可以負載均衡
- Path=/admin/**
filters:
# 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
- StripPrefix=1
# 降級配置
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/defaultFallback'
# 限流配置
- name: RequestRateLimiter
args:
# 用於限流的鍵的解析器的 Bean 對象的名字
key-resolver: '#{@hostAddrKeyResolver}'
# 令牌桶每秒填充平均速率(實際情況適當加大即可:10)
redis-rate-limiter.replenishRate: 1
# 令牌桶總容量(實際情況適當加大即可:20)
redis-rate-limiter.burstCapacity: 3
測試
使用測試工具測試,每秒兩次訪問。
查看redis中的值
將令牌桶總容量中的3個值消耗完畢,再多次訪問即頁面就是一片空白。
TODO : 後續有時間,研究一下自定義限流返回信息。