SpringCloud之訊息匯流排元件及微服務閘道器

2020-08-08 00:46:41

訊息匯流排元件 Spring Cloud Bus

單個工程更新

有了設定中心,我們就可以吧組態檔放到git上來統一管理了,但是如果組態檔發生了變化,用戶端 又如何更新呢?

1.在組態檔中增加自定義屬性

person:  
  id: 1  
  name: 李四

2.在customer-provider工程中的 Controller 中增加@Value註解除參照屬性

@RestController
public class MyController {

    @Value("${person.name}")
    private String name;

    @RequestMapping("getName")
    public String getName(){
        return name;
    }
}

3.存取url測試
在这里插入图片描述
更新組態檔組態檔

person:  
  id: 1  
  name: 張三

重新存取:
在这里插入图片描述
發現是沒有變化的,也就是說組態檔更新後,如果想讀取到最新的內容還是需要重新啓動工程的。這 樣就造成了很多不便。那麼怎麼才能 纔能不重新啓動更新呢?

spring boot actuator執行器重新整理操作

簡單介紹一下spring boot actuator,是spring boot專案執行的一個監視器服務,啓動專案的 endpoints就是由spring boot actuator輸出的,包含了對spring boot的bean的監視,健康狀況的管 理,可以通過/actuator 檢視各種專案執行的資訊。

1.在customer-provider 工程的pom檔案加入spring boot actuator的參照

		<!--  spring boot actuator的參照-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

2.修改 bootstrap.yml 組態檔,增加如下內容:

#執行器重新整理操作
management:
  endpoints:
    web:
      exposure:
        include: "*"

3.在Controller上增加 @RefreshScope 註解

@RestController
@RefreshScope
public class MyController {

    @Value("${person.name}")
    private String name;

    @RequestMapping("getName")
    public String getName(){
        return name;
    }
}

4.重新啓動 customer-provider 工程

5.修改組態檔後存取下面 下麪url 執行重新整理操作,使用post方法執行。

//檢視actuator可以執行的方法(get形式存取)
http://localhost:9001/actuator
//執行重新整理操作
http://localhost:9001/actuator/refresh

返回變更資訊,就重新整理成功了:
在这里插入图片描述
重新存取即可獲取新的資訊。

這樣就實現了動態重新整理,但是存在一個問題。每次更新檔案都需要子服務手動重新整理來獲取最新值,服務量一旦過大對於維護以及體驗都很糟糕。所以接下來介紹與spring cloud bus搭配實現無需子服務手動 重新整理即可動態獲取伺服器端最新值。

Spring cloud bus介紹

Spring cloud bus通過輕量訊息代理連線各個分佈的節點。這會用在廣播狀態的變化(例如設定變化) 或者其他的訊息指令。Spring bus的一個核心思想是通過分佈式的啓動器對spring boot應用進行擴充套件, 也可以用來建立一個多個應用之間的通訊頻道。目前唯一實現的方式是用AMQP訊息代理作爲通道,同樣特性的設定(有些取決於通道的設定)在更多通道的文件中。

Spring cloud bus被國內很多都翻譯爲訊息匯流排,也挺形象的。大家可以將它理解爲管理和傳播所有分佈式專案中的訊息既可,其實本質是利用了MQ的廣播機制 機製在分佈式的系統中傳播訊息,目前常用的有 Kafka和RabbitMQ。利用bus的機制 機製可以做很多的事情,其中設定中心用戶端重新整理就是典型的應用場景之一,我們用一張圖來描述bus在設定中心使用的機制 機製:
在这里插入图片描述
根據此圖我們可以看出利用Spring Cloud Bus做設定更新的步驟:

  • 提交程式碼觸發post給用戶端A發送bus/refresh

  • 用戶端A接收到請求從Server端更新設定並且發送給Spring Cloud Bus

  • Spring Cloud bus接到訊息並通知給其它用戶端

  • 其它用戶端接收到通知,請求Server端獲取最新設定

  • 全部用戶端均獲取到最新的設定

RabbitMQ

由於Spring cloud bus服務需要MQ中介軟體,所以我們需要先安裝RabbitMQ。rabbitMQ是一個在 AMQP協定標準基礎上完整的,可服用的企業訊息系統。它遵循Mozilla Public License開源協定,採用 Erlang 實現的工業級的訊息佇列(MQ)伺服器,Rabbit MQ 是建立在Erlang OTP平臺上。

  1. 安裝Erlang

    下載地址:網路硬碟鏈接
    提取碼:dj1l
    本文選擇erlang 22.0 Windows 64-bit,安裝時可以改安裝路徑,其他直接預設安裝就可以

    注意

    • erlang不支援中文,所以主機名一定要改成英文,以及C:/Users/使用者名稱 也要改
    • 注意erlang與rabbitmq的版本問題:詳情

設定環境變數,新建 ERLANG_HOME

此電腦–>滑鼠右鍵「屬性」–>高階系統設定–>環境變數–>「新建」系統環境變數
在这里插入图片描述
修改環境變數path,增加Erlang變數 %ERLANG_HOME%\bin 至path ;
在这里插入图片描述
在控制檯輸入erl ,出現版本資訊,就成功了。

  1. 安裝rabbitmq

鏈接:網路硬碟鏈接
提取碼:fwvv

安裝時設定以下安裝目錄即可,一路下一步安裝完畢。然後到rabbitmq安裝目錄下的sbin目錄 下執行如下命令來安裝管理外掛:

.\rabbitmq-plugins.bat enable rabbitmq_management

如果出現下面 下麪情況:
在这里插入图片描述
解決方法

C:\Users\Administrator.erlang.cookie 同步至C:\Windows\System32\config\systemprofile.erlang.cookie

同時刪除:C:\Users\Administrator\AppData\Roaming\RabbitMQ目錄

成功後時展現下面 下麪資訊:
在这里插入图片描述
3.爲了方便以後使用,可以新增RabbitMQ到系統環境變數:

//新增環境變數
名字:RABBIT_HOME  值:你的安裝位置,我的是 D:\RabbitMQ\rabbitmq_server-3.8.3
在path新增 %RABBIT_HOME%\sbin;

4.重新啓動服務

rabbitmq-server

4.存取rabbitmq的管理介面

http://127.0.0.1:15672/

使用者名稱:guest
密碼:guest

至此RabbitMQ安裝完畢。

登錄rabbitmq後建立新的使用者並設定許可權,以便用戶端存取。

用戶端改造

除去設定中心以外的所有服務包括 customer-consumercustomer-provider都是設定中心的客 戶端,我們可以在每個工程中都增加如下內容:

pom檔案

		<!--  訊息匯流排起步依賴      -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--  spring boot actuator的參照-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

bootstrap.yml中增加如下內容:

#訊息元件
spring:
  rabbitmq:
    #指定host,執行在本地可以不寫
    host: localhost
    #預設伺服器端口
    port: 5672
    username: lbb
    password: 123456
#執行器重新整理操作
management:
  endpoints:
    web:
      exposure:
        include: "*"

重新啓動工程

修改git伺服器上的組態檔,存取介面:

//bus重新整理介面 post方式提交
http://localhost:9001/actuator/bus-refresh

更新組態檔後在任意用戶端存取 /actuator/bus-refresh 可以重新整理所有用戶端設定。

改進版本

在上面的流程中,我們已經到達了利用訊息匯流排觸發一個用戶端 bus/refresh ,而重新整理所有用戶端的配 置的目的。但這種方式並不優雅。原因如下:

  • 打破了微服務的職責單一性。
  • 微服務本身是業務模組,它本不應該承擔設定重新整理的職責。
  • 破壞了微服務各節點的對等性。
  • 有一定的侷限性。例如,微服務在遷移時,它的網路地址常常會發生變化,此時如果想要做到自動 重新整理,那就不得不修改WebHook的設定。

因此我們將上面的架構模式稍微改變一下。
在这里插入图片描述
這時Spring Cloud Bus做設定更新步驟如下:

1、提交程式碼觸發post請求給bus/refresh

2、server端接收到請求併發送給Spring Cloud Bus

3、Spring Cloud bus接到訊息並通知給其它用戶端

4、其它用戶端接收到通知,請求Server端獲取最新設定

5、全部用戶端均獲取到最新的設定

升級改造:

設定中心工程的pom檔案中增加如下內容:

		<!--  訊息匯流排起步依賴      -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--  spring boot actuator的參照-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

設定中心的application.yml中增加如下內容:

#訊息元件
spring:
  rabbitmq:
    #指定host,執行在本地可以不寫
    host: localhost
    #預設伺服器端口
    port: 5672
    username: lbb
    password: 123456
#執行器重新整理操作
management:
  endpoints:
    web:
      exposure:
        include: "*"

用戶端工程中可以去掉對spring-boot-starter-actuator的依賴,及相關重新整理設定。

重新啓動config-centercustomer-providercustomer-consumer工程,此時若過設定發生改變,只需在設定中心重新整理,而其他的用戶端沒有此功能,這樣就把職責劃分開了。

//註冊中心對應埠下存取 ,執行重新整理
http://localhost:9911/actuator/bus-refresh

微服務閘道器Zuul

微服務閘道器介紹

前面我們介紹了,Eureka用於服務的註冊於發現,Feign支援服務的呼叫以及均衡負載,Hystrix處理服 務的熔斷防止故障擴散,Spring Cloud Config服務叢集設定中心,似乎一個微服務架構已經完成了。

我們還是少考慮了一個問題,外部的應用如何來存取內部各種各樣的微服務呢?在微服務架構中,後端 服務往往不直接開放給呼叫端,而是通過一個API閘道器根據請求的url,路由到相應的服務。當新增API網 關後,在第三方呼叫端和服務提供方之間就建立了一面牆,這面牆直接與呼叫方通訊進行許可權控制,後 將請求均衡分發給後臺伺服器端。

在微服務架構模式下後端服務的範例數一般是動態的,對於用戶端而言很難發現動態改變的服務範例的 存取地址資訊。因此在基於微服務的專案中爲了簡化前端的呼叫邏輯,通常會引入API Gateway作爲輕 量級閘道器,同時API Gateway中也會實現相關的認證邏輯從而簡化內部服務之間相互呼叫的複雜度。
在这里插入图片描述

Spring Cloud Zuul

在Spring Cloud體系中, Spring Cloud Zuul就是提供負載均衡、反向代理、許可權認證的一個API gateway。pring Cloud Zuul路由是微服務架構的不可或缺的一部分,提供動態路由,監控,彈性,安 全等的邊緣服務。Zuul是Netflix出品的一個基於JVM路由和伺服器端的負載均衡器。

簡單使用

  1. 建立一個新的maven模組 gate-way ,pom檔案中新增依賴:
        <!--    閘道器依賴    -->		
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
  1. 建立application.yml組態檔
spring:
  application:
    name: gate-way
server:
  port: 8888
#設定路由資訊
zuul:
  routes:
    baidu:
      path: /bing/**
      url: https://cn.bing.com/
  1. 編寫啓動類
@SpringBootApplication
@EnableZuulProxy //開啓閘道器代理,啓動類新增 @EnableZuulProxy ,支援閘道器路由。 
public class GateWayRunner {
    public static void main(String[] args) {
        SpringApplication.run(GateWayRunner.class,args);
    }
}
  1. 啓動工程並測試,存取:http://localhost:8888/bing/search?q=121

  2. 設定路由資訊增加本地微服務對映:

spring:
  application:
    name: gate-way
server:
  port: 8888
#設定路由資訊
zuul:
  routes:
    baidu:
      path: /bing/**
      url: https://cn.bing.com/
    customer:
      path: /provider/**
      url: http://localhost:9001/

服務化

通過url對映的方式來實現zull的轉發有侷限性,比如每增加一個服務就需要設定一條內容,另外後端的 服務如果是動態來提供,就不能採用這種方案來設定了。實際上在實現微服務架構時,服務名與服務實 例地址的關係在eureka server中已經存在了,所以只需要將Zuul註冊到eureka server上去發現其他服 務,就可以實現對serviceId的對映。我們還是對 gate-way 進行改造。

  1. 新增依賴,對eureka的支援。
		<!--   eureka起步依賴     -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 修改組態檔

application.yml中增加Eureka的設定資訊。將路由的url改爲 serviceId

spring:
  application:
    name: gate-way
server:
  port: 8888
#設定路由資訊
zuul:
  routes:
    baidu:
      path: /bing/**
      url: https://cn.bing.com/
    customer:
      path: /provider/**
      #url: http://localhost:9001/
      serviceId: CUSTOMER-SERVICE

#eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka
  1. 測試 存取:http://localhost:8888/provider/getName ,如果成功輸出,說明通過zuul成功呼叫了customer-client服務,如果要是多個服務叢集的話還可以實現負載均衡 。

預設路由規則

但是如果後端服務多達十幾個的時候,每一個都這樣設定也挺麻煩的,spring cloud zuul已經幫我們做 了預設設定。預設情況下,Zuul會代理所有註冊到Eureka Server的微服務,並且Zuul的路由規則如下: http://ZUUL_HOST:ZUUL_PORT/微服務在Eureka上的serviceId/** 會被轉發到serviceId對應的微服務。

存取url:

//注意url中的server-id應該是小寫字母。 
http://localhost:8888/customer-service/getName

過濾器

其實Zuul還有更多的應用場景,比如:鑑權、流量轉發、請求統計等等,這些功能都可以使用Zuul來實 現。Filter是Zuul的核心,用來實現對外服務的控制。Filter的生命週期有4個,分別是 「PRE」、 「ROUTING」、「POST」、「ERROR」,整個生命週期可以用下圖來表示。
在这里插入图片描述
Zuul大部分功能都是通過過濾器來實現的,這些過濾器型別對應於請求的典型生命週期。

  • PRE: 這種過濾器在請求被路由之前呼叫。我們可利用這種過濾器實現身份驗證、在叢集中選擇 請求的微服務、記錄偵錯資訊等。
  • ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用 Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來爲響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務發送給用戶端等。
  • ERROR:在其他階段發生錯誤時執行該過濾器。 除了預設的過濾器型別,Zuul還允許我們建立自 定義的過濾器型別。例如,我們可以定製一種STATIC型別的過濾器,直接在Zuul中生成響應,而 不將請求轉發到後端的微服務。

6.1 自定義Filter

實現自定義Filter,需要繼承ZuulFilter的類,並覆蓋其中的4個方法。

public class TokenFilter extends ZuulFilter {
    //定義filter的型別,有pre、route、post、error四種
    @Override
    public String filterType() {
        return "pre"; 
    }
	//定義filter的順序,數位越小表示順序越高,越先執行
    @Override
    public int filterOrder() {
        return 10; 
    }
	//表示是否需要執行該filter,true表示執行,false表示不執行 
    @Override
    public boolean shouldFilter() {
        return true; 
    }
	//filter需要執行的具體操作 
    @Override
    public Object run() throws ZuulException {
        return null; 
    }
}

6.2 自定義Filter範例

我們假設有這樣一個場景,因爲服務閘道器應對的是外部的所有請求,爲了避免產生安全隱患,我們需要 對請求做一定的限制,比如請求中含有Token便讓請求繼續往下走,如果請求不帶Token就直接返回並 給出提示。

首先自定義一個Filter,在run()方法中驗證參數是否含有Token。

public class TokenFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre"; //定義filter的型別,有pre、route、post、error四種
    }

    @Override
    public int filterOrder() {
        return 0; //定義filter的順序,數位越小表示順序越高,越先執行
    }

    @Override
    public boolean shouldFilter() {
        return true; //表示是否需要執行該filter,true表示執行,false表示不執行
    }

    @Override
    public Object run() throws ZuulException {
        //獲取request看是否含token
        //請求上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //獲取請求物件
        HttpServletRequest request = ctx.getRequest();
        // 獲取請求的參數
        String token = request.getParameter("token");
        //token無效
        if (token == null || token.length() < 1){
            //不對其進行路由轉發
            ctx.setSendZuulResponse(false);
            //狀態碼
            ctx.setResponseStatusCode(400);
            //返回錯誤資訊
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess", false);
            return null;
        }else {//token有效
            //不對其進行路由轉發
            ctx.setSendZuulResponse(true);
            //狀態碼
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        }
    }
}

TokenFilter加入到請求攔截佇列,修改啓動類:

@SpringBootApplication
@EnableZuulProxy //開啓閘道器代理
public class GateWayRunner {
    public static void main(String[] args) {
        SpringApplication.run(GateWayRunner.class,args);
    }
    @Bean
    public TokenFilter getTokenFilter(){
        return new TokenFilter();
    }
}

這樣就將我們自定義好的Filter加入到了請求攔截中。

測試

存取地址: http://localhost:8888/customer-service/getName ,返回:token is empty ,請求被攔截返回。

存取地址: http://localhost:8888/customer-service/getName?token=xx ,返回:json數據,說明請求正常響應。

通過上面這例子我們可以看出,我們可以使用「PRE」型別的Filter做很多的驗證工作,在實際使用中我們 可以結合shiro、oauth2.0等技術去做鑑權、驗證。