微服務6:通信之網關


★微服務系列

微服務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路由成功了:

 

  

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都啟動起來,可以看到如下的效果:

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM