SpringCloud之Hystrix、Gateway、Config

目錄
  1. SpringCloud Hystrix(服務熔斷與降級組件 / 服務容錯與保護組件)
  2. SpringCloud Gateway(網(wǎng)關組件)
  3. SpringCloud Config(分布式配置組件)

1. SpringCloud Hystrix(服務熔斷與降級組件 / 服務容錯與保護組件)

例:服務依賴關系
在微服務架構中,一個應用往往由多個服務組成,這些服務之間相互依賴,依賴關系錯綜復雜。
通常情況下,一個用戶請求往往需要多個服務配合才能完成。例如上圖所示,在所有服務都處于可用狀態(tài)時,請求 1 需要調用 A、D、E、F 四個服務才能完成,請求 2 需要調用 B、E、D 三個服務才能完成,請求 3 需要調用服務 C、F、E、D 四個服務才能完成。
當服務 E 發(fā)生故障或網(wǎng)絡延遲時,會出現(xiàn)以下情況:
    1. 即使其他所有服務都可用,由于服務 E 的不可用,那么用戶請求 1、2、3 都會處于阻塞狀態(tài),等待服務 E 的響應。在高并發(fā)的場景下,會導致整個服務器的線程資源在短時間內(nèi)迅速消耗殆盡。
    2. 所有依賴于服務 E 的其他服務,例如服務 B、D 以及 F 也都會處于線程阻塞狀態(tài),等待服務 E 的響應,導致這些服務的不可用。
    3. 所有依賴服務B、D 和 F 的服務,例如服務 A 和服務 C 也會處于線程阻塞狀態(tài),以等待服務 D 和服務 F 的響應,導致服務 A 和服務 C 也不可用。

當微服務系統(tǒng)的一個服務出現(xiàn)故障時,故障會沿著服務的調用鏈路在系統(tǒng)中瘋狂蔓延,最終導致整個微服務系統(tǒng)的癱瘓,這就是【雪崩效應】。為了防止此類事件的發(fā)生,微服務架構引入了“熔斷器”的一系列服務容錯和保護機制。 
熔斷器(Circuit Breaker)
  與物理學中的熔斷器作用(當線路出現(xiàn)故障時,迅速切斷電源以保護電路的安全)相似,微服務架構中的熔斷器能夠在某個服務發(fā)生故障后,向服務調用方返回一個符合預期的、可處理的降級響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常。這樣就保證了服務調用方的線程不會被長時間、不必要地占用,避免故障在微服務系統(tǒng)中的蔓延,防止系統(tǒng)雪崩效應的發(fā)生。

Hystrix(服務容錯與保護組件)讓服務擁有自我保護的能力
  1. 保護線程資源
    防止單個服務的故障耗盡系統(tǒng)中的所有線程資源。
  2. 快速失敗機制
    當某個服務發(fā)生了故障,不讓服務調用方一直等待,而是直接返回請求失敗。
  3. 監(jiān)控功能
    提供熔斷器故障監(jiān)控組件HystrixDashboard(隨時監(jiān)控熔斷器的狀態(tài))。Hystrix 會持續(xù)地記錄所有通過 Hystrix 發(fā)起的請求的執(zhí)行信息,并以統(tǒng)計報表的形式展示給用戶,包括每秒執(zhí)行請求的數(shù)量、成功請求的數(shù)量和失敗請求的數(shù)量等。
  4. 服務降級 FallBack(保證當前服務不受其他服務故障的影響,提高服務的健壯性)
    (既可以在服務端,也可以在客戶端)提供一個請求失敗后的降級方案(通常是一個兜底方法,當請求失敗后即調用該方法)。
    使用場景:
      1. 在服務器壓力劇增時,根據(jù)實際業(yè)務情況及流量,對一些不重要、不緊急的服務進行有策略地不處理或簡單處理,從而釋放服務器資源以保證核心服務正常運作。
      2. 當某些服務不可用時,為了避免長時間等待造成服務卡頓或雪崩效應,而主動執(zhí)行備用的降級邏輯立刻返回一個友好的提示,以保障主體業(yè)務不受影響。
      3. 程序運行異常、服務超時、熔斷器處于打開狀態(tài)、線程池資源耗盡。
    使用步驟:
      重寫HystrixCommand的getFallBack()方法 或 HystrixObservableCommand的resumeWithFallback()方法來使服務支持降級。
  5. 服務熔斷(防止故障擴散到其他服務)
    為了應對雪崩效應而出現(xiàn)的一種微服務鏈路保護機制。
    Hystrix會監(jiān)控微服務間調用的狀況,當某個微服務不可用 或 響應時間太長 或 失敗調用到一定比例時,為了保護系統(tǒng)的整體可用性,就會啟動熔斷機制(暫時切斷請求對該服務的調用,并快速返回一個友好的錯誤響應)。這種熔斷狀態(tài)不是永久的,在經(jīng)歷了一定的時間后,熔斷器會再次檢測該微服務是否恢復正常,若服務恢復正常則恢復其調用鏈路。
    熔斷狀態(tài)(3種):
      1. 熔斷關閉狀態(tài)(Closed)
        當服務訪問正常時,熔斷器處于關閉狀態(tài),服務調用方可以正常地對服務進行調用。
      2. 熔斷開啟狀態(tài)(Open)
        默認情況下,在固定時間內(nèi)接口調用出錯比率達到一個閾值(例如 50%),熔斷器會進入熔斷開啟狀態(tài)。進入熔斷狀態(tài)后,后續(xù)對該服務的調用都會被切斷,熔斷器會執(zhí)行本地的降級(FallBack)方法。
      3. 半熔斷狀態(tài)(Half-Open)
        在熔斷開啟一段時間之后,熔斷器會進入半熔斷狀態(tài)。在半熔斷狀態(tài)下,熔斷器會嘗試恢復服務調用方對服務的調用,允許部分請求調用該服務,并監(jiān)控其調用成功率。如果成功率達到預期,則說明服務已恢復正常,熔斷器進入關閉狀態(tài);如果成功率仍舊很低,則重新進入熔斷開啟狀態(tài)。
    熔斷流程:
      1. 當服務的調用出錯率達到或超過Hystix規(guī)定的比率(默認為50%)后,熔斷器進入熔斷開啟狀態(tài)。
      2. 熔斷器進入熔斷開啟狀態(tài)后,Hystrix會啟動一個休眠時間窗,在這個時間窗內(nèi),該服務的降級邏輯會臨時充當業(yè)務主邏輯,而原來的業(yè)務主邏輯不可用。
      3. 當有請求再次調用該服務時,會直接調用降級邏輯快速地返回失敗響應,以避免系統(tǒng)雪崩。
      4. 當休眠時間窗到期后,Hystrix 會進入半熔斷轉態(tài),允許部分請求對服務原來的主業(yè)務邏輯進行調用,并監(jiān)控其調用成功率。
      5. 如果調用成功率達到預期,則說明服務已恢復正常,Hystrix 進入熔斷關閉狀態(tài),服務原來的主業(yè)務邏輯恢復;否則 Hystrix 重新進入熔斷開啟狀態(tài),休眠時間窗口重新計時,繼續(xù)重復第 2 到第 5 步。

SpringCloudHystrix 
  對Netflix公司的Hystrix開源組件進行二次封裝,提供了熔斷器功能,能夠有效地阻止分布式微服務系統(tǒng)中出現(xiàn)聯(lián)動故障,以提高微服務系統(tǒng)的彈性。
  具有服務降級、服務熔斷、線程隔離、請求緩存、請求合并以及實時故障監(jiān)控等強大功能。

@HystrixCommand注解的commandProperties屬性的參數(shù)
  1. metrics.rollingStats.timeInMilliseconds(統(tǒng)計時間窗)
    監(jiān)控統(tǒng)計時間窗內(nèi)的服務調用出錯率。
  2. circuitBreaker.sleepWindowInMilliseconds(休眠時間窗)
    熔斷開啟狀態(tài)持續(xù)一段時間后,熔斷器會自動進入半熔斷狀態(tài),這段時間就被稱為休眠窗口期。
  3. circuitBreaker.requestVolumeThreshold(請求總數(shù)閥值)
    在統(tǒng)計時間窗內(nèi),請求總數(shù)必須到達一定的數(shù)量級,Hystrix 才可能會將熔斷器打開進入熔斷開啟轉態(tài),而這個請求數(shù)量級就是 請求總數(shù)閥值。Hystrix 請求總數(shù)閾值默認為 20,這就意味著在統(tǒng)計時間窗內(nèi),如果服務調用次數(shù)不足 20 次,即使所有的請求都調用出錯,熔斷器也不會打開。
  4. circuitBreaker.errorThresholdPercentage(錯誤百分比閾值)
    當請求總數(shù)在統(tǒng)計時間窗內(nèi)超過了請求總數(shù)閥值,且請求調用出錯率超過一定的比例,熔斷器才會打開進入熔斷開啟轉態(tài),而這個比例就是錯誤百分比閾值。錯誤百分比閾值設置為 50,就表示錯誤百分比為 50%,如果服務發(fā)生了 30 次調用,其中有 15 次發(fā)生了錯誤,即超過了 50% 的錯誤百分比,這時候將熔斷器就會打開。
三種熔斷狀態(tài)轉換

示例(服務端降級)

以之前的例子為基礎
1. 創(chuàng)建spring-cloud-provider-user-hystrix-8004服務提供者,修改pom.xml:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--添加 Spring Boot 的監(jiān)控模塊-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</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>
            <version>2.2.10.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建application.yml(類路徑resources下)
server:
  port: 8004 #服務端口號
spring:
  application:
    name: springCloudProviderUserHystrix  #對外暴露的微服務名稱
  jackson:
    serialization:
      FAIL_ON_EMPTY_BEANS: false
######### SpringCloud自定義服務名稱和ip地址 #############
eureka:
  client: #將客戶端注冊到eureka服務列表內(nèi)
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/   #該地址為 7001注冊中心在application.yml中暴露出來的注冊地址 (單機版)
  instance:
    instance-id: spring-cloud-provider-8004 #自定義服務名稱信息
    prefer-ip-address: true  #顯示訪問路徑的ip地址
########### SpringCloud使用 SpringBootActuator 監(jiān)控完善信息 ########
# SpringBoot2.50對 actuator監(jiān)控屏蔽了大多數(shù)的節(jié)點,只暴露了heath節(jié)點,本段配置(*)就是為了開啟所有的節(jié)點
management:
  endpoints:
    web:
      exposure:
        include: "*"   # * 在yaml 文件屬于關鍵字,所以需要加引號
info:
  app.name: spring-cloud-provider-user-hystrix
  company.name: com.sst.cx
  build.aetifactId: @project.artifactId@
  build.version: @project.version@
3. 創(chuàng)建UserService.java(com.sst.cx.service)
package com.sst.cx.service;
public interface UserService {
    // hystrix 熔斷器ok
    public String userInfo_Ok(Integer id);
    // hystrix 熔斷器超時
    public String userInfo_Timeout(Integer id);
}
4. 創(chuàng)建UserServiceImpl.java(com.sst.cx.service.impl)
package com.sst.cx.service.impl;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import com.sst.cx.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public String userInfo_Ok(Integer id) {
        return "線程池:" + Thread.currentThread().getName() + "  userInfo_Ok,id:   " + id;
    }
    // 一旦該方法失敗并拋出了異常信息后,會自動調用@HystrixCommand注解標注的fallbackMethod指定的方法(即指定降級方法)
    @HystrixCommand(fallbackMethod = "user_TimeoutHandler",
            commandProperties =
                    // 規(guī)定5秒鐘以內(nèi)就不報錯,正常運行,超過5秒就報錯,調用指定的方法
                    {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")})
    @Override
    public String userInfo_Timeout(Integer id) {
        int outTime = 6;
        try {
            TimeUnit.SECONDS.sleep(outTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "線程池:" + Thread.currentThread().getName() + "  userInfo_Timeout,id:   " + id + "  耗時: " + outTime;
    }
    // 當服務出現(xiàn)故障后,調用該方法給出友好提示
    public String user_TimeoutHandler(Integer id) {
       return  "系統(tǒng)繁忙請稍后再試!"+"線程池:" + Thread.currentThread().getName() + "  userInfo_Timeout,id:   " + id;
    }
}
5. 創(chuàng)建UserController.java(com.sst.cx.controller)
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import com.sst.cx.service.UserService;
@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;
    @Value("${server.port}")
    private String serverPort;
    @RequestMapping(value = "/user/hystrix/ok/{id}")
    public String userInfo_Ok(@PathVariable("id") Integer id) {
        String result = userService.userInfo_Ok(id);
        log.info("端口號:" + serverPort + " result:" + result);
        return result + ",   端口號:" + serverPort;
    }
    // Hystrix服務超時降級
    @RequestMapping(value = "/user/hystrix/timeout/{id}")
    public String userInfo_Timeout(@PathVariable("id") Integer id) {
        String result = userService.userInfo_Timeout(id);
        log.info("端口號:" + serverPort + " result:" + result);
        return result + ",   端口號:" + serverPort;
    }
}
6. 創(chuàng)建主啟動類
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient // 開啟Eureka客戶端功能
@EnableHystrix // 激活熔斷器功能
public class ServiceCloudProviderUser8004Application {
    public static void main(String[] args) {
        SpringApplication.run(ServiceCloudProviderUser8004Application.class, args);
    }
}
7. 依次啟動服務注冊中心集群、spring-cloud-provider-user-hystrix-8004,
在瀏覽器中訪問http://www.eureka7001.com:8004/user/hystrix/ok/1(正常訪問)、http://www.eureka7001.com:8004/user/hystrix/timeout/1(服務降級)

示例(客戶端降級)

對spring-cloud-feign-user-80項目添加Hystrix功能
1. 在pom.xml文件中添加Hystrix依賴
        <!--hystrix 依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>
2. 在application.yml文件中添加
####### 配置所有請求超時時間(單位為毫秒) ##########
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 7000
####### 配置具體請求超時時間 ########
    UserHystrixService#userInfo_Timeout(Integer):
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000      
###### 開啟hystrix
feign:
  hystrix:
    enabled: true #開啟客戶端hystrix 
3. 創(chuàng)建UserHystrixService.java(com.sst.cx.service)
package com.sst.cx.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(value = "springCloudProviderUserHystrix")
public interface UserHystrixService {
    @RequestMapping(value = "/user/hystrix/ok/{id}")
    public String userInfo_Ok(@PathVariable("id") Integer id);
    @RequestMapping(value = "/user/hystrix/timeout/{id}")
    public String userInfo_Timeout(@PathVariable("id") Integer id);
}
4. 創(chuàng)建HystrixController_Consumer.java(com.sst.cx.controller)
package com.sst.cx.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import com.sst.cx.service.UserHystrixService;
@Slf4j
@RestController
public class HystrixController_Consumer {
    @Resource
    private UserHystrixService userHystrixService;
    @RequestMapping(value = "/consumer/user/hystrix/ok/{id}")
    public String userInfo_Ok(@PathVariable("id") Integer id) {
        return userHystrixService.userInfo_Ok(id);
    }
    // 在客戶端進行降級
    @RequestMapping(value = "/consumer/user/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "user_TimeoutHandler") // 為該請求指定專屬的回退方法
    public String userInfo_Timeout(@PathVariable("id") Integer id) {
        String s = userHystrixService.userInfo_Timeout(id);
        log.info(s);
        return s;
    }
    // userInfo_Timeout方法的 專用 fallback 方法
    public String user_TimeoutHandler(@PathVariable("id") Integer id) {
        log.info("userInfo_Timeout 出錯,服務已被降級!");
        return "服務端系統(tǒng)繁忙,請稍后再試?。蛻舳?userInfo_Timeout 專屬的回退方法觸發(fā))";
    }
}
5. 在主啟動類上添加@EnableHystrix注解啟用Hystrix。
6. 修改spring-cloud-provider-user-hystrix-8004的UserServiceImpl.java的userInfo_Timeout()方法的outTime改為4(讓客戶端來處理降級)。
7. 重啟spring-cloud-provider-user-hystrix-8004、spring-cloud-feign-user-80
在瀏覽器中訪問http://www.eureka7001.com/consumer/user/hystrix/timeout/1
/*
1. hystrix.command.xxx#yyy(zzz).execution.isolation.thread.timeoutInMilliseconds=mmm
2. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=mmm
說明
    xxx:為包含該服務方法的類的名稱(通常為服務綁定接口的名稱),例如 UserHystrixService 接口。
    yyy:服務方法名,例如 userInfo_Timeout() 方法。
    zzz:方法內(nèi)的參數(shù)類型,例如 Integer、String 等等
    mmm:要設置的超時時間,單位為毫秒(1 秒 =1000 毫秒)
*/

【存疑:配置具體請求超時時間無效,正常請求未降級】

示例(全局降級方法)

通過上面的方式實現(xiàn)服務降級時,需要針對所有業(yè)務方法都配置降級方法,這極有可能會造成代碼的急劇膨脹。為了解決該問題,可以為所有業(yè)務方法指定一個全局的回退方法。
全局降級方法的優(yōu)先級較低,只有業(yè)務方法沒有指定其降級方法時,服務降級時才會觸發(fā)全局回退方法。若業(yè)務方法指定它自己的回退方法,那么在服務降級時,就只會直接觸發(fā)它自己的回退方法,而非全局回退方法。

HystrixController_Consumer.java中
1. 給該類添加@DefaultProperties注解,通過其defaultFallback屬性指定一個全局的降級方法。
  @DefaultProperties(defaultFallback = "user_Global_FallbackMethod") // 全局的服務降級方法
2. 創(chuàng)建該方法。
    public String user_Global_FallbackMethod() {
        return "運行出錯或服務端系統(tǒng)繁忙,請稍后再試?。蛻舳巳只赝朔椒ㄓ|發(fā),)";
    }
3. 在所有的業(yè)務方法上都標注@HystrixCommand注解。
重啟spring-cloud-feign-user-80,
在瀏覽器中訪問http://www.eureka7001.com/consumer/user/hystrix/timeout/1

【存疑:配置具體請求超時時間無效,正常請求未降級】

示例(解耦降級邏輯)

不管是業(yè)務方法指定的降級方法還是全局降級方法,它們都必須和業(yè)務方法在同一個類中才能生效,業(yè)務邏輯與降級邏輯耦合度極高。

1. 創(chuàng)建UserHystrixFallBackService.java,實現(xiàn)UserHystrixService(com.sst.cx.service)
package com.sst.cx.service;
import org.springframework.stereotype.Component;
@Component
public class UserHystrixFallBackService implements UserHystrixService {
    @Override
    public String userInfo_Ok(Integer id) {
        return "--------------------系統(tǒng)繁忙,請稍后重試?。ń怦罨赝朔椒ㄓ|發(fā))-----------------------";
    }
    @Override
    public String userInfo_Timeout(Integer id) {
        return "--------------------系統(tǒng)繁忙,請稍后重試?。ń怦罨赝朔椒ㄓ|發(fā))-----------------------";
    }
}
2. 修改UserHystrixService.java的@FeignClient注解,添加fallback屬性為UserHystrixFallBackService.class
  @FeignClient(value = "springCloudProviderUserHystrix",fallback = UserHystrixFallBackService.class)
3. 去除HystrixController_Consumer.java的@DefaultProperties注解、降級方法。
4. 重啟spring-cloud-feign-user-80,
在瀏覽器中訪問http://www.eureka7001.com/consumer/user/hystrix/timeout/1

【存疑:配置具體請求超時時間無效,正常請求未降級】

示例(驗證熔斷機制)

1. 在spring-cloud-provider-user-hystrix-8004的UserService接口中,添加一個userCircuitBreaker()方法:
    // hystrix 熔斷機制
    public String userCircuitBreaker(Integer id);
2. 在UserServiceImpl.java中,添加userCircuitBreaker()方法實現(xiàn):
    //
    @Override
    @HystrixCommand(fallbackMethod = "userCircuitBreaker_fallback", commandProperties = {
            // 以下參數(shù)在 HystrixCommandProperties 類中有默認配置
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否開啟熔斷器
            @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1000"), // 統(tǒng)計時間窗
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 統(tǒng)計時間窗內(nèi)請求次數(shù)
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 休眠時間窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 在統(tǒng)計時間窗口期以內(nèi),請求失敗率達到 60%時進入熔斷狀態(tài)
    })
    public String userCircuitBreaker(Integer id) {
        if (id < 0) {
            // 當傳入的 id 為負數(shù)時,拋出異常,調用降級方法
            throw new RuntimeException("id 不能是負數(shù)!");
        }
        return Thread.currentThread().getName() + "\t" + "調用成功";
    }
    public String userCircuitBreaker_fallback(Integer id) {
        return "id 不能是負數(shù),請稍后重試!\t id:" + id;
    }
3. 在UserController.java中,添加userCircuitBreaker()方法(對外提供服務):
    // Hystrix服務熔斷
    @RequestMapping(value = "/user/hystrix/circuit/{id}")
    public String deptCircuitBreaker(@PathVariable("id") Integer id){
        String result = userService.userCircuitBreaker(id);
        log.info("result:"+result);
        return result;
    }
4. 重啟spring-cloud-provider-user-hystrix-8004
在瀏覽器中訪問http://www.eureka7001.com:8004/user/hystrix/circuit/1;
多次請求http://www.eureka7001.com:8004/user/hystrix/circuit/-1,使調用出錯率大于錯誤百分比閥值;
多次請求http://www.eureka7001.com:8004/user/hystrix/circuit/1,當服務調用正確率上升到一定的利率后,Hystrix 進入熔斷關閉狀態(tài)。

【存疑:一直沒進入熔斷狀態(tài)】

示例(故障監(jiān)控)

創(chuàng)建spring-cloud-consumer-user-hystrix-dashboard-9002子項目 來監(jiān)控spring-cloud-provider-user-hystrix-8004的運行情況。

1. 修改pom.xml文件
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--Spring Boot 測試依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--hystrix-dashboard 監(jiān)控的依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>
        <!--添加 Spring Boot 的監(jiān)控模塊-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建application.yml文件(類路徑resources目錄下)
server:
  port: 9002  #端口號
#http://www.eureka7001.com:9002/hystrix 熔斷器監(jiān)控頁面
# localhost:8004//actuator/hystrix.stream 監(jiān)控地址
hystrix:
  dashboard:
    proxy-stream-allow-list:
      - "localhost"
3. 在主啟動類上添加@EnableHystrixDashboard注解開啟Hystrix 監(jiān)控功能
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class ServiceCloudConsumerUserHystrixDashboard9002Application{
    public static void main(String[] args) {
        SpringApplication.run(ServiceCloudConsumerUserHystrixDashboard9002Application.class, args);
    }
}
4. 創(chuàng)建HystrixDashboardConfig.java配置類
package com.sst.cx.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HystrixDashboardConfig {
    // Hystrix dashboard 監(jiān)控界面必須配置
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/actuator/hystrix.stream");// 訪問路徑
        registrationBean.setName("hystrix.stream");
        return registrationBean;
    }
}
5. 啟動spring-cloud-consumer-user-hystrix-dashboard-9002、spring-cloud-provider-user-hystrix-8004
在瀏覽器中訪問http://www.eureka7001.com:9002/hystrix,填入localhost:8004//actuator/hystrix.stream、2000、Hystrix Circuit,點擊下MonitorStream按鈕跳轉到監(jiān)控頁面,
在瀏覽器中多次訪問http://www.eureka7001.com:8004/user/hystrix/circuit/1、http://www.eureka7001.com:8004/user/hystrix/circuit/-1,查看Hystrix監(jiān)控頁面;
監(jiān)控頁面

2. SpringCloud Gateway(網(wǎng)關組件)

在微服務架構中,一個系統(tǒng)往往由多個微服務組成,而這些服務可能部署在不同機房、不同地區(qū)、不同域名下。這種情況下,客戶端(如:瀏覽器、手機、Postman軟件工具等)想要直接請求這些服務,就需要知道它們具體的地址信息,例如IP地址、端口號等。
這種客戶端直接請求服務的方式存在以下問題:
  1. 當服務數(shù)量眾多時,客戶端需要維護大量的服務地址,這對于客戶端來說,是非常繁瑣復雜的。
  2. 在某些場景下可能會存在跨域請求的問題。
  3. 身份認證的難度大,每個微服務需要獨立認證。
可以通過API網(wǎng)關來解決這些問題。

API網(wǎng)關(一個搭建在客戶端和微服務之間的服務)
  可以在API網(wǎng)關中處理一些非業(yè)務功能的邏輯(如:權限驗證、監(jiān)控、緩存、請求路由等)。
  API網(wǎng)關就像整個微服務系統(tǒng)的門面一樣,是系統(tǒng)對外的唯一入口。有了它,客戶端會先將請求發(fā)送到 API 網(wǎng)關,然后由 API 網(wǎng)關根據(jù)請求的標識信息將請求轉發(fā)到微服務實例。
  對于服務數(shù)量眾多、復雜度較高、規(guī)模比較大的系統(tǒng)來說,使用 API 網(wǎng)關具有以下好處:
    1. 客戶端通過 API 網(wǎng)關與微服務交互時,客戶端只需要知道 API 網(wǎng)關地址即可,而不需要維護大量的服務地址,簡化了客戶端的開發(fā)。
    2. 客戶端直接與 API 網(wǎng)關通信,能夠減少客戶端與各個服務的交互次數(shù)。
    3. 客戶端與后端的服務耦合度降低。
    4. 節(jié)省流量,提高性能,提升用戶體驗。
    5. API 網(wǎng)關還提供了安全、流控、過濾、緩存、計費以及監(jiān)控等 API 管理功能。
  常見的API網(wǎng)關實現(xiàn)方案:
    1. Spring Cloud Gateway
    2. Spring Cloud Netflix Zuul
    3. Kong
    4. Nginx+Lua
    5. Traefik
兩種服務訪問方式對比
SpringCloud Gateway 
  基于Spring5.0、SpringBoot2.0、Project Reactor等技術開發(fā)的高性能API網(wǎng)關組件。Spring Cloud Gateway 是基于 WebFlux 框架實現(xiàn)的,而 WebFlux 框架底層則使用了高性能的 Reactor 模式通信框架 Netty。
  旨在提供一種簡單而有效的途徑來發(fā)送API,并為它們提供橫切關注點(如:安全性、監(jiān)控/指標、彈性)。 

核心概念(定義路由轉發(fā)規(guī)則)
  1. Route(路由)  Route和Predicate必須同時聲明
    網(wǎng)關最基本的模塊。它由一個 ID、一個目標 URI、一組斷言(Predicate)和一組過濾器(Filter)組成。
  2. Predicate(斷言,實現(xiàn)Route路由的匹配規(guī)則)   
    路由轉發(fā)的判斷條件(對HTTP請求進行匹配,如:請求方式、請求路徑、請求頭、參數(shù)等),如果請求與斷言匹配成功(滿足了Predicate的條件),則將請求轉發(fā)到指定的服務。
    需要注意以下3點:
      1. Route 路由與Predicate 斷言的對應關系為“一對多”,一個路由可以包含多個不同斷言。
      2. 一個請求想要轉發(fā)到指定的路由上,就必須同時匹配路由上的所有斷言。
      3. 當一個請求同時滿足多個路由的斷言條件時,請求只會被首個成功匹配的路由轉發(fā)。
  3. Filter(過濾器)對請求進行攔截和修改、對上文的響應進行再處理
    通常情況下,出于安全方面的考慮,服務端提供的服務往往都會有一定的校驗邏輯(如:用戶登陸狀態(tài)校驗、簽名校驗等)。在微服務架構中,系統(tǒng)由多個微服務組成,所有這些服務都需要這些校驗邏輯,可以將這些校驗邏輯寫到SpringCloudGateway的Filter過濾器中。
    Filter的分類
      1. Pre 類型     
        在請求被轉發(fā)到微服務之前可以對請求進行攔截和修改(如:參數(shù)校驗、權限校驗、流量監(jiān)控、日志輸出以及協(xié)議轉換等操作)。
      2. Post 類型    
        在微服務對請求做出響應后可以對響應進行攔截和再處理(如:修改響應內(nèi)容或響應頭、日志輸出、流量監(jiān)控等)。
    Filter的分類(按作用范圍)  
      1. GatewayFilter網(wǎng)關過濾器
        作用于單個路由或者一組路由上的過濾器(對單個路由或者一組路由上傳入的請求和傳出響應進行攔截,并實現(xiàn)一些與業(yè)務無關的功能,比如登陸狀態(tài)校驗、簽名校驗、權限校驗、日志輸出、流量監(jiān)控等)。
        GatewayFilter在配置文件中的寫法與Predicate類似,格式如下:
/*
    spring:
      cloud:
        gateway: 
          routes:
            - id: xxxx
              uri: xxxx
              predicates:
                - Path=xxxx
              filters:
                - AddRequestParameter=X-Request-Id,1024 #過濾器工廠會在匹配的請求頭加上一對請求頭,名稱為 X-Request-Id 值為 1024
                - PrefixPath=/user #在請求路徑前面加上 /user
                ……
*/
      2. GlobalFilter
        作用于所有的路由上的全局過濾器。
        可以實現(xiàn)一些統(tǒng)一化的業(yè)務功能(如:權限認證、IP 訪問限制等)。當某個請求被路由匹配時,那么所有的 GlobalFilter 會和該路由自身配置的 GatewayFilter 組合成一個過濾器鏈。
        提供了多種默認的GlobalFilter(如:與轉發(fā)、路由、負載均衡等相關的全局過濾器)。但在實際的項目開發(fā)中,通常都會自定義GlobalFilter全局過濾器以滿足自身業(yè)務需求,很少直接使用默認GlobalFilter。 

動態(tài)路由
  默認情況下,SpringCloudGateway會根據(jù)服務注冊中心(如:EurekaServer)中維護的服務列表,以服務名(spring.application.name)作為路徑創(chuàng)建動態(tài)路由進行轉發(fā),從而實現(xiàn)動態(tài)路由功能。
  可以在配置文件中,將Route的uri地址修改為以下形式:
    lb://service-name
  說明:
    1. lb:uri的協(xié)議(表示開啟SpringCloudGateway的負載均衡功能)。
    2. service-name:服務名(SpringCloudGateway會根據(jù)它獲取到具體的微服務地址)。

特性:
    1. 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0 構建。
    2. 能夠在任意請求屬性上匹配路由。
    3. predicates(斷言) 和 filters(過濾器)是特定于路由的。
    4. 集成了 Hystrix 熔斷器。
    5. 集成了 Spring Cloud DiscoveryClient(服務發(fā)現(xiàn)客戶端)。
    6. 易于編寫斷言和過濾器。
    7. 能夠限制請求頻率。
    8. 能夠重寫請求路徑。

工作流程:
    1. 客戶端將請求發(fā)送到SpringCloudGateway上。
    2. SpringCloudGateway通過 GatewayHandlerMapping 找到與請求相匹配的路由,將其發(fā)送給 GatewayWebHandler。
    3. GatewayWebHandler通過指定的過濾器鏈(Filter Chain),將請求轉發(fā)到實際的服務節(jié)點中,執(zhí)行業(yè)務邏輯返回響應結果。
    4. 過濾器之間用虛線分開是因為過濾器可能會在轉發(fā)請求之前(pre)或之后(post)執(zhí)行業(yè)務邏輯。
    5. 過濾器(Filter)可以在請求被轉發(fā)到服務端前,對請求進行攔截和修改(如:參數(shù)校驗、權限校驗、流量監(jiān)控、日志輸出以及協(xié)議轉換等)。
    6. 過濾器可以在響應返回客戶端之前,對響應進行攔截和再處理(如:修改響應內(nèi)容或響應頭、日志輸出、流量監(jiān)控等)。
    7. 響應原路返回給客戶端。
SpringCloudGateway工作流程

Predicate斷言匹配
斷言 示例 說明
Path - Path=/user/list/** 請求路徑必須與 /user/list/** 匹配。
Before - Before=2001-10-20T11:47:34.255+08:00[Asia/Shanghai] 必須是 2001 年 10 月 20 日 11 時 47 分 34.255 秒之前的請求。
After - After=2001-10-20T11:47:34.255+08:00[Asia/Shanghai] 必須是 2001 年 10 月 20 日 11 時 47 分 34.255 秒之后的請求。
Between - Between=2001-10-20T15:18:33.226+08:00[Asia/Shanghai],2001-10-20T15:23:33.226+08:00[Asia/Shanghai] 必須是 2001 年 10 月 20 日 15 時 18 分 33.226 秒 到 2001 年 10 月 20 日 15 時 23 分 33.226 秒之間的請求。
Cookie - Cookie=name,com.sst.cx 必須攜帶 Cookie 且 Cookie 的內(nèi)容為 name=com.sst.cx 的請求。
Header - Header=X-Request-Id,\d+ 請求頭上必須攜帶屬性 X-Request-Id 且屬性值為整數(shù)的請求。
Method - Method=GET 必須是GET請求。
路由過濾器 描述 參數(shù) 使用示例
AddRequestHeader 攔截請求 并添加一個指定的請求頭參數(shù)。 name:需要添加的請求頭參數(shù)的 key;value:需要添加的請求頭參數(shù)的value。 - AddRequestHeader=my-request-header,1024
AddRequestParameter 攔截請求 并添加一個指定的請求參數(shù)。 name:需要添加的請求參數(shù)的 key;value:需要添加的請求參數(shù)的 value。 - AddRequestParameter=my-request-param,c.biancheng.net
AddResponseHeader 攔截響應 并添加一個指定的響應頭參數(shù)。 name:需要添加的響應頭的 key;value:需要添加的響應頭的 value。 - AddResponseHeader=my-response-header,c.biancheng.net
PrefixPath 攔截請求 并在請求路徑前增加一個指定的前綴。 prefix:需要增加的路徑前綴。 - PrefixPath=/consumer
PreserveHostHeader 轉發(fā)請求時,保持客戶端的Host信息不變,然后將它傳遞到提供具體服務的微服務中。 - PreserveHostHeader
RemoveRequestHeader 移除請求頭中指定的參數(shù)。 name:需要移除的請求頭的 key。 - RemoveRequestHeader=my-request-header
RemoveResponseHeader 移除響應頭中指定的參數(shù)。 name:需要移除的響應頭。 - RemoveResponseHeader=my-response-header
RemoveRequestParameter 移除指定的請求參數(shù)。 name:需要移除的請求參數(shù)。 - RemoveRequestParameter=my-request-param
RequestSize 配置請求體的大小,當請求體過大時,將會返回 413 Payload Too Large。 maxSize:請求體的大小。 - name: RequestSize args: maxSize: 5000000

示例(Predicate的使用)

創(chuàng)建spring-cloud-gateway-9527子項目
1. 修改pom.xml文件:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 注意:在gateway網(wǎng)關服務中不能引入spring-boot-starter-web依賴,否則會報錯 -->
        <!-- SpringCloudGateway網(wǎng)關依賴-->
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建application.yml(類路徑Resources目錄下)
server:
  port: 9527  #端口號
spring:
  application:
    name: microServiceCloudGateway
  cloud:
    gateway: #網(wǎng)關路由配置
      routes:
        #將spring-cloud-provider-user-8001提供的服務隱藏起來,不暴露給客戶端,只給客戶端暴露API網(wǎng)關的地址9527
        - id: provider_user_list_routh   #路由id,沒有固定規(guī)則,但必須唯一,建議與服務名對應
          uri: http://localhost:8001          #匹配后提供服務的路由地址
          predicates:
            #斷言條件(必選全部符合)
            - Path=/user/list/**               #路徑匹配(注意:Path 中 P 為大寫)
            - Method=GET #僅GET請求
eureka:
  instance:
    instance-id: micro-service-cloud-gateway-9527
    hostname: micro-service-cloud-gateway
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
3. 依次啟動Eureka服務注冊中心集群、spring-cloud-provider-user-8001、spring-cloud-gateway-9527,
在瀏覽器訪問http://localhost:9527/user/list

示例(動態(tài)路由)

1. 修改pom.xml文件:
server:
  port: 9527 #端口號
spring:
  application:
    name: microServiceCloudGateway  #服務注冊中心注冊的服務名
  cloud:
    gateway: #網(wǎng)關路由配置
      discovery:
        locator:
          enabled: true #默認值為 true,即默認開啟從注冊中心動態(tài)創(chuàng)建路由的功能,利用微服務名進行路由
      routes:
        #將spring-cloud-provider-user-8001提供的服務隱藏起來,不暴露給客戶端,只給客戶端暴露API網(wǎng)關的地址9527
        - id: provider_user_list_routh   #路由id,沒有固定規(guī)則,但唯一,建議與服務名對應
          uri: lb://springCloudProviderUser #動態(tài)路由,使用服務名代替上面的具體帶端口   http://www.eureka7001.com:9527/user/list
          predicates:
            #斷言條件(必選全部符合)
            - Path=/user/list/**    #路徑匹配 注意:Path 中 P 為大寫
            - Method=GET #只能時 GET 請求時,才能訪問
eureka:
  instance:
    instance-id: micro-service-cloud-gateway-9527
    hostname: micro-service-cloud-gateway
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/

2. 重新啟動spring-cloud-gateway-9527,
在瀏覽器中訪問http://localhost:9527/user/list

示例(GatewayFilter網(wǎng)關過濾器)

1. 修改pom.xml文件
  predicates:
    - Path=/get/**
  filters:
    - PrefixPath=/user #在請求路徑上增加一個前綴 /user

2. 重新啟動spring-cloud-gateway-9527,
在瀏覽器中訪問http://localhost:9527/get/1

示例(GlobalFilter全局過濾器)

1. 創(chuàng)建MyGlobalFilter.java(自定義全局網(wǎng)關過濾器類)
package com.sst.cx.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
// 自定義全局網(wǎng)關過濾器(GlobalFilter)
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("進入自定義的全局過濾器 MyGlobalFilter" + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            log.info("參數(shù) uname 不能為 null!");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        // 過濾器的順序,0 表示第一個
        return 0;
    }
}
2. 重新啟動spring-cloud-gateway-9527,
在瀏覽器訪問http://localhost:9527/user/list?uname=123456,不添加uname參數(shù)會被過濾掉。

3. SpringCloud Config(分布式配置組件)

在分布式微服務系統(tǒng)中,幾乎所有服務的運行都離不開配置文件的支持,這些配置文件通常由各個服務自行管理,以properties或yml 格式(如:application.properties、application.yml)保存在各個微服務的類路徑下。
這種將配置文件散落在各個服務中的管理方式,存在以下問題:
  1. 管理難度大:配置文件散落在各個微服務中,難以管理。
  2. 安全性低:配置跟隨源代碼保存在代碼庫中,容易造成配置泄漏。
  3. 時效性差:微服務中的配置修改后,必須重啟服務,否則無法生效。
  4. 局限性明顯:無法支持動態(tài)調整(如:日志開關、功能開關)。
使用配置中心(如:百度的Disconf、淘寶的diamond、360的QConf、攜程的Apollo、SpringCloud的Config)對配置進行統(tǒng)一管理可解決這些問題。
SpringCloudConfig
  為微服務架構中各個微服務提供集中化的外部配置支持。即:將各個微服務的配置文件集中存儲在一個外部的存儲倉庫或系統(tǒng)(如:Git、SVN等)中,對配置統(tǒng)一管理。
  分為2部分:
    1. ConfigServer(分布式配置中心),它是一個獨立運行的微服務應用,用來連接配置倉庫并為客戶端提供獲取配置信息、加密信息和解密信息的訪問接口。
    2. ConfigClient:微服務架構中的各個微服務,它們通過ConfigServer對配置進行管理,并從ConfigSever中獲取和加載配置信息。
  Spring Cloud Config 默認使用 Git 存儲配置信息,因此使用 Spirng Cloud Config 構建的配置服務器天然就支持對微服務配置的版本管理。我們可以使用 Git 客戶端工具方便地對配置內(nèi)容進行管理和訪問。除了 Git 外,Spring Cloud Config 還提供了對其他存儲方式的支持,例如 SVN、本地化文件系統(tǒng)等。

工作流程:
    1. 開發(fā)或運維人員提交配置文件到遠程的 Git 倉庫。
    2. Config 服務端(分布式配置中心)負責連接配置倉庫 Git,并對 Config 客戶端暴露獲取配置的接口。
    3. Config 客戶端通過 Config 服務端暴露出來的接口,拉取配置倉庫中的配置。
    4. Config 客戶端獲取到配置信息,以支持服務的運行。

特點:
    1. 與Spring的生態(tài)體系無縫集成。
    2. 將所有微服務的配置文件集中存儲在一個外部的存儲倉庫或系統(tǒng)中統(tǒng)一管理。
    3. Spring Cloud Config 配置中心將配置以 REST 接口的形式暴露給各個微服務,以方便各個微服務獲取。
    4. 微服務可以通過 Spring Cloud Config 向配置中心統(tǒng)一拉取屬于它們自己的配置信息。
    5. 當配置發(fā)生變化時,微服務不需要重啟即可感知到配置的變化,并自動獲取和應用最新配置。
    6. 一個應用可能有多個環(huán)境,例如開發(fā)(dev)環(huán)境、測試(test)環(huán)境、生產(chǎn)(prod)環(huán)境等等,開發(fā)人員可以通過 Spring Cloud Config 對不同環(huán)境的各配置進行管理,且能夠確保應用在環(huán)境遷移后仍然有完整的配置支持其正常運行。

配置文件的訪問規(guī)則(在瀏覽器中可直接對配置文件進行訪問)
  格式1. /{application}/{profile}[/{label}]   
    例:/config/dev/master
  格式2. /{application}-{profile}.{suffix}    
    例:/config-dev.yml
  格式3. /{label}/{application}-{profile}.{suffix}    
    例:/master/config-dev.yml
  說明:
    1. {application}:應用名(即:配置文件的名稱),如:config-dev。
    2. {profile}:環(huán)境名,一個項目通常都有開發(fā)(dev)版本、測試(test)版本、生產(chǎn)(prod)版本,配置文件則以application-{profile}.yml 的形式進行區(qū)分,如:application-dev.yml、application-test.yml、application-prod.yml等。
    3. {label}:Git分支名,默認:master分支,當訪問默認分支下的配置文件時,該參數(shù)可以省略,即第二種訪問方式。
    4. {suffix}:配置文件的后綴,如:config-dev.yml的后綴為yml。
SpringCloudConfig 工作原理

示例(搭建Config服務端)

1. 在Github或Gitee上創(chuàng)建一個名為 springcloud-config 的倉庫(Repository),并獲取該倉庫的地址。
2. 創(chuàng)建spring-cloud-config-center-3344子項目,修改pom.xml文件
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--配置中心服務器依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
3. 創(chuàng)建application.yml(類路徑Resources目錄下)
server:
  port: 3344 #端口號
spring:
  application:
    name: spring-cloud-config-center #服務名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/xxx/springcloud-config.git
          #倉庫名
          search-paths:
            - springcloud-config
          force-pull: true
          # 如果Git倉庫為公開倉庫,可以不填寫用戶名和密碼,如果是私有倉庫需要填寫
          username: ********
          password: ********
      #分支名
      label: master
eureka:                                            
  client: #將客戶端注冊到 eureka 服務列表內(nèi)
    service-url: 
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/  #將服務注冊到 Eureka 集群
4. 給主啟動類添加@EnableConfigServer注解開啟SpringCloudConfig配置中心功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class MicroServiceCloudConfigCenter3344Application {
    public static void main(String[] args) {
        SpringApplication.run(MicroServiceCloudConfigCenter3344Application.class, args);
    }
}
5. 新建一個名為config-dev.yml文件,并將其上傳到 springcloud-config 倉庫 master 分支下。
config:
  info: com.sst.cx
  version: 1.0
6. 依次啟動服務注冊中心集群和 spring-cloud-config-center-3344,在瀏覽器中訪問http://localhost:3344/master/config-dev.yml 或 http://localhost:3344/config-dev.yml 或 http://localhost:3344/config/dev/master
瀏覽器中訪問配置文件

示例(搭建Config客戶端)

創(chuàng)建spring-cloud-config-client-3355子項目
1. 修改pom.xml文件:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Spring Cloud Config 客戶端依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- SpringCloud 2020.* 版本把bootstrap禁用,添加該依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建bootstrap.yml(類路徑Resources目錄下)
#bootstrap.yml 是系統(tǒng)級別的,加載優(yōu)先級高于 application.yml ,負責從外部加載配置并解析
server:
  port: 3355 #端口號
spring:
  application:
    name: spring-cloud-config-client #服務名
  cloud:
    config:
      label: master #分支名稱
      name: config  #配置文件名稱,config-dev.yml 中的 config
      profile: dev  #環(huán)境名  config-dev.yml 中的 dev
      #這里不要忘記添加 http:// 否則無法讀取
      uri: http://localhost:3344 #Spring Cloud Config 服務端(配置中心)地址
eureka:
  client: #將客戶端注冊到 eureka 服務列表內(nèi)
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/  #將服務注冊到 Eureka 集群
3. 創(chuàng)建ConfigClientController.java(通過該類獲取配置文件中的配置)
package com.sst.cx.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 讀取配置中心指定配置文件的內(nèi)容,并展示到頁面
@RestController
public class ConfigClientController {
    @Value("${server.port}")
    private String serverPort;
    @Value("${config.info}")
    private String configInfo;
    @Value("${config.version}")
    private String configVersion;
    @GetMapping(value = "/getConfig")
    public String getConfig() {
        return "info:" + configInfo + "<br/>version:" + configVersion + "<br/>port:" + serverPort;
    }
}
4. 給主啟動類添加@EnableEurekaClient注解開啟 Eureka客戶端功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class MicroServiceCloudConfigClient3355Application {
    public static void main(String[] args) {
        SpringApplication.run(MicroServiceCloudConfigClient3355Application.class, args);
    }
}
5. 啟動服務注冊中心集群、spring-cloud-config-center-3344、spring-cloud-config-client-3355,在瀏覽器中訪問http://localhost:3355/getConfig
6. 將配置文件 config-dev.yml 中 config.version 的值修改為 2.0
7. 在瀏覽器中訪問http://localhost:3355/getConfig,可以看到version還是1.0;訪問http://localhost:3344/master/config-dev.yml,是2.0;
9. 重啟spring-cloud-config-client-3355,在瀏覽器中訪問http://localhost:3355/getConfig,可以看到是2.0了。

示例(不重啟Config客戶端的情況下,手動刷新配置 來獲取最新配置)

從上例中可知,配置文件更新后
  1. SpringCloudConfig服務端可以直接從Git倉庫中獲取最新的配置。
  2. SpringCloudConfig客戶端則需要重啟,否則無法通過SpringCloudConfig服務端獲取最新的配置。

通過在Config客戶端中引入SpringBootActuator監(jiān)控組件來監(jiān)控配置的變化,可以在不重啟Config客戶端的情況下獲取最新配置。
使用步驟:
1. 在spring-cloud-config-client-3355的pom.xml文件中添加
    <!-- Spring Boot actuator 監(jiān)控模塊 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
2. 在bootstrap.yml配置文件中添加(對外暴露SpringBootActuator的監(jiān)控節(jié)點):
# Spring Boot 2.50對 actuator 監(jiān)控屏蔽了大多數(shù)的節(jié)點,只暴露了 health 節(jié)點,本段配置(*)就是為了開啟所有的節(jié)點
management:
  endpoints:
    web:
      exposure:
        include: *   # * 在yaml 文件屬于關鍵字,所以需要加引號
3. 在ConfigClientController類上添加@RefreshScope注解開啟配置刷新
4. 重啟spring-cloud-config-client-3355
修改config-dev.yml中的config.version為3.0
在瀏覽器中訪問http://localhost:3355/getConfig,還是2.0。
在終端執(zhí)行curl -X POST "http://localhost:3355/actuator/refresh"命令
在瀏覽器中訪問http://localhost:3355/getConfig,可以看到是3.0了。
SpringCloudCongfig手動刷新
這種方式帶來一個問題:只要配置倉庫中的配置發(fā)生改變,就需要挨個向Config客戶端手動發(fā)送POST請求,通知它們重新拉取配置。
在微服務架構中,一個系統(tǒng)往往包含十幾甚至幾十個服務,如果因為某一個配置文件的修改而向幾十個微服務發(fā)送POST請求,這顯然是不合理的。

SpringCloudBus(消息總線)
  通過輕量級的消息代理(如:RabbitMQ、Kafka)(構建一個公共的消息主題Topic,默認為“springCloudBus”,這個Topic中的消息會被所有服務實例監(jiān)聽和消費。當其中的一個服務刷新數(shù)據(jù)時,SpringCloudBus會把信息保存到Topic中,這樣監(jiān)聽這個Topic的服務就收到消息并自動消費)將微服務架構中的各個服務連接起來,實現(xiàn)廣播狀態(tài)更改、事件推送等功能,還可以實現(xiàn)微服務之間的通信功能。

Config+Bus實現(xiàn)配置的動態(tài)刷新(一次通知,處處生效)
  利用SpringCloudBus的特殊機制可以實現(xiàn)很多功能,如:配合SpringCloudConfig可以實現(xiàn)配置的動態(tài)刷新。當Git倉庫中的配置發(fā)生了改變,只需要向某一個服務(既可以是Config服務端,也可以是Config客戶端)發(fā)送一個 POST 請求,SpringCloudBus 就可以通過消息代理通知其他服務重新拉取最新配置,以實現(xiàn)配置的動態(tài)刷新。
  工作流程:
    1. 當 Git 倉庫中的配置發(fā)生改變后,運維人員向 Config 服務端發(fā)送一個 POST 請求,請求路徑為“/actuator/refresh”。
    2. Config 服務端接收到請求后,會將該請求轉發(fā)給服務總線 Spring Cloud Bus。
    3. Spring Cloud Bus 接到消息后,會通知給所有 Config 客戶端。
    4. Config 客戶端接收到通知,請求 Config 服務端拉取最新配置。
    5. 所有 Config 客戶端都獲取到最新的配置。
SpringCloudBus動態(tài)刷新配置的工作原理

示例(SpringCloudBus動態(tài)刷新配置)全局廣播

1. 在spring-cloud-config-center-3344的pom.xml文件中,添加
        <!--添加消息總線(Bus)對 RabbitMQ 的支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--添加Spring Boot actuator 監(jiān)控模塊的依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
2. 在spring-cloud-config-center-3344的application.xml文件中,spring下添加
##### RabbitMQ 相關配置,15672 是web 管理界面的端口,5672 是 MQ 的訪問端口###########
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
# Spring Boot 2.50對 actuator 監(jiān)控屏蔽了大多數(shù)的節(jié)點,只暴露了 heath 節(jié)點,本段配置(*)就是為了開啟所有的節(jié)點
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'
3. 在spring-cloud-config-client-3355的pom.xml文件中,添加
        <!--添加消息總線(Bus)對 RabbitMQ 的支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
4. 在spring-cloud-config-client-3355的bootstrap.xml文件中,spring下添加
##### RabbitMQ 相關配置,15672 是web 管理界面的端口,5672 是 MQ 的訪問端口###########
spring: 
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
5. 參照spring-cloud-config-client-3355,創(chuàng)建spring-cloud-config-client-3366,修改bootstrap.yml(
bootstrap.yml是系統(tǒng)級別的,加載優(yōu)先級高于application.yml ,負責從外部加載配置并解析)
server:
  port: 3366  #端口號為 3366
spring:
  application:
    name: spring-cloud-config-client-bus
  cloud:
    config:
      label: master #分支名稱
      name: config  #配置文件名稱,config-dev.yml 中的 config
      profile: dev  #配置文件的后綴名  config-dev.yml 中的 dev
      #這里不要忘記添加 http:// 否則無法讀取
      uri: http://localhost:3344 #spring cloud 配置中心地址
##### RabbitMQ 相關配置,15672 是web 管理界面的端口,5672 是 MQ 的訪問端口###########
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
###################### eureka 配置 ####################
eureka:
  client: #將客戶端注冊到 eureka 服務列表內(nèi)
    service-url:
      defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/  #將服務注冊到 Eureka 集群
# Spring Boot 2.50對 actuator 監(jiān)控屏蔽了大多數(shù)的節(jié)點,只暴露了 heath 節(jié)點,本段配置(*)就是為了開啟所有的節(jié)點
management:
  endpoints:
    web:
      exposure:
        include: "*"   # * 在yaml 文件屬于關鍵字,所以需要加引號
6. 啟動spring-cloud-config-center-3344、spring-cloud-config-client-3355、spring-cloud-config-client-3366,
在瀏覽器中訪問http://localhost:3355/getConfig、http://localhost:3366/getConfig
7. 將配置文件 config-dev.yml 中 config.version 的值修改為 4.0
8. 在終端執(zhí)行curl -X POST "http://localhost:3344/actuator/bus-refresh"
在瀏覽器中訪問http://localhost:3355/getConfig、http://localhost:3366/getConfig

可能遇到的錯誤:
  1. 執(zhí)行curl命令405(Method Not Allowed)
    原因:springcloud版本過高,降低版本,或者bus-refresh改為busrefresh
  2. 執(zhí)行curl命令500(Internal Server Error)

【存疑:改為busrefresh后報500】

示例(SpringCloudBus動態(tài)刷新配置)定點通知

定點通知:不再通知所有的Config客戶端,而是根據(jù)需求只通知指定Config客戶端。
只要我們在發(fā)送POST請求時使用以下格式即可:
  http://{hostname}:{port}/actuator/bus-refresh/{destination}
  說明:
    1. {hostname}:表示Config服務端的主機地址(域名 或 IP地址)。
    2. {port}:表示Config服務端的端口號。
    3. {destination}:表示需要定點通知的Config客戶端(微服務)。由Config客戶端的服務名(spring.application.name)+冒號+端口號(server.port)組成。

例:curl -X POST "http://localhost:3344/actuator/bus-refresh/spring-cloud-config-client:3355"
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容