欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Cloud?Gateway?服務(wù)網(wǎng)關(guān)的部署與使用詳細(xì)講解

 更新時(shí)間:2023年04月04日 11:01:33   作者:張維鵬  
這篇文章主要介紹了Spring?Cloud?Gateway?服務(wù)網(wǎng)關(guān)的部署與使用詳細(xì)介紹,本文給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、為什么需要服務(wù)網(wǎng)關(guān):

1、什么是服務(wù)網(wǎng)關(guān):

        傳統(tǒng)的單體架構(gòu)中只需要開放一個(gè)服務(wù)給客戶端調(diào)用,但是微服務(wù)架構(gòu)中是將一個(gè)系統(tǒng)拆分成多個(gè)微服務(wù),如果沒有網(wǎng)關(guān),客戶端只能在本地記錄每個(gè)微服務(wù)的調(diào)用地址,當(dāng)需要調(diào)用的微服務(wù)數(shù)量很多時(shí),它需要了解每個(gè)服務(wù)的接口,這個(gè)工作量很大。那有了網(wǎng)關(guān)之后,能夠起到怎樣的改善呢?

        網(wǎng)關(guān)作為系統(tǒng)的唯一流量入口,封裝內(nèi)部系統(tǒng)的架構(gòu),所有請(qǐng)求都先經(jīng)過網(wǎng)關(guān),由網(wǎng)關(guān)將請(qǐng)求路由到合適的微服務(wù),所以,使用網(wǎng)關(guān)的好處在于:

  • (1)簡(jiǎn)化客戶端的工作。網(wǎng)關(guān)將微服務(wù)封裝起來后,客戶端只需同網(wǎng)關(guān)交互,而不必調(diào)用各個(gè)不同服務(wù);
  • (2)降低函數(shù)間的耦合度。 一旦服務(wù)接口修改,只需修改網(wǎng)關(guān)的路由策略,不必修改每個(gè)調(diào)用該函數(shù)的客戶端,從而減少了程序間的耦合性
  • (3)解放開發(fā)人員把精力專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。由網(wǎng)關(guān)統(tǒng)一實(shí)現(xiàn)服務(wù)路由(灰度與ABTest)、負(fù)載均衡、訪問控制、流控熔斷降級(jí)等非業(yè)務(wù)相關(guān)功能,而不需要每個(gè)服務(wù) API 實(shí)現(xiàn)時(shí)都去考慮

        但是 API 網(wǎng)關(guān)也存在不足之處,在微服務(wù)這種去中心化的架構(gòu)中,網(wǎng)關(guān)又成了一個(gè)中心點(diǎn)或瓶頸點(diǎn),它增加了一個(gè)我們必須開發(fā)、部署和維護(hù)的高可用組件。正是由于這個(gè)原因,在網(wǎng)關(guān)設(shè)計(jì)時(shí)必須考慮即使 API 網(wǎng)關(guān)宕機(jī)也不要影響到服務(wù)的調(diào)用和運(yùn)行,所以需要對(duì)網(wǎng)關(guān)的響應(yīng)結(jié)果有數(shù)據(jù)緩存能力,通過返回緩存數(shù)據(jù)或默認(rèn)數(shù)據(jù)屏蔽后端服務(wù)的失敗。

        在服務(wù)的調(diào)用方式上面,網(wǎng)關(guān)也有一定的要求,API 網(wǎng)關(guān)最好是支持 I/O 異步、同步非阻塞的,如果服務(wù)是同步阻塞調(diào)用,可以理解為微服務(wù)模塊之間是沒有徹底解耦的,即如果A依賴B提供的API,如果B提供的服務(wù)不可用將直接影響到A不可用,除非同步服務(wù)調(diào)用在API網(wǎng)關(guān)層或客戶端做了相應(yīng)的緩存。因此為了徹底解耦,在微服務(wù)調(diào)用上更建議選擇異步方式進(jìn)行。而對(duì)于 API 網(wǎng)關(guān)需要通過底層多個(gè)細(xì)粒度的 API 組合的場(chǎng)景,推薦采用響應(yīng)式編程模型進(jìn)行而不是傳統(tǒng)的異步回調(diào)方法組合代碼,其原因除了采用回調(diào)方式導(dǎo)致的代碼混亂外,還有就是對(duì)于 API 組合本身可能存在并行或先后調(diào)用,對(duì)于采用回調(diào)方式往往很難控制。

2、服務(wù)網(wǎng)關(guān)的基本功能:

3、流量網(wǎng)關(guān)與服務(wù)網(wǎng)關(guān)的區(qū)別:

        流量網(wǎng)關(guān)和服務(wù)網(wǎng)關(guān)在系統(tǒng)整體架構(gòu)中所處的位置如上圖所示,流量網(wǎng)關(guān)(如Nignx)是指提供全局性的、與后端業(yè)務(wù)應(yīng)用無關(guān)的策略,例如 HTTPS證書卸載、Web防火墻、全局流量監(jiān)控等。而微服務(wù)網(wǎng)關(guān)(如Spring Cloud Gateway)是指與業(yè)務(wù)緊耦合的、提供單個(gè)業(yè)務(wù)域級(jí)別的策略,如服務(wù)治理、身份認(rèn)證等。也就是說,流量網(wǎng)關(guān)負(fù)責(zé)南北向流量調(diào)度及安全防護(hù),微服務(wù)網(wǎng)關(guān)負(fù)責(zé)東西向流量調(diào)度及服務(wù)治理。

二、服務(wù)網(wǎng)關(guān)的部署:

1、主流網(wǎng)關(guān)的對(duì)比與選型:

 (1)Kong 網(wǎng)關(guān):Kong 的性能非常好,非常適合做流量網(wǎng)關(guān),但是對(duì)于復(fù)雜系統(tǒng)不建議業(yè)務(wù)網(wǎng)關(guān)用 Kong,主要是工程性方面的考慮

(2)Zuul1.x 網(wǎng)關(guān):Zuul 1.0 的落地經(jīng)驗(yàn)豐富,但是性能差、基于同步阻塞IO,適合中小架構(gòu),不適合并發(fā)流量高的場(chǎng)景,因?yàn)槿菀桩a(chǎn)生線程耗盡,導(dǎo)致請(qǐng)求被拒絕的情況

(3)gateway 網(wǎng)關(guān):功能強(qiáng)大豐富,性能好,官方基準(zhǔn)測(cè)試 RPS (每秒請(qǐng)求數(shù))是Zuul的1.6倍,能與 SpringCloud 生態(tài)很好兼容,單從流式編程+支持異步上也足以讓開發(fā)者選擇它了。

(4)Zuul 2.x:性能與 gateway 差不多,基于非阻塞的,支持長(zhǎng)連接,但 SpringCloud 沒有集成 zuul2 的計(jì)劃,并且 Netflix 相關(guān)組件都宣布進(jìn)入維護(hù)期,前景未知。

        綜上,gateway 網(wǎng)關(guān)更加適合 SpringCloud 項(xiàng)目,而從發(fā)展趨勢(shì)上看,gateway 替代 zuul 也是必然的。

2、Spring Cloud Gateway 網(wǎng)關(guān)的搭建:

(1)聲明依賴版本號(hào):

	<properties>
		<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
		<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
	</properties>
 
	<!-- 只聲明依賴,不引入依賴 -->
	<dependencyManagement>
		<dependencies>
			<!-- 聲明springBoot版本 -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<!-- 聲明springCloud版本 -->
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<!-- 聲明 springCloud Alibaba 版本 -->
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${spring-cloud-alibaba.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

(2)添加依賴:

<!-- 引入gateway網(wǎng)關(guān) -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
	<exclusions>
        <exclusion>
			<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
</dependency>

注意:一定要排除掉 spring-boot-starter-web 依賴,否則啟動(dòng)報(bào)錯(cuò)

(3)配置項(xiàng)目名與端口:

server:
  port: 9023
  servlet:
    context-path: /${spring.application.name}
spring:
  application:
    name: gateway

好了,網(wǎng)關(guān)項(xiàng)目搭建完成,其實(shí)就添加這么一個(gè)依賴,關(guān)于詳細(xì)的配置以及作用下文介紹。

3、Spring Cloud Gateway 配置項(xiàng)的說明:

        在介紹 Spring Cloud Gateway 的配置項(xiàng)之前,我們先了解幾個(gè) Spring Cloud Gateway 的核心術(shù)語:

  • 斷言(Predicate):參照 Java8 的新特性Predicate,允許開發(fā)人員匹配 HTTP 請(qǐng)求中的任何內(nèi)容,比如請(qǐng)求頭或請(qǐng)求參數(shù),最后根據(jù)匹配結(jié)果返回一個(gè)布爾值。
  • 路由(route):由ID、目標(biāo)URI、斷言集合和過濾器集合組成。如果聚合斷言結(jié)果為真,則轉(zhuǎn)發(fā)到該路由。
  • 過濾器(filter):可以在返回請(qǐng)求之前或之后修改請(qǐng)求和響應(yīng)的內(nèi)容。

3.1、路由 Route:

        Route 主要由 路由id、目標(biāo)uri、斷言集合和過濾器集合組成,那我們簡(jiǎn)單看看這些屬性到底有什么作用。

(1)id:路由標(biāo)識(shí),要求唯一,名稱任意(默認(rèn)值 uuid,一般不用,需要自定義)

(2)uri:請(qǐng)求最終被轉(zhuǎn)發(fā)到的目標(biāo)地址

(3)order: 路由優(yōu)先級(jí),數(shù)字越小,優(yōu)先級(jí)越高

(4)predicates:斷言數(shù)組,即判斷條件,如果返回值是boolean,則轉(zhuǎn)發(fā)請(qǐng)求到 uri 屬性指定的服務(wù)中

(5)filters:過濾器數(shù)組,在請(qǐng)求傳遞過程中,對(duì)請(qǐng)求做一些修改

3.2、斷言 Predicate:

        Predicate 來自于 Java8 的接口。Predicate 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果。該接口包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)。

        Predicate 可以用于接口請(qǐng)求參數(shù)校驗(yàn)、判斷新老數(shù)據(jù)是否有變化需要進(jìn)行更新操作。Spring Cloud Gateway 內(nèi)置了許多 Predict,這些 Predict 的源碼在 org.springframework.cloud.gateway.handler.predicate 包中,有興趣可以閱讀一下。內(nèi)置的一些斷言如下圖:

以上11種斷言這里就不再介紹如何配置了,官方文檔寫的很清楚:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/

下面就以最后一種權(quán)重?cái)嘌詾槔榻B一下如何配置。配置如下:

spring:
  cloud:
    gateway:
      # 路由數(shù)組:指當(dāng)請(qǐng)求滿足什么樣的斷言時(shí),轉(zhuǎn)發(fā)到哪個(gè)服務(wù)上
      routes:
        # 路由標(biāo)識(shí),要求唯一,名稱任意
        - id: gateway-provider_1
		  # 請(qǐng)求最終被轉(zhuǎn)發(fā)到的目標(biāo)地址
          uri: http://localhost:9024
          # 設(shè)置斷言
          predicates:
            # Path Route Predicate Factory 斷言,滿足 /gateway/provider/** 路徑的請(qǐng)求都會(huì)被路由到 http://localhost:9024 這個(gè)uri中
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory 斷言,同一分組按照權(quán)重進(jìn)行分配流量,這里分配了80%
            # 第一個(gè)group1是分組名,第二個(gè)參數(shù)是權(quán)重
            - Weight=group1, 8
          # 配置過濾器(局部)
          filters:
            # StripPrefix:去除原始請(qǐng)求路徑中的前1級(jí)路徑,即/gateway
            - StripPrefix=1            
            
        - id: gateway-provider_2
          uri: http://localhost:9025
          # 設(shè)置斷言
          predicates:
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory,同一分組按照權(quán)重進(jìn)行分配流量,這里分配了20%
            - Weight=group1, 2
		  # 配置過濾器(局部)
          filters:
            # StripPrefix:去除原始請(qǐng)求路徑中的前1級(jí)路徑,即/gateway
            - StripPrefix=1            

        Spring Cloud Gateway 中的斷言命名都是有規(guī)范的,格式:“xxx + RoutePredicateFactory”,比如權(quán)重?cái)嘌?WeightRoutePredicateFactory,那么配置時(shí)直接取前面的 “Weight”。

        如果路由轉(zhuǎn)發(fā)匹配到了兩個(gè)或以上,則是的按照配置先后順序轉(zhuǎn)發(fā),上面都配置了路徑:“ Path=/gateway/provider/** ”,如果沒有配置權(quán)重,則肯定是先轉(zhuǎn)發(fā)到 “http://localhost:9024”,但是既然配置配置了權(quán)重并且相同的分組,則按照權(quán)重比例進(jìn)行分配流量。

3.3、過濾器 filter:

Gateway 過濾器的生命周期:

  • PRE:這種過濾器在請(qǐng)求被路由之前調(diào)用。我們可利用這種過濾器實(shí)現(xiàn)身份驗(yàn)證、在集群中選擇請(qǐng)求的微服務(wù)、記錄調(diào)試信息等。
  • POST:這種過濾器在路由到微服務(wù)以后執(zhí)行。這種過濾器可用來為響應(yīng)添加標(biāo)準(zhǔn)的 HTTP Header、收集統(tǒng)計(jì)信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶端等。

Gateway 過濾器從作用范圍可分為兩種:

  • GatewayFilter:應(yīng)用到單個(gè)路由或者一個(gè)分組的路由上(需要在配置文件中配置)
  • GlobalFilter:應(yīng)用到所有的路由上(無需配置,全局生效)

(1)局部過濾器 GatewayFilter:

        Spring Cloud Gateway 中內(nèi)置了許多的局部過濾器,如下圖:

         局部過濾器需要在指定路由配置才能生效,默認(rèn)是不生效的。以 “AddResponseHeaderGatewayFilterFactory” 這個(gè)過濾器為例,為原始響應(yīng)添加Header,配置如下:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          uri: http://localhost:9024
          predicates:
            - Path=/gateway/provider/**
          # 配置過濾器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            # StripPrefix:去除原始請(qǐng)求路徑中的前1級(jí)路徑,即/gateway
            - StripPrefix=1   

瀏覽器請(qǐng)求,發(fā)現(xiàn)響應(yīng)頭中已經(jīng)有了 X-Response-Foo=Bar 這個(gè)鍵值對(duì),如下圖:

        在前面的示例中,我們也使用到了另一個(gè)局部過濾器 StripPrefixGatewayFilterFactory,該過濾器主要用于截?cái)嘣颊?qǐng)求的路徑,當(dāng)我們請(qǐng)求 localhost:9023/gateway/provider/test 時(shí),實(shí)際請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到 http://localhost:9024 服務(wù)上,并被截?cái)喑?“http://localhost:9024/provider/test"

注意:過濾器的名稱只需要寫前綴,過濾器命名必須是 "xxx + GatewayFilterFactory“(包括自定義)。

更多過濾器的配置參考官方文檔:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

(2)自定義局部過濾器:

        雖說內(nèi)置的過濾器能夠解決很多場(chǎng)景,但是難免還是有些特殊需求需要定制一個(gè)過濾器,下面就來介紹一下如何自定義局部過濾器。

/**
 * 名稱必須是xxxGatewayFilterFactory形式
 * todo:模擬授權(quán)的驗(yàn)證,具體邏輯根據(jù)業(yè)務(wù)完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {
 
    private static final String AUTHORIZE_TOKEN = "token";
 
    //構(gòu)造函數(shù),加載Config
    public AuthorizeGatewayFilterFactory() {
        //固定寫法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }
 
    //讀取配置文件中的參數(shù) 賦值到 配置類中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }
 
    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判斷是否開啟授權(quán)驗(yàn)證
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }
 
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //從請(qǐng)求頭中獲取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //從請(qǐng)求頭參數(shù)中獲取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }
 
            ServerHttpResponse response = exchange.getResponse();
            //如果token為空,直接返回401,未授權(quán)
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //處理完成,直接攔截,不再進(jìn)行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是過濾器的前置處理
             *
             * chain.filter().then(
             *  過濾器的后置處理...........
             * )
             */
            //授權(quán)正常,繼續(xù)下一個(gè)過濾器鏈的調(diào)用
            return chain.filter(exchange);
        };
    }
 
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否開啟認(rèn)證
        private boolean enabled;
    }
}

局部過濾器需要在路由中配置才能生效,配置如下:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          uri: http://localhost:9024
          predicates:
            - Path=/gateway/provider/**
          ## 配置過濾器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定義過濾器配置,值為true需要驗(yàn)證授權(quán),false不需要
            - Authorize=true

此時(shí)直接訪問:http://localhost:9023/gateway/provider/port,不攜帶token,返回如下圖:

請(qǐng)求參數(shù)帶上token:http://localhost:9023/gateway/provider/port?token=abcdcdecd-ddcdeicd12,成功返回,如下圖:

        上述的 AuthorizeGatewayFilterFactory 只是涉及到了過濾器的前置處理,后置處理是在 chain.filter().then() 中的 then() 方法中完成的,具體可以看下項(xiàng)目源碼中的 TimeGatewayFilterFactory,代碼就不再貼出來了,如下圖:

(3)GlobalFilter 全局過濾器:

        全局過濾器應(yīng)用全部路由上,無需開發(fā)者配置,Spring Cloud Gateway 也內(nèi)置了一些全局過濾器,如下圖:

        GlobalFilter 的功能其實(shí)和 GatewayFilter 是相同的,只是 GlobalFilter 的作用域是所有的路由配置,而不是綁定在指定的路由配置上。多個(gè) GlobalFilter 可以通過 @Order 或者 getOrder() 方法指定執(zhí)行順序,order值越小,執(zhí)行的優(yōu)先級(jí)越高。

        注意,由于過濾器有 pre 和 post 兩種類型,pre 類型過濾器如果 order 值越小,那么它就應(yīng)該在pre過濾器鏈的頂層,post 類型過濾器如果 order 值越小,那么它就應(yīng)該在 post 過濾器鏈的底層。示意圖如下:

(4)自定義全局過濾器:

        當(dāng)然除了內(nèi)置的全局過濾器,實(shí)際工作中還需要定制過濾器,下面來介紹一下如何自定義。我們模擬 Nginx 的 Access Log 功能,記錄每次請(qǐng)求的相關(guān)信息。代碼如下:

@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //filter的前置處理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                //繼續(xù)調(diào)用filter
                .filter(exchange)
                //filter的后置處理
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("請(qǐng)求路徑:{},遠(yuǎn)程IP地址:{},響應(yīng)碼:{}", path, remoteAddress, statusCode);
        }));
    }
}

好了,全局過濾器不必在路由上配置,注入到IOC容器中即可全局生效。

此時(shí)發(fā)出一個(gè)請(qǐng)求,控制臺(tái)打印信息如下:

請(qǐng)求路徑:/gateway/provider/port,遠(yuǎn)程IP地址:/0:0:0:0:0:0:0:1:64114,響應(yīng)碼:200 OK

4、Gateway 集成 nacos 注冊(cè)中心實(shí)現(xiàn)服務(wù)發(fā)現(xiàn):

        上述 demo 中并沒有集成注冊(cè)中心,每次路由配置都是指定固定的服務(wù)uri,如下圖:

這樣做有什么壞處呢?

  • 網(wǎng)關(guān)服務(wù)需要知道所有服務(wù)的域名或IP地址,另外,一旦服務(wù)的域名或IP地址發(fā)生修改,路由配置中的 uri 就必須修改
  • 服務(wù)集群中無法實(shí)現(xiàn)負(fù)載均衡

        那么此時(shí)我們可以集成的注冊(cè)中心,使得網(wǎng)關(guān)能夠從注冊(cè)中心自動(dòng)獲取uri,并實(shí)現(xiàn)負(fù)載均衡,這里我們以 nacos 注冊(cè)中心為例介紹一下

(1)pom 文件中新增依賴:

<!--nacos注冊(cè)中心-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

(2)啟動(dòng)類添加 @EnableDiscoveryClient 注解開啟注冊(cè)中心功能,如下圖:

 (3)配置 nacos 注冊(cè)中心的地址:

nacos:
  namespace: 856a40d7-6548-4494-bdb9-c44491865f63
  url: 120.76.129.106:80
spring:
  cloud:
    nacos:
      discovery:
      	server-addr: ${nacos.url}
        namespace: ${nacos.namespace}
        register-enabled: true

(4)服務(wù)路由配置:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          ## 使用了lb形式,從注冊(cè)中心負(fù)載均衡的獲取uri
          uri: lb://gateway-provider
          ## 配置斷言
          predicates:
            - Path=/gateway/provider/**
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

路由配置中唯一不同的就是路由的 uri,格式:lb://service-name,這是固定寫法:

  • lb:固定格式,指的是從nacos中按照名稱獲取微服務(wù),并遵循負(fù)載均衡策略
  • service-name:nacos注冊(cè)中心的服務(wù)名稱,這里并不是IP地址形式的

        為什么指定了 lb 就可以開啟負(fù)載均衡,前面說過全局過濾器 LoadBalancerClientFilter 就是負(fù)責(zé)路由尋址和負(fù)載均衡的,可以看到如下源碼:

(5)開啟 gateway 自動(dòng)路由配置:

        隨著我們的系統(tǒng)架構(gòu)不斷地發(fā)展,系統(tǒng)中微服務(wù)的數(shù)量肯定會(huì)越來越多,我們不可能每添加一個(gè)服務(wù),就在網(wǎng)關(guān)配置一個(gè)新的路由規(guī)則,這樣的維護(hù)成本很大;特別在很多種情況,我們?cè)谡?qǐng)求路徑中會(huì)攜帶一個(gè)路由標(biāo)識(shí)方便進(jìn)行轉(zhuǎn)發(fā),而這個(gè)路由標(biāo)識(shí)一般都是服務(wù)在注冊(cè)中心中的服務(wù)名,因此這是我們就可以開啟 spring cloud gateway 的自動(dòng)路由功能,網(wǎng)關(guān)自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù),配置如下:

# enabled:默認(rèn)為false,設(shè)置為true表明spring cloud gateway開啟服務(wù)發(fā)現(xiàn)和路由的功能,網(wǎng)關(guān)自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù)
spring.cloud.gateway.discovery.locator.enabled = true
# lowerCaseServiceId:?jiǎn)?dòng) locator.enabled=true 自動(dòng)路由時(shí),路由的路徑默認(rèn)會(huì)使用大寫ID,若想要使用小寫ID,可將lowerCaseServiceId設(shè)置為true
spring.cloud.gateway.discovery.locator.lower-case-service-id = true

        這里需要注意的是,由于我們的網(wǎng)關(guān)項(xiàng)目配置了 server.servlet.context-path 屬性,這會(huì)導(dǎo)致自動(dòng)路由失敗的問題,因此我們需要做如下兩個(gè)修改:

# 重寫過濾鏈,解決項(xiàng)目設(shè)置了 server.servlet.context-path 導(dǎo)致 locator.enabled=true 默認(rèn)路由策略404的問題
spring.cloud.gateway.discovery.locator.filters[0] = PreserveHostHeader
@Configuration
public class GatewayConfig
{
    @Value ("${server.servlet.context-path}")
    private String prefix;
 
    /**
     * 過濾 server.servlet.context-path 屬性配置的項(xiàng)目路徑,防止對(duì)后續(xù)路由策略產(chǎn)生影響,因?yàn)?gateway 網(wǎng)關(guān)不支持 servlet
     */
    @Bean
    @Order (-1)
    public WebFilter apiPrefixFilter()
    {
        return (exchange, chain) ->
        {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getRawPath();
 
            path = path.startsWith(prefix) ? path.replaceFirst(prefix, "") : path;
            ServerHttpRequest newRequest = request.mutate().path(path).build();
 
            return chain.filter(exchange.mutate().request(newRequest).build());
        };
    }
}

        至此,我們就開啟了 spring cloud gateway 的自動(dòng)路由功能,網(wǎng)關(guān)自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù)。假設(shè)我們的服務(wù)提供者在 nacos 注冊(cè)中心的服務(wù)名為 “gateway-provider”,這時(shí)我們只需要訪問 “http://localhost:9023/gateway/gateway-provider/test”,就可以將請(qǐng)求成功轉(zhuǎn)發(fā)過去了

5、Gateway 整合 Apollo 實(shí)現(xiàn)動(dòng)態(tài)路由配置:

        上述例子都是將網(wǎng)關(guān)的一系列配置寫到項(xiàng)目的配置文件中,一旦路由策略發(fā)生改變必須要重啟項(xiàng)目,這樣維護(hù)成本很高,特別是服務(wù)網(wǎng)關(guān)作為系統(tǒng)的中心點(diǎn),一旦重啟出現(xiàn)問題,影響面將是十分巨大的,因此,我們將網(wǎng)關(guān)的配置存放到配置中心中,這樣由配置中心統(tǒng)一管理,一旦路由發(fā)生改變,只需要在配置中心修改即可,降低風(fēng)險(xiǎn)且實(shí)時(shí)失效。本部分就以 Apollo 配置中心為例介紹下如下實(shí)現(xiàn)動(dòng)態(tài)路由配置:

(1)添加 apollo 配置中心依賴:

<!-- Apollo 統(tǒng)一配置中心 -->
<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>1.7.0</version>
</dependency>

(2)添加 Apollo 路由更改監(jiān)聽刷新類:

import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Configuration;
 
import java.util.ArrayList;
 
/**
 * Apollo路由更改監(jiān)聽刷新
 */
@Configuration
public class GatewayPropertRefresher implements ApplicationContextAware, ApplicationEventPublisherAware
{
    private static final Logger logger = LoggerFactory.getLogger(GatewayPropertRefresher.class);
 
    private static final String ID_PATTERN = "spring\\.cloud\\.gateway\\.routes\\[\\d+\\]\\.id";
 
    private static final String DEFAULT_FILTER_PATTERN = "spring\\.cloud\\.gateway\\.default-filters\\[\\d+\\]\\.name";
 
 
    private ApplicationContext applicationContext;
 
    private ApplicationEventPublisher publisher;
 
    @Autowired
    private  GatewayProperties gatewayProperties;
 
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
 
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
 
 
    /**
     * 監(jiān)聽路由修改
     */
    @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.cloud.gateway.")
    public void onChange(ConfigChangeEvent changeEvent)
    {
        refreshGatewayProperties(changeEvent);
    }
 
    /**
     * 刷新路由信息
     */
    private void refreshGatewayProperties(ConfigChangeEvent changeEvent)
    {
        logger.info("gateway網(wǎng)關(guān)配置 刷新開始!");
 
        preDestroyGatewayProperties(changeEvent);
        //更新配置
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
        //更新路由
        refreshGatewayRouteDefinition();
 
        logger.info("gateway網(wǎng)關(guān)配置 刷新完成!");
    }
 
    /***
     * GatewayProperties沒有@PreDestroy和destroy方法
     * org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)中destroyBean時(shí)不會(huì)銷毀當(dāng)前對(duì)象
     * 如果把spring.cloud.gateway.前綴的配置項(xiàng)全部刪除(例如需要?jiǎng)討B(tài)刪除最后一個(gè)路由的場(chǎng)景),initializeBean時(shí)也無法創(chuàng)建新的bean,則return當(dāng)前bean
     * 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean時(shí)會(huì)注入新的屬性替換已有的bean
     * 這個(gè)方法提供了類似@PreDestroy的操作,根據(jù)配置文件的實(shí)際情況把org.springframework.cloud.gateway.config.GatewayProperties#routes
     * 和org.springframework.cloud.gateway.config.GatewayProperties#defaultFilters兩個(gè)集合清空
     */
    private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent)
    {
        logger.info("Pre Destroy GatewayProperties 操作開始!");
 
        final boolean needClearRoutes = this.checkNeedClear(changeEvent, ID_PATTERN, this.gatewayProperties.getRoutes().size());
        if (needClearRoutes)
        {
            this.gatewayProperties.setRoutes(new ArrayList());
        }
 
        final boolean needClearDefaultFilters = this.checkNeedClear(changeEvent, DEFAULT_FILTER_PATTERN, this.gatewayProperties.getDefaultFilters().size());
        if (needClearDefaultFilters)
        {
            this.gatewayProperties.setRoutes(new ArrayList());
        }
 
        logger.info("Pre Destroy GatewayProperties 操作完成!");
    }
 
 
    private void refreshGatewayRouteDefinition()
    {
        logger.info("Refreshing Gateway RouteDefinition 操作開始!");
 
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
 
        logger.info("Gateway RouteDefinition refreshed 操作完成!");
    }
 
    /***
     * 根據(jù)changeEvent和定義的pattern匹配key,如果所有對(duì)應(yīng)PropertyChangeType為DELETED則需要清空GatewayProperties里相關(guān)集合
     */
    private boolean checkNeedClear(ConfigChangeEvent changeEvent, String pattern, int existSize) {
 
        return changeEvent.changedKeys().stream().filter(key -> key.matches(pattern)).filter(key ->
        {
            ConfigChange change = changeEvent.getChange(key);
            return PropertyChangeType.DELETED.equals(change.getChangeType());
        }).count() == existSize;
    }
}

(3)暴露endpoint端點(diǎn):

# 暴露endpoint端點(diǎn),暴露路由信息,有獲取所有路由、刷新路由、查看單個(gè)路由、刪除路由等方法
management.endpoints.web.exposure.include = *
management.endpoint.health.show-details = always

        至此,我們就完成了 Gateway 網(wǎng)關(guān)整合 Apollo 配置中心實(shí)現(xiàn)動(dòng)態(tài)路由配置,一旦路由發(fā)生改變,只需要在配置中心修改即可被監(jiān)聽到并實(shí)時(shí)失效

如果有整合 Nacos 或 MySQL 進(jìn)行動(dòng)態(tài)路由配置的讀者可以參考以下兩篇文章:

(1)整合 Nacos 進(jìn)行動(dòng)態(tài)路由配置:https://www.cnblogs.com/jian0110/p/12862569.html

(2)整合 MySQL 進(jìn)行動(dòng)態(tài)路由配置:https://blog.csdn.net/qq_42714869/article/details/92794911

6、自定義全局異常處理器:

        通過前面的測(cè)試可以看到一個(gè)現(xiàn)象:一旦路由的微服務(wù)下線或者失聯(lián)了,Spring Cloud Gateway直接返回了一個(gè)錯(cuò)誤頁面,如下圖:

        顯然這種異常信息不友好,前后端分離架構(gòu)中必須定制返回的異常信息。傳統(tǒng)的Spring Boot 服務(wù)中都是使用 @ControllerAdvice 來包裝全局異常處理的,但是由于服務(wù)下線,請(qǐng)求并沒有到達(dá)。因此必須在網(wǎng)關(guān)中也要定制一層全局異常處理,這樣才能更加友好的和客戶端交互。

        Spring Cloud Gateway提供了多種全局處理的方式,今天只介紹其中一種方式,實(shí)現(xiàn)還算比較優(yōu)雅:

        直接創(chuàng)建一個(gè)類 GlobalErrorExceptionHandler,實(shí)現(xiàn) ErrorWebExceptionHandler,重寫其中的 handle 方法,代碼如下:

/**
 * 用于網(wǎng)關(guān)的全局異常處理
 * @Order(-1):優(yōu)先級(jí)一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {
 
 private final ObjectMapper objectMapper;
 
 @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
 @Override
 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
  ServerHttpResponse response = exchange.getResponse();
  if (response.isCommitted()) {
   return Mono.error(ex);
  }
 
  // JOSN格式返回
  response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  if (ex instanceof ResponseStatusException) {
   response.setStatusCode(((ResponseStatusException) ex).getStatus());
  }
 
  return response.writeWith(Mono.fromSupplier(() -> {
   DataBufferFactory bufferFactory = response.bufferFactory();
   try {
    //todo 返回響應(yīng)結(jié)果,根據(jù)業(yè)務(wù)需求,自己定制
    CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
    return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
   }
   catch (JsonProcessingException e) {
    log.error("Error writing response", ex);
    return bufferFactory.wrap(new byte[0]);
   }
  }));
 }
}

        好了,全局異常處理已經(jīng)定制完成了,在測(cè)試一下,此時(shí)正常返回JSON數(shù)據(jù)了(JSON的樣式根據(jù)架構(gòu)需要自己定制),如下圖:

參考文章:

干掉復(fù)雜的工具類,Hutool 工具庫(kù)確實(shí)香??!

Spring Cloud Gateway奪命連環(huán)10問?

到此這篇關(guān)于Spring Cloud Gateway 服務(wù)網(wǎng)關(guān)的部署與使用詳細(xì)介紹的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway 服務(wù)網(wǎng)關(guān)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java反射機(jī)制用法總結(jié)

    Java反射機(jī)制用法總結(jié)

    反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制。下面我們來一起學(xué)習(xí)一下吧
    2019-05-05
  • Shiro:自定義Realm實(shí)現(xiàn)權(quán)限管理方式

    Shiro:自定義Realm實(shí)現(xiàn)權(quán)限管理方式

    這篇文章主要介紹了Shiro:自定義Realm實(shí)現(xiàn)權(quán)限管理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • JPA 加鎖機(jī)制及@Version版本控制方式

    JPA 加鎖機(jī)制及@Version版本控制方式

    這篇文章主要介紹了JPA 加鎖機(jī)制及@Version版本控制方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 詳解Java中PriorityQueue的作用和源碼實(shí)現(xiàn)

    詳解Java中PriorityQueue的作用和源碼實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了Java中阻塞隊(duì)列PriorityQueue的作用和源碼實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),需要的小伙伴可以了解下
    2024-02-02
  • 詳解JAVA流程控制語句

    詳解JAVA流程控制語句

    這篇文章主要介紹了Java中的流程控制語句,循環(huán)等語句是Java編程中流程控制的基礎(chǔ),需要的朋友可以參考下
    2017-04-04
  • Java中clone方法使用筆記

    Java中clone方法使用筆記

    clone顧名思義是復(fù)制,在Java語言中,clone方法被對(duì)象調(diào)用,所以會(huì)復(fù)制對(duì)象,下面這篇文章主要給大家介紹了關(guān)于Java中clone方法使用的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • Spring JdbcTemplate實(shí)現(xiàn)添加與查詢方法詳解

    Spring JdbcTemplate實(shí)現(xiàn)添加與查詢方法詳解

    JdbcTemplate是Spring框架自帶的對(duì)JDBC操作的封裝,目的是提供統(tǒng)一的模板方法使對(duì)數(shù)據(jù)庫(kù)的操作更加方便、友好,效率也不錯(cuò),這篇文章主要介紹了Spring?JdbcTemplate執(zhí)行數(shù)據(jù)庫(kù)操作,需要的朋友可以參考下
    2022-11-11
  • Java數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)二維數(shù)組與稀疏數(shù)組轉(zhuǎn)換詳解

    Java數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)二維數(shù)組與稀疏數(shù)組轉(zhuǎn)換詳解

    稀疏數(shù)組是用于優(yōu)化,壓縮具有以下特點(diǎn)的二維數(shù)組:當(dāng)二維數(shù)組中的元素大部分相同,有意義的數(shù)據(jù)元素較少時(shí),可以使用稀疏數(shù)組進(jìn)行簡(jiǎn)化,節(jié)省存儲(chǔ)空間
    2021-10-10
  • java讀取Excel導(dǎo)入去除空行簡(jiǎn)單方法

    java讀取Excel導(dǎo)入去除空行簡(jiǎn)單方法

    這篇文章主要給大家介紹了關(guān)于java讀取Excel導(dǎo)入去除空行的簡(jiǎn)單方法,在日常開發(fā)中,想必都遇到過批處理的需求,文中給出了詳細(xì)的示例代碼,需要的朋友可以參考下
    2023-07-07
  • Java實(shí)現(xiàn)文件上傳的方法總結(jié)

    Java實(shí)現(xiàn)文件上傳的方法總結(jié)

    這篇文章主要為大家介紹了三種Java實(shí)現(xiàn)文件上傳的方法,文中的示例代碼講解詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的借鑒價(jià)值,感興趣的可以了解一下
    2023-04-04

最新評(píng)論