1、API網關
API網關是一個伺服器,是系統的唯一入口。從面向對象設計的角度看,它與外觀模式類似。API網關封裝了系統内部架構,為每個用戶端提供一個定制的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存、請求分片與管理、靜态響應處理。API網關方式的核心要點是,所有的用戶端和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。通常,網關也是提供REST/HTTP的通路API。
網關應當具備以下功能:
- 性能:API高可用,負載均衡,容錯機制。
- 安全:權限身份認證、脫敏,流量清洗,後端簽名(保證全鍊路可信調用),黑名單(非法調用的限制)。
- 日志:日志記錄(spainid,traceid)一旦涉及分布式,全鍊路跟蹤必不可少。
- 緩存:資料緩存。
- 監控:記錄請求響應資料,api耗時分析,性能監控。
- 限流:流量控制,錯峰流控,可以定義多種限流規則。
- 灰階:線上灰階部署,可以減小風險。
- 路由:動态路由規則。
目前,比較流行的網關有:Nginx 、 Kong 、Orange等等,還有微服務網關Zuul 、Spring Cloud Gateway等等
對于 API Gateway,常見的選型有基于 Openresty 的 Kong、基于 Go 的 Tyk 和基于 Java 的 Zuul。這三個選型本身沒有什麼明顯的差別,主要還是看技術棧是否能滿足快速應用和二次開發。
2、spring-cloud-gateway 模型圖:
在使用spring-cloud-gateway的時候,我們一般的做法是建構一個網關微服務,然後在裡面配置各種路由資訊。
3、實戰案例:
3.1、建構一個spring-boot項目引入如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
3.2、項目啟動類:
@SpringBootApplication
public class GatewayService {
public static void main(String[] args) {
SpringApplication.run(GatewayService.class, args);
}
}
3.3、配置路由:
我們配置一個簡單的路由,主要是通過網關服務進行轉發到我們的訂單服務:
spring:
cloud:
gateway:
routes:
- id: orderapi #目前路由的id
uri: http://localhost:7070 #目前路由轉發的目标服務
predicates: #目前路由的斷言清單
- Path=/orderapi/** 使用spring-cloud-gateway提供的Path斷言
filters: #目前路由的過濾器清單
- StripPrefix=1 #表示在路徑比對的時候會去掉第一級,例如輸入http://localhost:6060/orderapi/order/findAll
#将會轉發到http://localhost:7070/order/findAll
#StripPrefix=1的含義就是會去掉/orderapi 如果等于2就會去掉/orderapi/order
這就是一個簡單的路由配置。在spring-cloud-gateway中提供了很多的斷言、過濾器的實作,詳細可見官網。
4、自定義斷言:
在spring-cloud-gateway中提供了很多的斷言、過濾器的實作,我們也可以實作自己的斷言或者過濾器,接下來我們以實作斷言為案例進行講解。
我們實作一個斷言,這個斷言的核心業務是需要檢查請求的Header中是否存在accesstoken這個屬性,如果存在我們進行請求轉發,如果不存在就不進行轉發。
實作的方式也比較簡單,隻需要實作 AbstractRoutePredicateFactory 這個接口即可,隻是有一下限制而已,固定為斷言Name + "RoutePredicateFactory"字元,然後配置使用的時候使用斷言名稱配置即可。
/**
* 自定義斷言,類名格式斷言名稱+ “RoutePredicateFactory”
*/
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {
public AuthRoutePredicateFactory() {
super(Config.class);
}
//定義配置值的順序
@Override
public List <String> shortcutFieldOrder() {
return Arrays.asList("headeKey");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
List <String> strings = headers.get(config.getHeadeKey());
if (CollectionUtils.isEmpty(strings)){
return false;
}else {
return true;
}
}
};
}
@Validated
public static class Config {
@NotEmpty
private String headeKey;
public Config() {
}
public String getHeadeKey() {
return headeKey;
}
public void setHeadeKey(String headeKey) {
this.headeKey = headeKey;
}
}
}
自定義斷言的配置方式:
spring:
cloud:
gateway:
routes:
- id: costomer_predicate
uri: http://www.baidu.com
predicates:
- Path=/customer/predicate/**
- Auth=accesstoken //自定義的斷言名稱,斷言中的配置執行個體的headeKey=accesstoken
filters:
- StripPrefix=2
5、網關層使用hystrix進行斷路保護
5.1、引入hystrix的如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
5.2、使用spring-cloud-gateway提供的Hystrix相關的過濾器,配置如下:
spring:
cloud:
gateway:
routes:
#正常斷言的使用
- id: orderapi
uri: http://localhost:7070
predicates:
- Path=/orderapi/**
filters:
- StripPrefix=1 #表示在路徑比對的時候會去掉第一級,例如輸入http://localhost:6060/orderapi/order/findAll
#将會轉發到http://localhost:7070/order/findAll
#StripPrefix=1的含義就是會去掉/orderapi 如果等于2就會去掉/orderapi/order
配置hystrix過濾器進行斷路保護
- name: Hystrix
args:
name: fallbackcmd 配置建構的hystrixCommand的id=fallbackcmd
fallbackUri: forward:http://localhost:6060/fallback
#fallbackUri: forward:/orderapi/order/mock #轉發請求到http://localhost:6060//orderapi/order/mock
#是以也會調到http://localhost:7070/order/mock, 也可以在
#網關服務中定義controller 進行轉發過去。
可對id=fallbackcmd進行屬性配置
hystrix:
command:
fallbackcmd:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
6、網關層使用redis進行網關層的限流
6.1、先引入redis相關依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
6.2、實作限流的key解析器,作用就是定義限流的規則,例如按照請求參數中的某個值進行限流,我們就以請求中的userId進行限流來實作一個key解析器:
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
6.3、配置redis:
spring:
redis:
host: localhost
port: 6379
6.4、給某個路由配置限流:
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
#正常斷言的使用
- id: orderapi
uri: http://localhost:7070
predicates:
- Path=/orderapi/**
filters:
- StripPrefix=1 #表示在路徑比對的時候會去掉第一級,例如輸入http://localhost:6060/orderapi/order/findAll
#将會轉發到http://localhost:7070/order/findAll
#StripPrefix=1的含義就是會去掉/orderapi 如果等于2就會去掉/orderapi/order
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:http://localhost:6060/fallback
# fallbackUri: forward:/orderapi/order/mock #轉發請求到http://localhost:6060//orderapi/order/mock
#是以也會調到http://localhost:7070/order/mock, 也可以在
#網關服務中定義controller 進行轉發過去。
- name: RequestRateLimiter
args:
# redis-rate-limiter是基于令牌桶來實作的
#rate-limiter: "#{@redisRateLimiter}" #指定限流器的beanName, 如果我們配置了redis-rate-limiter.*
#redis-rate-limiter: "#{@redisRateLimiter}" redisRateLimiter 這個bean也是
#spring-cloud-gateway自動裝配的,當然如果我們自定義限流器的話,我們是需要配置
#自定義的限流器beanName的。
redis-rate-limiter.replenishRate: 10 #往令牌桶裡放令牌的速率,3 表示每秒放3個令牌進去
redis-rate-limiter.burstCapacity: 20 #令牌通中最多放多少個令牌,這個值就約等于每秒最大請求數。
redis-rate-limiter.requestedTokens: 1 #每次請求消耗多少個令牌
key-resolver: "#{@userKeyResolver}" #限流鍵的解析器,此處配置的是實作了KeyResolver的bean userKeyResolver,
#例如userKeyResolver從請求中擷取有查詢參數的user的入參,例如 user=1
#userId=1 跟userId=2 的令牌通是隔離的,不通用的。
7、整合服務注冊與發現在轉發服務按照服務名稱進行負載均衡的配置:
我們部署微服務的時候基本上都是叢集部署,并且将服務資訊注冊到服務注冊中心,那麼在網關層面就需要進行服務發現,然後進行轉發的負載均衡。
7.1、在網關服務中映入eureka-clientd 的相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
7.2、配置eureka-server資訊:
eureka:
client:
service-url:
defaultZone: http://localhost:9090/eureka
7.3、配置激活器網關的服務發現定位器:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
7.4、配置路由route的uri使其能夠進行負載均衡:
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
- id: userapi
uri: lb://user-service //使用lb://服務名稱的方式
predicates:
- Path=/userapi/**
filters:
- StripPrefix=1
8、自定義全局過濾器
在spring-cloud-gateway中有兩種類型的filter,一種的局部的filter,隻有配置到路由中的時候才生效,還有就是全局的filter,全局的不需要單獨配置給某個route,而是所有的route都生效,接下來我們來實作一個全局的filter,這個filter的業務就是請求前後進行日志輸出:這裡的實作是跟reactor的知識點有關,需要有一定的響應式程式設計的基礎知識。
/**
* 全局過濾器是不需單獨給predicate 進行配置,因為預設就是給所有的predicate配置上。
*/
@Component
public class CustomerLogGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("【CustomerLogGlobalFilter】 pre log 。 。 。");
Mono <Void> mono = chain.filter(exchange).then(Mono.fromRunnable(() -> {
MultiValueMap <String, HttpCookie> cookies = exchange.getRequest().getCookies();
System.out.println("【CustomerLogGlobalFilter】 post log 。 。 。" + cookies.toSingleValueMap());
}));
return mono;
}
}