★微服務系列
1 概述
回顧下前面幾篇關於微服務的介紹,我們可以瞭解到從當單體系統到微服務,再到服務網格的演進過程。那單體系統和微服務相比,有哪些區別呢,下面是對功能性的對比?
單體系統 | 微服務系統 |
程式、資料、配置集中管理 | 按照功能拆分、微服務化、鬆耦合 |
開發效率低下 | 分模組快速迭代 |
釋出全量,啟動慢 | 平滑釋出,快速啟動 |
可靠性差 | 熔斷、限流、降級,超時重試,異常離群 |
服務內直接呼叫 | 輕量級通訊 |
技術單一 | 跨語言 |
微服務有諸多的有利條件,但是如果微服務的粒度比較細(按照業務功能拆分),則他們之間服務呼叫就會比較複雜,鏈路會比較長。
因此,在微服務架構體系下,服務間的通訊就顯得非常重要。
你可能需要關注很多問題,包括不同的技術棧不同的開發語言之間的上下游互動,服務之間的註冊與發現,請求認證,接入授權。
下游對上游進行呼叫的時候,上游怎麼做負載均衡、故障注入、超時重複、熔斷、降級、限流、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都啟動起來,可以看到如下的效果: