天天看點

Spring Cloud 之 Config與動态路由.

一、簡介

 Spring Cloud Confg 是用來為分布式系統中的基礎設施和微服務應用提供集中化的外部配置支援,它分為服務端與用戶端兩個部分。其中服務端也稱為分布式配置中心,它是一個獨立的微服務應用,用來連接配接配置倉庫并為用戶端提供擷取配置資訊、加密/解密資訊等通路接口;而用戶端則是微服務架構中的各個微服務應用或基礎設施,它們通過指定的配置中心來管理應用資源與業務相關的配置内容,并在啟動的時候從配置中心擷取和加載配置資訊。

二、Spring Config Server

搭建一個 Config Server,首先需要一個倉庫,作為分布式配置中心的存儲。這裡我們選擇了 Github 作為我們的倉庫:https://github.com/JMCuixy/cloud-config-server/tree/master/config-repo

Spring Cloud 之 Config與動态路由.

1. pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
           

2. application.yml

server:
  port: 7001

spring:
  application:
    name: cloud-config-server

  # 配置完成後可通路的 url 如下,比如:http://localhost:7001/env/default
  # /{application}/{profile} [/{label}]
  # /{application}-{profile}.yml
  # /{label}/{application}-{profile}.yml
  # /{application}-{profile}.properties
  # /{label}/{application}-{profile}.properties
  cloud:
    config:
      # 為配置中心提供安全保護
      username: user
      password: password
      server:
        git:
          # 倉庫位址
          uri: https://github.com/JMCuixy/cloud-config-server.git
          # 搜尋路徑
          search-paths: config-repo
        # 通路 http://localhost:7001/actuator/health 可以擷取配置中心健康名額
        health:
          repositories:
            env:
              name: env
              profiles: default
              label: master

management:
  endpoint:
    health:
      enabled: true
      show-details: always

eureka:
  client:
    service-url:
      defaultZone: http://user:password@localhost:1111/eureka/
           

這裡我沒有配置 Github 的 username 和 password,用的是 SSH key 的方式。

3. ConfigApplication.java

// 開啟 Spring Cloud Config 的 Server 功能
@EnableConfigServer
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }

}
           

至此,一個 Spring Cloud Config Server 就搭建完成了。上面的配置中,我們将 Config Server 注冊到 Eureka Server 中,當作整個系統服務的一部分,是以 Config Client 隻要利用 Eureka 的服務發現維持與 Config Server 通信就可以了。

在Config Server 的檔案系統中,每次用戶端請求擷取配置資訊時,Confg Server 從 Git 倉庫中擷取最新配置到本地,然後在本地 Git 倉庫中讀取并傳回。當遠端倉庫無法擷取時,直接将本地内容傳回。

三、Spring Config Client

Spring Cloud Confg 的用戶端在啟動的時候,預設會從工程的 classpath 中加載配置資訊并啟動應用。隻有當我們配置 spring.cloud.config.uri(或者spring.cloud.config.discovery) 的時候,用戶端應用才會嘗試連接配接 Spring Cloud Confg 的服務端來擷取遠端配置資訊并初始化 Spring 環境配置。同時,我們必須将該參數配置在bootstrap.yml、環境變量或是其他優先級高于應用 Jar 包内的配置資訊中,才能正确加載到遠端配置。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- 當連接配接 config-server 失敗的時候,可增加重試-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

        <!--配置動态重新整理-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
           

2. bootstrap.yml 和 application.yml

  • bootstrap.yml
spring:
  application:
    # 對應配置檔案規則中的 {application} 部分
    name: env
  cloud:
    config:
      name: env
      # uri: http://localhost:7001
      discovery:
        enabled: true
        service-id: cloud-config-server
      # 環境變量  
      profile: default
      # 分支
      label: master
      # config Server 配置的安全資訊
      username: user
      password: password
      # 快速失敗響應(當發現 config-server 連接配接失敗時,就不做連接配接的準備工作,直接傳回失敗)
      fail-fast: true
      # 失敗重試
      retry:
        # 初始重試間隔時間,毫秒
        initial-interval: 1000
        # 下一間隔的乘數
        multiplier: 1.1
        # 最大間隔時間
        max-interval: 2000
        # 最多重試次數
        max-attempts: 6
           

bootstrap 配置會系統會優先加載,加載優先級比 application 高。

  • application.yml
server:
  port: 7002

spring:
  application:
    name: cloud-config-client

eureka:
  client:
    service-url:
      defaultZone: http://user:password@localhost:1111/eureka/

management:
  endpoints:
    web:
      exposure:
        # 開啟指定端點
        # 配置重新整理位址:POST http://127.0.0.1:7002/actuator/refresh
        include: 'refresh'
           

3. ConfigClientApplication.java

@EnableDiscoveryClient
@SpringBootApplication
public class ConfigClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }

}
           

4. 應用

接下來瞅瞅用戶端要怎麼讀到伺服器的配置項呢?

@RefreshScope
@RestController
public class ConfigClientAdmin {

    @Value("${from:default}")
    private String from;

    @Autowired
    private Environment environment;


    @RequestMapping("/from")
    public String from() {
        String fromEnv = environment.getProperty("from");
        return from + "_" + fromEnv;
    }
}
           

如上,我們可以使用 @Value 注解注入配置資訊,或者使用 Environment Bean 來擷取配置項。

需要注意的是,當服務端的配置項更新的時候,用戶端并不會同步獲得更新,需要 Post 方法執行 "/actuator/refresh" 來重新整理配置項。

@RefreshScope 注解使配置的内容動态化,當使用 http://127.0.0.1:7002/actuator/refresh 重新整理配置的時候,會重新整理帶有 @RefreshScope 的 Bean。

四、動态路由

上一篇文章 我們嘗試用 Spring Cloud Zuul 搭建了網關服務,但是我們發現路由資訊都配置在 application.yml 中,這對網關的高可用是個不小的打擊,因為網關作為系統流量的路口,總不能因為改個路由資訊天天重新開機網關吧?是以動态路由的實作,就變得迫不及待了,好在我們現在有了 Spring Cloud Config。

首先,我們将 Spring Cloud Zuul 的路由資訊,配置在 Config Server 的 env.yml 中:

zuul:
  routes:
    client-1:
      # ?:比對任意單個數量字元;*:比對任意多個數量字元;**:比對任意多個數量字元,支援多級目錄
      # 使用 url 的配置沒有線程隔離和斷路器的自我保護功能,不推薦使用
      path: /client-1/**
      url: http://localhost:2222/
      # 敏感頭資訊設定為空,表示不過濾敏感頭資訊,允許敏感頭資訊滲透到下遊伺服器
      sensitiveHeaders: ""
      customSensitiveHeaders: true
    client-2:
      path: /client-2/**
      serviceId: cloud-eureka-client
    # zuul.routes.<serviceid> = <path>
    cloud-eureka-client: /client-3/**
    client-4:
      path: /client-4/**
      # 請求轉發 —— 僅限轉發到本地接口
      url: forward:/local

  # Zuul 将對所有的服務都不自動建立路由規則
  ignored-services: "*"
  # 對某些 url 設定不經過路由選擇
  ignored-patterns: {"/**/world/**","/**/zuul/**"}
  # Spring Cloud Zuul在請求路由時,會過濾掉 HTTP 請求頭(Cookie、Set-Cookie、Authorization)資訊中的一些敏感資訊,
  sensitive-headers: {"Cookie", "Set-Cookie", "Authorization"}
  # 網關在進行路由轉發時為請求設定 Host 頭資訊(保持在路由轉發過程中 host 頭資訊不變)
  add-host-header: true
  # 請求轉發時加上 X-Forwarded-*頭域
  add-proxy-headers: true
  # 是否開啟重試,預設關閉
  retryable: true
  # 通過 /zuul 路徑通路的請求會繞過 dispatcherServlet, 被 Zuu1Servlet 處理,主要用來應對處理大檔案上傳的情況。
  servlet-path: /zuul
  # 禁用某個過濾器 zuul.<SimpleClassName>.<filterTye>.disable=true
  TokenFilter:
    pre:
      disable: true
           

然後,我們将網關服務注冊為 Config Client(配置項與上面類似,就不贅述了),從 Config Server 擷取路由資訊:

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class DynamicRouteApplication {

    public static void main(String[] args) {
        SpringApplication.run(DynamicRouteApplication.class, args);
    }

    /**
     * 重新整理位址:POST http://127.0.0.1:5006/actuator/refresh
     * 路由檢視位址:GET http://127.0.0.1:5006/actuator/routes
     *
     * @return
     */
    @Bean
    @Primary
    //該注解來使 zuul 的配置内容動态化
    @RefreshScope
    @ConfigurationProperties(prefix = "zuul")
    public ZuulProperties zuulProperties() {
        return new ZuulProperties();
    }

}
           

這樣,就把我們的路由資訊交給 Config Server 去管理了~~

五、Spring Cloud Bus

上面的内容我們學習到:當 Config Server 配置變更了以後,Config Client 通過執行 “/actuator/refresh” 來重新整理配置項。一兩個執行個體的話,倒不算什麼。執行個體一旦多了,這将是一個非常繁瑣的工作。怎麼辦呢?就要說到 Spring Cloud Bus。

微服務架構的系統中,我們通常會使用輕量級的消息代理來建構一個共用的消息主題讓系統中所有微服務執行個體都連接配接上來,由于該主題中産生的消息會被所有執行個體監聽和消費,是以我們稱它為消息總線。在總線上的各個執行個體都可以友善地廣播一些需要讓其他連接配接在該主題上的執行個體都知道的消息,例如配置資訊的變更或者其他一些管理操作等。Bus 就是 Spring Cloud 中的消息總線。

那麼 Spring Cloud Bus 怎麼重新整理我們的配置項呢?

  1. 當 Config Server 配置項發生變更的時候,發送一個消息到消息代理的 Topic 下。
  2. Config Client 訂閱 Topic 接收到消息後,主動執行 “/actuator/refresh” 重新整理配置。

Spring Cloud Bus 的內建(基于 RabbitMQ 消息代理)也非常簡單,隻需要我們在 Config Server 中引用:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
 </dependency>
           

然後,在 application.yml 中配置 RabbitMQ ,并暴露 “bus-refresh” 端點:

management:
  endpoints:
    web:
      exposure:
        # 執行 http://127.0.0.1:7001/actuator/bus-refresh,把配置内容修改的消息釋出到服務總線。
        # 用戶端收到訂閱消息,自動執行 /actuator/refresh,重新整理配置
        include: bus-refresh
           

用戶端的配置和服務端類似,也隻要引入 jar 包,配置 RabbitMQ 即可(不用暴露 “bus-refresh” 端點)。

篇幅有限,不細講了,大家可以看我 GitHub 上的内容,包括 RabbitMQ 的內建和 Kafka 的內建。

六、附錄

SpringBoot 版本号:2.1.6.RELEASE

SpringCloud 版本号:Greenwich.RELEASE

示範源代碼 :https://github.com/JMCuixy/spring-cloud-demo

内容參考:《Spring Cloud 微服務實戰》