微服務6:通訊之閘道器

翁智華發表於2022-02-18

★微服務系列

微服務1:微服務及其演進史

微服務2:微服務全景架構 

微服務3:微服務拆分策略

微服務4:服務註冊與發現

微服務5:服務註冊與發現(實踐篇)

微服務6:通訊之閘道器

1 概述

回顧下前面幾篇關於微服務的介紹,我們可以瞭解到從當單體系統到微服務,再到服務網格的演進過程。那單體系統和微服務相比,有哪些區別呢,下面是對功能性的對比?

單體系統 微服務系統
程式、資料、配置集中管理 按照功能拆分、微服務化、鬆耦合
開發效率低下 分模組快速迭代
釋出全量,啟動慢 平滑釋出,快速啟動
可靠性差 熔斷、限流、降級,超時重試,異常離群
服務內直接呼叫 輕量級通訊
技術單一 跨語言

微服務有諸多的有利條件,但是如果微服務的粒度比較細(按照業務功能拆分),則他們之間服務呼叫就會比較複雜,鏈路會比較長。 

 

比如上圖中,我們按照職能將服務進行了拆分,這時候從不同的客戶端(如Web、App、3rd)訪問,就有可能訪問不同的服務。而服務與服務之間又有上下游的協作,呼叫就變得錯綜複雜。

因此,在微服務架構體系下,服務間的通訊就顯得非常重要。

你可能需要關注很多問題,包括不同的技術棧不同的開發語言之間的上下游互動,服務之間的註冊與發現,請求認證,接入授權。

下游對上游進行呼叫的時候,上游怎麼做負載均衡、故障注入、超時重複、熔斷、降級、限流、ABTesting等,端到端之間如何實現監控和trace,這些都是微服務體系下需要去思考的問題。

要解決上面這些問題,微服務通訊可以從三個方面進行討論

細化服務顆粒:按照功能拆分、微服務化、鬆耦合

分模組快速迭代:可以將應用程式拆分為核心和非核心模組。非核心模組出現問題的時候,核心模組不會受到影響。

參考這篇《微服務3:微服務拆分策略

流量管理:金絲雀釋出,ABTesting

實現服務的高可用治理:熔斷、限流、降級,超時重試,異常離群

輕量級通訊,使用 RESTful API 或者 RPC 進行介面訪問

跨語言,語言有特定的應用場景,比如go和Java、c++適合不同的業務方向,開發語言不同,但是遵循同一套標準,

使用輕量級的API進行通訊,實現服務語言上的解耦。

2 服務之間的通訊方式 

而微服務的通訊,是在服務之間增加一個間接的中間層來完成服務間的通訊過程。目前微服務的通訊方式有以下三種:

1、基於閘道器的通訊

2、基於RPC的通訊

3、基於ServiceMesh的資料面(SideCar)的通訊

接下來逐一介紹這三種通訊方式的具體實現,本文先介紹基於閘道器的通訊方式。 

2.1 基於閘道器的通訊

 我們先看看,在沒有閘道器的情況下,服務是怎麼通訊的?

 

如上圖有3個客戶端,在呼叫4個服務的介面。這種直連呼叫的方式有很多問題:客戶端需要儲存所有服務的地址,同時也需要實現一些系統級的容錯策略。

比如負載均衡、超時重試、服務熔斷等,非常複雜,並且難以維護。因為是在各客戶端儲存的服務地址,一旦某個服務端出現問題或者發生遷移,所有的客戶端都需要修改並且升級。

另外如果再增加一個E svc,所有的客戶端也需要升級。而且在某些場景下存在跨域請求的問題,每個服務都需要實現獨立的身份和許可權認證等等。

這些問題導致 服務間的通訊過於複雜,對於開發和維護都不優化。

如果我們在客戶端和服務端增加一層閘道器,所有請求都經過閘道器轉發到對應的下游服務,客戶端只需要儲存閘道器的地址並且只和閘道器進行互動,這樣就大大簡化了客戶端的開發。

 

如果需要訪問使用者服務,只需要構造右邊這個請求發給閘道器,然後由閘道器將請求轉發給對應的下游服務。

可以將閘道器簡單理解為:路由轉發+治理策略,治理策略是指和業務無關的一些通用策略,包括:負載均衡,安全認證,身份驗證,系統容錯等等

閘道器作為一個 API 架構層,用來保護、增強和控制對服務的訪問。 

2.1.1 閘道器的主要功能

請求接入

1、為各種應用提供統一的服務接入

2、管理所有的接入請求:提供流量分流、代理轉發、延遲、故障注入等能力

安全防護

使用者認證、許可權校驗、黑白名單、請求過濾、防web攻擊

治理策略

負載均衡、介面限流、超時重試、服務熔斷、灰度釋出、協議適配、流量監控、日誌統計等

統一管理

1、提供配置管理工具

2、對所有服務進行統一管理

3、對中介策略進行統一管理

2.1.2 閘道器使用場景 

藍綠部署
 

我們前面看到,在單體應用中,部署是一件比較麻煩的事情,每次的改動,都需要把整個應用程式都發布啟動一次。而且系統規模越大,部署過程越複雜,時間越長。

而在微服務架構中,模組部署起來相對更快,更容易。你可以在短時間內對於同一個模組做多次部署,閘道器可以幫你實現藍綠部署。

如圖所示之前的使用者服務版本是V1.0,然後部署V1.1版本,在閘道器上只需要做一個轉發配置的修改,就可以迅速的將所有流量都流到新版本。 

灰度釋出

  

類似金絲雀的理念,你對一次性升級版本感到擔憂,可以先配置5%的流量達到新版本,讓部分人試用一下,等線上觀察一段時間後,可以逐步增加對新版本的流量百分比,最終實現百分之百切流。

負載均衡

 

此能力需要依賴服務註冊和服務發現。 

服務熔斷

閘道器還可以實現斷路器的功能;如果某個下游忽然返回了大量錯誤,原因有可能是服務掛了或者網路問題或者伺服器負載太高,如果此時繼續給這個問題服務轉發流量就可能會產生級聯故障。

出問題的服務有可能產生雪崩,雪崩會沿著呼叫鏈向上傳遞,導致整個服務鏈都崩潰。

斷路器可以停止向問題模組轉發流量,在業務層面可以給使用者返回一個服務降級之後的頁面,開發人員就有相對充分的時間來定位和解決問題。 

2.1.3 開源閘道器

目前常見的開源閘道器按照語言大概分為上圖的五種,如果按照使用數量和成熟度來劃分的話,主流有4個,分別是 OpenResty、Kong、Zull和Spring Cloud。

其中 Zull 和Spring Cloud 是用java 實現,前兩種是用Nginx+Lua實現的,其中,OpenResty 是一個基於Nginx+Lua實現的一個高效能web 平臺,它整合了大量的第三方模組和lua庫,用於方便的搭建高效能擴充套件性強的web 應用服務或者閘道器,

Kong 是一個基於OpenResty實現的一個高效能可擴充套件的API閘道器。從效能上來說:Kong的效能是最好的,其次分別是OpenResty、Spring Cloud 和Zuul。 

2.1.4 Nginx介紹

大家熟悉的Nginx是用C語言實現的一個開源、跨平臺、高效能的HTTP和反向代理Web伺服器,具有高度模組化、擴充套件性強、輕量級、資源消耗少、高併發、高效能等特點。

Nginx程式模型

 

 

Nginx是一個多程式模型,包含一個master 程式和多個worker 程式。

master 程式主要負責接收外部訊號,向各個worker 程式傳送訊號,監控worker 程式的狀態。當worker 程式異常退出後,服務不會中斷的。master 程式會迅速拉起新的worker 程式來工作。基本的網路事件比如讀寫,都是在worker 程式來實現。

多個worker 程式是相互獨立的,他們共同競爭來自客戶端的請求,一個請求只能在一個worker 中進行處理。 

Nginx網路模型

首先,master 程式會listen socket,然後再fork出多個worker 程式,每個worker 程式都可以去accept 這個socket。
Nginx 提供了一把共享accept mutex鎖來保證只有一個worker 程式可以把這個請求accept 成功。當這個worker 程式accept 連線成功之後,就可以進行請求的解析讀取。

大概資料流:master 先 fork 多個worker 程式,然後當有client 來的時候,client 會連線到一個worker 程式裡面,傳送訊息request。worker 程式再去讀取、解析並處理,最終把response 返回給客戶端。 

那麼大家可以想一下這個問題,Nginx 採用了一個多worker 的方式來處理請求,每個worker 裡面其實是隻有一個主執行緒,那麼它是怎麼實現高併發的呢?

答案是採用非同步非阻塞的方式;什麼是非同步非阻塞?

舉個例子,當worker 收到一個來自client request 時,就會有一個worker 程式去處理它。但這個worker 程式並不是全程處理,worker 會處理到請求可能會發生阻塞的地方。比如它向後端伺服器轉發的這個request,並等待請求的返回,

worker 程式不會同步的等待,而是註冊一個事件,如果下游返回了,再繼續處理這個事件。如果有新的請求再進來,它就可以很快按照這種方式再進行處理。這其實就是非阻塞和IO多路複用。

一旦服務端返回了,就會觸發worker 剛才註冊的回撥事件,worker 才會繼續接手這個request。  

2.2 Zuul實戰

剛才前面已經說過,閘道器是客戶端和伺服器之間的中間層,作為系統唯一對外的入口,能夠處理流量治理、安全訪問等工作。

功能型別 功能說明
統一接入 智慧路由
  AB測試、灰度測試
  負載均衡、容災處理
  日誌埋點(類似Nignx日誌)
流量監控 限流處理
  服務降級
安全防護 鑑權處理
  監控 
   機器網路隔離

 

業內主要的閘道器有如下幾種:
1、
zuul:是Netflix開源的微服務閘道器,可以和Eureka,Ribbon,Hystrix等元件配合使用,Zuul提供了動態路由、監控、彈性負載和安全功能。

2、kong: 由Mashape公司開源的,基於OpenResty(Nginx+Lua)的 API gateway。

3、Nginx + Lua:高效能的HTTP和反向代理伺服器,Lua作為指令碼語言,為Nginx提供執行程式,可以高併發、非阻塞的處理各種請求

下面我們以Zuul為案例,來測試下閘道器的使用。

2.2.1 配置路由規則

新建一個spring-clouid專案,匯入Maven 依賴:

 1  <!-- eureka client -->
 2         <dependency>
 3             <groupId>org.springframework.cloud</groupId>
 4             <artifactId>spring-cloud-netflix-eureka-server</artifactId>
 5         </dependency>
 6         <!-- zuul閘道器 -->
 7         <dependency>
 8             <groupId>org.springframework.cloud</groupId>
 9             <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
10             <version>2.2.10.RELEASE</version>
11         </dependency> 

配置yaml檔案:

 1 server:
 2   port: 1002
 3 spring:
 4   application:
 5     name: zuul-proxy # 服務的名稱
 6 eureka:
 7   instance:
 8     hostname: localhost
 9   client:
10     service-url:  # 這邊就保證了註冊到 eureka-service 這個註冊中心去
11       defaultZone: http://localhost:1000/eureka/
12 
13 #自定義路由對映
14 zuul:
15   routes: #路由規則
16     key-v1:  #自定義key
17       path: /proxy/**   # 匹配路徑,/proxy/ 會路由到 zuul-proxy服務
18       serviceId: zuul-proxy
19       url: http://${eureka.instance.hostname}:${server.port}/zuulservice/api/v1.0/  # 只要路徑匹配,就轉到這個服務對應路徑下 

Application中啟動閘道器代理:

1 @SpringBootApplication
2 @EnableZuulProxy // 開啟閘道器代理
3 public class ZuulGatewayApplication {
4     public static void main(String[] args) {
5         SpringApplication.run(ZuulGatewayApplication.class, args);
6         System.out.println("start zuulgateway!");
7     }
8

介面實現:

 1 /**
 2  * @author brand
 3  * @Description:
 4  * @Copyright: Copyright (c) 2021
 5  * @Company: Helenlyn, Inc. All Rights Reserved.
 6  * @date 2021/12/5 12:30 下午
 7  * @Update Time:
 8  * @Updater:
 9  * @Update Comments:
10  */
11 @Controller
12 @RequestMapping("/zuulservice/api/v1.0")
13 public class ZuulServiceController {
14 
15     /**
16      * 獲取註冊服務資訊
17      */
18     @RequestMapping(value = "/serviceinfo", method = {RequestMethod.GET})
19     @ResponseBody
20     public String getServiceInfo() {
21         return  "serviceinfo:v1.0,instance 1";    

 

檢視效果,proxy路由成功了:

微服務6:通訊之閘道器

 

  

2.2.2 測試負載均衡

我們在新增一個埠為1003的服務,跟埠為1002做zuul-proxy服務做負載均衡,這時候先修改 zuul-proxy 的yaml配置。

 

 1 # 自定義路由對映
 2 zuul:
 3   routes: # 路由規則
 4     key-v1:  # 自定義key
 5       path: /proxy/**   # 匹配路徑,/proxy/ 會路由到 zuul-proxy服務
 6       serviceId: zuul-proxy
 7 ribbon:
 8   eureka:
 9     enabled: true # 允許Ribbon使用Eureka
10 zuul-proxy:
11   ribbon:
12     listOfServers: localhost:1002,localhost:1003 # 這邊需要建立兩個服務 1002,1003,在這兩個服務間做負載均衡 

建立一個module,命名zuul-client,增加註冊依賴 

1         <!-- eureka client -->
2         <dependency>
3             <groupId>org.springframework.cloud</groupId>
4             <artifactId>spring-cloud-netflix-eureka-server</artifactId>
5         </dependency> 

配置zuul-client的yaml檔案: 

1 server:
2   port: 1003 # 這邊注意,埠為1003,跟上面對應起來了
3 spring:
4   application:
5     name: zuul-client # 服務的名稱
6 eureka:
7   client:
8     service-url:  # 這邊就保證了註冊到 eureka-service 這個註冊中心去
9       defaultZone: http://localhost:1000/eureka/ 

補充一個介面,注意返回的資訊不一樣: 

 1 /**
 2  * @author brand
 3  * @Description:
 4  * @Copyright: Copyright (c) 2021
 5  * @Company:Helenlyn, Inc. All Rights Reserved.
 6  * @date 2021/12/5 3:52 下午
 7  * @Update Time:
 8  * @Updater:
 9  * @Update Comments:
10  */
11 @Controller
12 @RequestMapping("/zuulservice/api/v1.0")
13 public class ZuulServiceController {
14     /**
15      * 獲取註冊服務資訊
16      */
17     @RequestMapping(value = "/serviceinfo", method = {RequestMethod.GET})
18     @ResponseBody
19     public String getServiceInfo() {
20         return  "serviceinfo:v1.0,instance 2";
21     }
22

閘道器 zuul-proxy 和 服務zuul-client都啟動起來,可以看到如下的效果:

  

 

 

相關文章