天天看點

萬字長文詳解微服務網關(下)

作者:馬士兵教育

書接上回!

3.6 網關高可用

高可用HA(High Availability)是分布式系統架構設計中必須考慮的因素之一,它通常是指,通過設計減少系統不能提供服務的時間。我們都知道,單點是系統高可用的大敵,單點往往是系統高可用最大的風險和敵人,應該盡量在系統設計的過程中避免單點。方法論上,高可用保證的原則是“叢集化”,或者叫“備援”:隻有一個單點,挂了服務會受影響;如果有備援備份,挂了還有其他backup能夠頂上。

萬字長文詳解微服務網關(下)

我們實際使用 Spring Cloud Gateway 的方式如上圖,不同的用戶端使用不同的負載将請求分發到後端的 Gateway,Gateway 再通過HTTP調用後端服務,最後對外輸出。是以為了保證 Gateway 的高可用性,前端可以同時啟動多個 Gateway 執行個體進行負載,在 Gateway 的前端使用 Nginx 或者 F5 進行負載轉發以達到高可用性。

(1)準備多個GateWay工程

修改 shop_gateway_server 的application.yml。添加如下配置

spring:
application:
name: api-gateway #指定服務名
cloud:
gateway:
routes:
- id: product-service
uri: lb://shop-service-product
predicates:
- Path=/product-service/**
filters:
- RewritePath=/product-service/(?<segment>.*), /$\{segment}
eureka:
client:
serviceUrl:
defaultZone: http://eureka1:8761/eureka/
registry-fetch-interval-seconds: 5 # 擷取服務清單的周期:5s
instance:
preferIpAddress: true
ip-address: 127.0.0.1
---
spring:
profiles: gateway01
server:
port: 8080 #服務端口
---
spring:
profiles: gateway02
server:
port: 8081 #服務端口           

通過不同的profifiles配置啟動兩個網關服務,請求端口分别為8080和8081。浏覽器驗證發現效果是一緻的。

(2)配置ngnix

找到ngnix添加負載均衡配置

#配置多台伺服器(這裡隻在一台伺服器上的不同端口)

upstream gateway {
server 127.0.0.1:8081;
server 127.0.0.1:8080;
}
#請求轉向mysvr 定義的伺服器清單
location / {
proxy_pass http://gateway;
}           

在浏覽器上通過通路http://localhost/order-service/order/buy/1請求的效果和之前是一樣的。這次關閉一台網關伺服器,還是可以支援部分請求的通路。

3.7 執行流程分析

萬字長文詳解微服務網關(下)

Spring Cloud Gateway 核心處理流程如上圖所示,Gateway的用戶端向 Spring Cloud Gateway 發送請求,請求首先被 HttpWebHandlerAdapter 進行提取組裝成網關上下文,然後網關的上下文會傳遞到 DispatcherHandler 。 DispatcherHandler 是所有請求的分發處理器, DispatcherHandler 主要負責分發請求對應的處理器。比如請求分發到對應的 RoutePredicateHandlerMapping (路由斷言處理映射器)。路由斷言處理映射器主要作用用于路由查找,以及找到路由後傳回對應的FilterWebHandler 。 FilterWebHandler 主要負責組裝Filter鍊并調用Filter執行一系列的Filter處理,然後再把請求轉到後端對應的代理服務處理,處理完畢之後将Response傳回到Gateway用戶端。

4 微服務的鍊路追蹤概述

4.1 微服務架構下的問題

在大型系統的微服務化建構中,一個系統會被拆分成許多子產品。這些子產品負責不同的功能,組合成系統,最終可以提供豐富的功能。在這種架構中,一次請求往往需要涉及到多個服務。網際網路應用建構在不同的軟體子產品集上,這些軟體子產品,有可能是由不同的團隊開發、可能使用不同的程式設計語言來實作、有可能布在了幾千台伺服器,橫跨多個不同的資料中心,也就意味着這種架構形式也會存在一些問題:

  • 如何快速發現問題?
  • 如何判斷故障影響範圍?
  • 如何梳理服務依賴以及依賴的合理性?
  • 如何分析鍊路性能問題以及實時容量規劃?

分布式鍊路追蹤(Distributed Tracing),就是将一次分布式請求還原成調用鍊路,進行日志記錄,性能監控并将 一次分布式請求的調用情況集中展示。比如各個服務節點上的耗時、請求具體到達哪台機器上、每個服務節點的請求狀态等等。

目前業界比較流行的鍊路追蹤系統如:Twitter的Zipkin,阿裡的鷹眼,美團的Mtrace,大衆點評的cat等,大部分都是基于google發表的Dapper。Dapper闡述了分布式系統,特别是微服務架構中鍊路追蹤的概念、資料表示、埋點、傳遞、收集、存儲與展示等技術細節。

4.2 Sleuth概述

4.2.1 簡介

Spring Cloud Sleuth 主要功能就是在分布式系統中提供追蹤解決方案,并且相容支援了 zipkin,你隻需要在pom檔案中引入相應的依賴即可。

4.2.2 相關概念

Spring Cloud Sleuth 為Spring Cloud提供了分布式根據的解決方案。它大量借用了Google Dapper的設計。先來了解一下Sleuth中的術語和相關概念。

Spring Cloud Sleuth采用的是Google的開源項目Dapper的專業術語。

  • Span:基本工作單元,例如,在一個建立的span中發送一個RPC等同于發送一個回應請求給RPC,span通過一個64位ID唯一辨別,trace以另一個64位ID表示,span還有其他資料資訊,比如摘要、時間戳事件、關鍵值注釋(tags)、span的ID、以及進度ID(通常是IP位址)
  • span在不斷的啟動和停止,同時記錄了時間資訊,當你建立了一個span,你必須在未來的某個時刻停止它。
  • Trace:一系列spans組成的一個樹狀結構,例如,如果你正在跑一個分布式大資料工程,你可能需要建立一個trace。
  • Annotation:用來及時記錄一個事件的存在,一些核心annotations用來定義一個請求的開始和結束
  1. cs - Client Sent -用戶端發起一個請求,這個annotion描述了這個span的開始sr - Server Received -服務端獲得請求并準備開始處理它,如果将其sr減去cs時間戳便可得到網絡延遲
  2. ss - Server Sent -注解表明請求處理的完成(當請求傳回用戶端),如果ss減去sr時間戳便可得到服務端需要的處理請求時間
  3. cr - Client Received -表明span的結束,用戶端成功接收到服務端的回複,如果cr減去cs時間戳便可得到用戶端從服務端擷取回複的所有所需時間
萬字長文詳解微服務網關(下)

4.3 鍊路追蹤Sleuth入門

接下來通過之前的項目案例整合Sleuth,完成入門案例的編寫

(1)配置依賴

修改微服務工程引入Sleuth依賴

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

(2)修改配置檔案

修改application.yml添加日志級别

logging:
level:
root: INFO
org.springframework.web.servlet.DispatcherServlet: DEBUG
org.springframework.cloud.sleuth: DEBUG           

每個微服務都需要添加如上的配置。啟動微服務,調用之後,我們可以在控制台觀察到sleuth的日志輸出。

萬字長文詳解微服務網關(下)

其中 ff8ff8b803a3b558 是TraceId,後面跟着的是SpanId,依次調用有一個全局的TraceId,将調用鍊路串起來。仔細分析每個微服務的日志,不難看出請求的具體過程。

檢視日志檔案并不是一個很好的方法,當微服務越來越多日志檔案也會越來越多,通過Zipkin可以将日志聚合,并進行可視化展示和全文檢索。

4.4 Zipkin的概述

Zipkin 是 Twitter 的一個開源項目,它基于 Google Dapper 實作,它緻力于收集服務的定時資料,以解決微服務架構中的延遲問題,包括資料的收集、存儲、查找和展現。 我們可以使用它來收集各個伺服器上請求鍊路的跟蹤資料,并通過它提供的 REST API 接口來輔助我們查詢跟蹤資料以實作對分布式系統的監控程式,進而及時地發現系統中出現的延遲升高問題并找出系統性能瓶頸的根源。除了面向開發的 API 接口之外,它也提供了友善的 UI 元件來幫助我們直覺的搜尋跟蹤資訊和分析請求鍊路明細,比如:可以查詢某段時間内各使用者請求的處理時間等。 Zipkin 提供了可插拔資料存儲方式:InMemory、MySql、Cassandra 以及 Elasticsearch。

萬字長文詳解微服務網關(下)

上圖展示了 Zipkin 的基礎架構,它主要由 4 個核心元件構成:

  • Collector:收集器元件,它主要用于處理從外部系統發送過來的跟蹤資訊,将這些資訊轉換為Zipkin 内部處理的 Span 格式,以支援後續的存儲、分析、展示等功能。
  • Storage:存儲元件,它主要對處理收集器接收到的跟蹤資訊,預設會将這些資訊存儲在記憶體中,我們也可以修改此存儲政策,通過使用其他存儲元件将跟蹤資訊存儲到資料庫中。
  • RESTful API:API 元件,它主要用來提供外部通路接口。比如給用戶端展示跟蹤資訊,或是外接系統通路以實作監控等。
  • Web UI:UI 元件,基于 API 元件實作的上層應用。通過 UI 元件使用者可以友善而有直覺地查詢和分析跟蹤資訊。

Zipkin 分為兩端,一個是 Zipkin 服務端,一個是 Zipkin 用戶端,用戶端也就是微服務的應用。

用戶端會配置服務端的 URL 位址,一旦發生服務間的調用的時候,會被配置在微服務裡面的 Sleuth 的監聽器監聽,并生成相應的 Trace 和 Span 資訊發送給服務端。

發送的方式主要有兩種,一種是 HTTP 封包的方式,還有一種是消息總線的方式如 RabbitMQ。

不論哪種方式,我們都需要:

  • 一個 Eureka 服務注冊中心,這裡我們就用之前的 eureka 項目來當注冊中心。
  • 一個 Zipkin 服務端。
  • 多個微服務,這些微服務中配置Zipkin 用戶端。

4.5 Zipkin Server的部署和配置

(1) Zipkin Server下載下傳

從spring boot 2.0開始,官方就不再支援使用自建Zipkin Server的方式進行服務鍊路追蹤,而是直接提供了編譯好的 jar 包來給我們使用。可以從官方網站下載下傳先下載下傳Zipkin的web UI,我們這裡下載下傳的是zipkin-server-2.12.9-exec.jar

(2)啟動

在指令行輸入 java -jar zipkin-server-2.12.9-exec.jar 啟動 Zipkin Server

萬字長文詳解微服務網關(下)
  • 預設Zipkin Server的請求端口為 9411
  • Zipkin Server的啟動參數可以通過官方提供的yml配置檔案查找
  • 在浏覽器輸入 http://127.0.0.1:9411即可進入到Zipkin Server的管理背景

4.6 用戶端Zipkin+Sleuth整合

通過檢視日志分析微服務的調用鍊路并不是一個很直覺的方案,結合zipkin可以很直覺地顯示微服務之間的調用關系。

(1)用戶端添加依賴

用戶端指的是需要被追蹤的微服務

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

(2)修改用戶端配置檔案

zipkin:
base-url: http://127.0.0.1:9411/ #zipkin server的請求位址
sender:
type: web #請求方式,預設以http的方式向zipkin server發送追蹤資料
sleuth:
sampler:
probability: 1.0 #采樣的百分比           

指定了zipkin server的位址,下面制定需采樣的百分比,預設為0.1,即10%,這裡配置1,是記錄全部的sleuth資訊,是為了收集到更多的資料(僅供測試用)。在分布式系統中,過于頻繁的采樣會影響系統性能,是以這裡配置需要采用一個合适的值。

(3)測試

以此啟動每個微服務,啟動Zipkin Service。通過浏覽器發送一次微服務請求。打開 Zipkin Service控制台,我們可以根據條件追蹤每次請求調用過程

萬字長文詳解微服務網關(下)

單擊該trace可以看到請求的細節

萬字長文詳解微服務網關(下)

4.7 基于消息中間件收集資料

在預設情況下,Zipkin用戶端和Server之間是使用HTTP請求的方式進行通信(即同步的請求方式),在網絡波動,Server端異常等情況下可能存在資訊收集不及時的問題。Zipkin支援與rabbitMQ整合完成異步消息傳輸。

加了MQ之後,通信過程如下圖所示:

萬字長文詳解微服務網關(下)

4.7.1 RabbitMQ的安裝與啟動

4.7.2 服務端啟動

java -jar zipkin-server-2.12.9-exec.jar --RABBIT_ADDRESSES=127.0.0.1:5672

  • RABBIT_ADDRESSES :指定RabbitMQ位址
  • RABBIT_USER:使用者名(預設guest)
  • RABBIT_PASSWORD :密碼(預設guest)

啟動Zipkin Server之後,我們打開RabbitMQ的控制台可以看到多了一個Queue

萬字長文詳解微服務網關(下)

其中 zipkin 就是為我們自動建立的Queue隊列

7.7.3 用戶端配置

(1)配置依賴

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
           

導入 spring-rabbit 依賴,是Spring提供的對rabbit的封裝,用戶端會根據配置自動的生産消息并發送到目标隊列中

(2)配置消息中間件rabbit mq位址等資訊

zipkin:
#base-url: http://127.0.0.1:9411/ #zipkin server的請求位址
 sender:
 type: rabbit
#type: web #請求方式,預設以http的方式向zipkin server發送追蹤資料
sleuth:
sampler:
probability: 1.0 #采樣的百分比
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
listener: # 這裡配置了重試政策
direct:
retry:
enabled: true
simple:
retry:
enabled: true
           
  • 修改消息的投遞方式,改為rabbit即可。
  • 添加rabbitmq的相關配置

(3)測試

關閉Zipkin Server,并随意請求連接配接。打開rabbitmq管理背景可以看到,消息已經推送到rabbitmq。當Zipkin Server啟動時,會自動的從rabbitmq擷取消息并消費,展示追蹤資料

可以看到如下效果:

  • 請求的耗時時間不會出現突然耗時特長的情況
  • 當ZipkinServer不可用時(比如關閉、網絡不通等),追蹤資訊不會丢失,因為這些資訊會儲存在Rabbitmq伺服器上,直到Zipkin伺服器可用時,再從Rabbitmq中取出這段時間的資訊

4.8 存儲跟蹤資料

Zipkin Server預設時間追蹤資料資訊儲存到記憶體,這種方式不适合生産環境。因為一旦Service關閉重新開機或者服務崩潰,就會導緻曆史資料消失。Zipkin支援将追蹤資料持久化到mysql資料庫或者存儲到elasticsearch中。這裡已mysql為例。

4.8.1 準備資料庫

可以從官網找到Zipkin Server持久mysql的資料庫腳本。

CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means
the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs
query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration
and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE
utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`)
COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`)
COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for
getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and
getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces
ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
 `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means
the trace uses 128 bit traceIds instead of 64 bit',
 `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
 `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
 `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or
Annotation.value if type == -1',
 `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller
than 64KB',
 `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if
Annotation',
 `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp
or zipkin_spans.timestamp',
 `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is
null',
 `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint
is null, or no IPv6 address',
 `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is
null',
 `endpoint_service_name` VARCHAR(255) COMMENT 'Null when
Binary/Annotation.endpoint is null'
 ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE
utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`,
`span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`,
`span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`)
COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT
'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`)
COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
 `day` DATE NOT NULL,
 `parent` VARCHAR(255) NOT NULL,
 `child` VARCHAR(255) NOT NULL,
 `call_count` BIGINT
 ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE
utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);           

4.8.2 配置啟動服務端

java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --
MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root -
-MYSQL_PASS=111111           
  • STORAGE_TYPE : 存儲類型
  • MYSQL_HOST: mysql主機位址
  • MYSQL_TCP_PORT:mysql端口
  • MYSQL_DB: mysql資料庫名稱
  • MYSQL_USER:mysql使用者名
  • MYSQL_PASS :mysql密碼

配置好服務端之後,可以在浏覽器請求幾次。回到資料庫檢視會發現資料已經持久化到mysql中

萬字長文詳解微服務網關(下)

完結!

繼續閱讀