粗話網關
網關,是整個微服務平台所有請求的統一入口,所有客戶端和服務端之間的聯系都通過網關來接入,相當於就是一個不賺差價的中間商,Spring Cloud Gateway做為替換上一代網關產品Zuul的新組件出現在Spring Cloud 2.0以及之后的版本中,網關作為一個服務的唯一入口,其中可以集成我們項目中除了業務之外的很多功能,比如認證授權,路由、負載均衡、日志、過濾等等一些列功能,
想了解Gateway就得我們就先了解它的三大功能名詞
斷言
匹配http中請求頭或者請求參數,如果請求與斷言想匹配則進行路由
路由
根據斷言規則,將某些請求轉發到指定的服務器上
過濾器
過濾可以發生在路由前后,使用過濾器的思想,對請求進行修改或者監視
10分鍾寫個Demo
演示Demo大致如下,創建一個訂單模塊,一個支付模塊(端口不同的兩個應用),一個網關模塊
演示功能如下:
Gateway + Nacos + Openfein,簡單跑通一下,主要關注點在於Gateway的基本使用和學習上
父工程pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springcloud_alibaba</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </dependency> <!--spring boot 2.2.2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud Alibaba--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud Hoxton.SR1--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> <plugins> <!--熱部署配置--> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
支付工程子模塊(兩個)
創建完后,復制一個出來,就該一下端口,使端口不同即可
該模塊用於后面演示:
訂單模塊負載均衡調用支付模塊
網關模塊負載均衡調用支付模塊
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud_alibaba</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>payment_8801</artifactId> <dependencies> <!-- nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--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> <!--監控 以json格式輸出信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> </dependencies> </project>
-
application.yml
server: port: 8801 spring: application: name: payment-server cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: #配置地址 dashboard: localhost:8080 #默認8719端口,本地啟動 HTTP API Server 的端口號 port: 8719 #監控 management: endpoints: web: exposure: include: '*'
-
Controller
@RestController @RequestMapping("/payment") public class PaymentController { @Value("${server.port}") private String port; @GetMapping("/test1/{id}") public String test1(@PathVariable("id") Integer id){ return "This is Payment server,Port is " + port + "\t id為" + id; } }
-
啟動類
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient public class PaymentApplication8801 { public static void main(String[] args) { SpringApplication.run(PaymentApplication8801.class); } }
訂單工程子模塊
該模塊的作用:
Gatwway學習中調用該模塊進行演示
該模塊負載均衡調用支付模塊,跑個流程
-
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud_alibaba</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>order_8803</artifactId> <dependencies> <!-- nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--遠程調用 OpenFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!--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> <!--監控 以json格式輸出信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> </dependencies> </project>
application.yml
server: port: 8803 spring: application: name: order-server cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: #配置地址 dashboard: localhost:8080 #默認8719端口,本地啟動 HTTP API Server 的端口號 port: 8719 #持久化規則到Nacos中,Nacos又通過配置持久化到數據mysql中 datasource: ds1: nacos: server-addr: localhost:8848 #nacos地址 dataId: order-server-sentinel #微服務名稱 groupId: DEFAULT_GROUP #默認分組 data-type: json #數據格式 rule-type: flow #流控規則 #feign集成hystrix需要配置開啟,實現降級 feign: sentinel: enabled: true #監控 management: endpoints: web: exposure: include: '*'
-
遠程調用Client
@FeignClient(value = "payment-server",fallback = PaymentClientDuty.class) public interface PaymentClient { @GetMapping("/payment/test1/{id}") public String test1(@PathVariable("id") Integer id); } @Component public class PaymentClientDuty implements PaymentClient { @Override public String test1(Integer id) { return "OpenFeign遠程調用失敗調用" + id; } }
-
controller
@RestController public class OrderController { @Autowired private PaymentClient paymentClient; @GetMapping("/order/{id}") public String openfeignTest(@PathVariable("id") Integer id){ return paymentClient.test1(id); } }
-
啟動類
@EnableDiscoveryClient @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableFeignClients(basePackages = "com.nacos.order.clients") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class); }
網關工程子模塊
-
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud_alibaba</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>gateway_8800</artifactId> <packaging>jar</packaging> <dependencies> <!-- nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
application.yml
server: port: 8800 spring: application: name: gateway-server cloud: nacos: discovery: server-addr: localhost:8848 #作為注冊中心的地址 gateway: routes: - id: test1 uri: http://localhost:8803 predicates: - Path=/order/**
-
啟動類
package com.ninja.gateway; import org.bouncycastle.math.ec.endo.GLVTypeAEndomorphism; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @Description * @Author Ninja * @Date 2020/8/30 **/ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class); } }
配置文件詳細說明
在我們的網關工程子模塊中,可以看到下面的一配置
網關的路由配置,可以采用硬編碼的方式也可以采用如下配置文件的方式
當然硬編碼的方式因為是寫死在代碼中,擴展性和動態性就受到了限制,不做過多說明
我們就對配置文件的方式做一個詳細的解說
gateway: routes: - id: test1 uri: http://localhost:8803 predicates: - Path=/order/**
上面只是一段配置,全稱應該為speing.cloud.gateway.routes
-
routes:路由的復數形式,用來包含所有的路由規則等相關信息
-
id: 路由id,隨便取,唯一要求就是全局唯一,一般我們配合服務名進行配置
-
uri:路由地址,這就是斷言后的請求轉發地址,
-
一般不會使用具體的ip和端口,而是配合nacos注冊中心一起使用
-
具體下面詳細講解
-
-
predicates:斷言的復數,表示包含所有的斷言規則
-
path : 就是一個斷言中的一種
-
-
上面的路由配置文件中,主要實現的功能為:
網關的端口8800,
當我們訪問localhost:8800/order/xx時,請求會轉發到localhost:8803/order/xx
可見路由轉發已經生效
斷言類型詳解
path
上面我們使用path 意思就是,路徑相匹配則進行路由跳轉,還有很多的規則可以配置,下面慢慢道來
After
-
After:可以指定,只有在指定時間后,才可以路由到指定微服務
ZonedDateTime date = ZonedDateTime.now(); System.out.println(date); //獲得時間,寫個測試得到即可
現在我們將配置文件修改為如下
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai]
-
uri 修改為指定注冊中心上的服務,而不是固定的ip和端口
-
lb://payment-server :表示使用負載均衡的方式調用該服務
-
path 意思就是,路徑相匹配則進行路由跳轉
-
After意思為,在指定時間后才可以訪問,之前訪問為404
時間到了后,根據暴露的端口信息得知:負載均衡調用服務
before和between
-
這兩個和上面after都是控制時間的,顧名思義:
before: 與after類似,他說在指定時間之前的才可以訪問 between: 需要指定兩個時間,在他們之間的時間才可以訪問
cookie
-
cookie:只有包含某些指定cookie(key,value),的請求才可以路由
現在我們將配置文件修改為如下
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** # - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai] - Cookie=username,test
表示:只有cookie中包含了username這個key,且值為test才路由
在不加cookie的時候會報404
瀏覽器打開控制台,鍵入以下命令,添加cookie再訪問
document.cookie="username=test";
header
-
header:只有包含指定請求頭的請求,才可以路由
-
現在我們將配置文件修改為如下
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** # - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai] # - Cookie=username,test - Header=ninja,\d+
Header 接受兩個參數,第一個參數為頭的名稱,第二個參數為一個正則表達式
以上的配置便是,請求頭中需有一個請求頭key為ninja,且值為數字類型的值時才路由,
host
-
host:只有攜帶請求頭中指定的Host才可以進行路由
-
現在我們將配置文件修改為如下
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** # - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai] # - Cookie=username,test # - Header=ninja,\d+ - Host= www.ninja.com
-
以上的配置便是,請求頭Host的值必須為www.ninja.com時才路由,其余一律不路由,
-
這個可以使用通配符方式比如:www.ninja.**,也是可以的
-
測試圖為如下所示
由於往下類型眾多,我就不一一截圖演示了,做詳細的文字說明,不懂就點我
method
-
method:指定請求類型才可路由,比如post,get等
-
配置文件修改為如下所示
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** # - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai] # - Cookie=username,test # - Header=ninja,\d+ # - Host= www.ninja.com - Method=GET
Query
-
query:必須帶有請求參數才可以訪問
-
配置文件修改為如下所示
gateway: routes: - id: test1 uri: lb://payment-server predicates: - Path=/payment/test1/** # - After=2020-08-30T14:37:20.465+08:00[Asia/Shanghai] # - Cookie=username,test # - Header=ninja,\d+ # - Host= www.ninja.com # - Method=GET - Query=username,\d+
基本上大致的斷言類型我們以及知道了,下面對另一個概念進行講解說明:過濾器
過濾器Filter
filter可用於修改的http請求和返回的http響應,相當於就是一個pre 一個post
單一過濾器(GatewayFilter)
也就是我們配置在某個路由規則中的過濾器,對單個路由發生作用,Gateway一共內置了20多個過濾器,分別對頭部過濾器、 路徑類過濾器、 Hystrix過濾器和變更請求URL的過濾器, 還有參數和狀態碼等其他類型的過濾器。這里就不一一說明了,我們挑幾個常用的講解一番,其余就不做講解了,當個人吧!
-
配置文件修改為如下所示
gateway: routes: - id: test1 uri: lb://payment-server filters: - AddRequestHeader=ninja, ninja_test #添加請求頭 - AddResponseHeader=ninja_response,ninja_test #添加響應頭 - RewritePath=/payment/(?<segment>.*), /$\{segment} #請求路徑重寫 predicates: - Path=/payment/payment/test1/**
測試之前想一想
-
服務的提供者我們一直沒有做任何改變
-
在網關中,通過斷言對請求url做了匹配
-
然后我們使用過濾器對請求路徑做了重寫
-
/payment/(?<segment>.*), /${segment} ???
-
第一個參數為原請求url,即是采用通配符的方式載入,
-
那么第一個參數把 /payment/test1/11111作為通配符的占位符
-
第二個參數為重寫后的請求url:/${segment},為通配符的占位符
-
根據上面我們對第一個參數的解讀,/${segment} 占位符替換即為:/payment/test1/11111
-
-
好了,更多的過濾器就面向百度吧,這里就不啰嗦了
全局過濾器(GlobalFilter)
-
全局過濾器不再針對單個路由規則,全局過濾器無需在配置文件中配置,為請求業務以及路由的URI轉換為真實業務服務的請求地址的核心過濾器,不需要配置,系統初始化時加載,並作用在每個路由上。
自定義過濾器(局部和全局)
-
除了上面網關提供的全局過濾器外,還可以自定義過濾器這也是我們經常使用的功能,下面詳細說明一下
-
全局過濾器之自定義過濾器,我們需要實現兩個接口:GlobalFilter, Ordered
-
局部過濾器子自定義過濾器,我們也需要實現兩個接口:GatewayFilter, Ordered
-
都是重寫兩個函數,一個filter為我們的業務函數,一個表示當前過濾器的執行級別的函數
-
如何運用局部 / 全局過濾器 ?
-
全局過濾器:因為無需掛載到某個路由上,全局生效,無需額外編碼和配置
-
局部過濾器:需要編碼指定某個路由,局部生效,需要額外的編碼
-
下面,我們分別來寫兩三個過濾器玩一玩
package com.ninja.gateway.custom; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @Description 全局過濾器,前置與后置過濾器 * @Author Ninja * @Date 2020/8/31 **/ @Component public class PreAndPostGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("------this is a pre filter"); return chain.filter(exchange).then(Mono.fromRunnable(() -> { System.out.println("------this is a post filter"); })); } @Override public int getOrder() { //定義過濾器執行順序 //返回值越小,越靠前執行 return -1; } }
package com.ninja.gateway.custom; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @Description 局部過濾器:遠程調用耗時記錄 * @Author Ninja * @Date 2020/8/31 **/ @Component public class TimeCountNoGlaobalFilter implements GatewayFilter, Ordered { private static final String REQUEST_TIME_BEGIN = "requestTimeBegin"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //記錄請求開始時間 exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); return chain.filter(exchange).then(Mono.fromRunnable(new Runnable() { @Override public void run() { Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN); if (startTime != null) { //打印 System.out.println(exchange.getRequest().getURI() + " 耗時" + (System.currentTimeMillis() - startTime)); } } })); } @Override public int getOrder() { //定義過濾器執行順序 //返回值越小,越靠前執行 return 0; } }
package com.ninja.gateway.custom; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @Description 全局過濾器 * @Author Ninja * @Date 2020/8/31 **/ @Component public class UriGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); System.out.println("UrlFilter攔截器已攔截,url:" + path); //攔截請求,直接響應 //return exchange.getResponse().setComplete(); //放行請求 return chain.filter(exchange); } @Override public int getOrder() { //定義過濾器執行順序 //返回值越小,越靠前執行 return 1; } }
上面我寫了三個過濾器,分別為:
-
PreAndPostGlobalFilter:全局過濾器,直接生效
-
TimeCountNoGlaobalFilter:局部過濾器,需要額外編碼指定路由
-
UriGlobalFilter:全局過濾器,直接生效
下面我們對唯一的局部過濾器進行綁定路由的編碼
package com.ninja.gateway.config; import com.ninja.gateway.custom.TimeCountNoGlaobalFilter; import com.ninja.gateway.custom.UriGlobalFilter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; import org.springframework.cloud.gateway.route.builder.PredicateSpec; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.UriSpec; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.function.Function; /** * @Description 將自定義局部filter注冊到路由中 * @Author Ninja * @Date 2020/8/31 **/ @Configuration public class FilterEureka { @Bean public RouteLocator getRouteLocator(RouteLocatorBuilder builder) { return builder.routes(). route(new Function<PredicateSpec, Route.AsyncBuilder>() { @Override public Route.AsyncBuilder apply(PredicateSpec predicateSpec) { return predicateSpec .path("/payment/payment/test1/**") .filters(new Function<GatewayFilterSpec, UriSpec>() { @Override public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) { return gatewayFilterSpec.stripPrefix(1).filter( new TimeCountNoGlaobalFilter()); } }) .uri("lb://payment-server") .id("test1)"); } }).build(); } }
-
仔細看看,無非就是指定了id,path 和 uri,使其綁定到了該路由上,下面我們啟動項目測試一把