自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我終于搞懂了微服務(wù),太不容易了...

開發(fā) 架構(gòu) 開發(fā)工具
微服務(wù)是什么?拋去教條性質(zhì)的解釋,從巨石應(yīng)用到微服務(wù)應(yīng)用,耦合度是其中最大的變化。

 微服務(wù)是什么?拋去教條性質(zhì)的解釋,從巨石應(yīng)用到微服務(wù)應(yīng)用,耦合度是其中最大的變化。

[[329434]]

 

圖片來自 Pexels

或是將多個(gè)模塊中重復(fù)的部分進(jìn)行拆分,或是純粹為了拆分膨脹的單體應(yīng)用,這些拆分出來的部分獨(dú)立成一個(gè)服務(wù)單獨(dú)部署與維護(hù),便是微服務(wù)了。

拆分后自然而然會(huì)催生出一些必要的需求:

  • 從本地方法調(diào)用的關(guān)系衍變成遠(yuǎn)程過程調(diào)用的關(guān)系,那么可靠的通信功能是首要的。
  • 隨著拆分工作的推進(jìn),資源調(diào)度關(guān)系會(huì)變得錯(cuò)綜復(fù)雜,這時(shí)候需要完善的服務(wù)治理。
  • 調(diào)用關(guān)系網(wǎng)的整體復(fù)雜化還會(huì)給我們帶來更大的風(fēng)險(xiǎn),即鏈?zhǔn)椒磻?yīng)導(dǎo)致服務(wù)雪崩的可能性,所以如何保障服務(wù)穩(wěn)定性也是微服務(wù)架構(gòu)中需要考慮的。
  • 這點(diǎn)就不是內(nèi)需而算是自我演進(jìn)了,服務(wù)化后,如果能結(jié)合容器化、Devops 技術(shù)實(shí)現(xiàn)服務(wù)運(yùn)維一體化,將大大降低微服務(wù)維護(hù)的成本,不管是現(xiàn)在還是將來。

微服務(wù)是什么樣的

從目前常見網(wǎng)站架構(gòu)的宏觀角度看,微服務(wù)處在中間的層次。紅框圈出的部分都屬于微服務(wù)的范疇。

 

包括最基礎(chǔ)的 RPC 框架、注冊(cè)中心、配置中心,以及更廣義角度的監(jiān)控追蹤、治理中心、調(diào)度中心等。

從微服務(wù)自身角度來看,則大致會(huì)包含以下這些模塊:

 

  • 服務(wù)注冊(cè)與發(fā)現(xiàn)
  • RPC 遠(yuǎn)程調(diào)用
  • 路由與負(fù)載均衡
  • 服務(wù)監(jiān)控
  • 服務(wù)治理

服務(wù)化的前提

是不是只要套上微服務(wù)框架就算是一個(gè)微服務(wù)了呢?雖然這樣有了微服務(wù)的表,但卻沒有微服務(wù)的實(shí)質(zhì),即“微”。

微服務(wù)化的前提是服務(wù)拆分到足夠”微“,足夠單一職責(zé),當(dāng)然拆分程度與服務(wù)邊界都需要結(jié)合業(yè)務(wù)自行把握。

廣義的服務(wù)拆分即包含了應(yīng)用拆分,也包含了數(shù)據(jù)拆分。應(yīng)用拆分后需要引入微服務(wù)框架來進(jìn)行服務(wù)通信與服務(wù)治理,這也就是傳統(tǒng)定義上的微服務(wù)。

數(shù)據(jù)拆分后同樣需要引入一系列手段來進(jìn)行保障,由于不是與微服務(wù)強(qiáng)相關(guān)的話題,在此只做簡(jiǎn)單闡述:

  • 分布式 ID
  • 新表優(yōu)化
  • 數(shù)據(jù)遷移與數(shù)據(jù)同步
  • SQL 調(diào)用方案改造
  • 切庫方案
  • 數(shù)據(jù)一致性

具體的微服務(wù)請(qǐng)求背后

在我們對(duì)微服務(wù)架構(gòu)有了整體的認(rèn)識(shí),并且具備了服務(wù)化的前提后,一個(gè)完整的微服務(wù)請(qǐng)求需要涉及到哪些內(nèi)容呢?

這其中包括了微服務(wù)框架所具備的三個(gè)基本功能:

  • 服務(wù)的發(fā)布與引用
  • 服務(wù)的注冊(cè)與發(fā)現(xiàn)
  • 服務(wù)的遠(yuǎn)程通信

服務(wù)的發(fā)布與引用

首先我們面臨的第一個(gè)問題是,如何發(fā)布服務(wù)和引用服務(wù)。具體一點(diǎn)就是,這個(gè)服務(wù)的接口名是啥,有哪些參數(shù),返回值是什么類型等等,通常也就是接口描述信息。

常見的發(fā)布和引用的方式包括:

  • RESTful API/聲明式 Restful API
  • XML
  • IDL

一般來講,不管使用哪種方式,服務(wù)端定義接口與實(shí)現(xiàn)接口都是必要的,例如:

 

  1. @exa(id = "xxx"
  2. public interface testApi { 
  3.  
  4.     @PostMapping(value = "/soatest/{id}"
  5.     String getResponse(@PathVariable(value = "id") final Integer index, @RequestParam(value = "str") final String Data); 
  6.     } 

具體實(shí)現(xiàn)如下:

 

  1. public class testApiImpl implements testApi{ 
  2.  
  3.         @Override 
  4.         String getResponse(final Integer index, final String Data){ 
  5.             return "ok"
  6.         } 

聲明式 Restful API:這種常使用 HTTP 或者 HTTPS 協(xié)議調(diào)用服務(wù),相對(duì)來說,性能稍差。

首先服務(wù)端如上定義接口并實(shí)現(xiàn)接口,隨后服務(wù)提供者可以使用類似 restEasy 這樣的框架通過 Servlet 的方式發(fā)布服務(wù),而服務(wù)消費(fèi)者直接引用定義的接口調(diào)用。

除此之外還有一種類似 Feign 的方式,即服務(wù)端的發(fā)布依賴于 SpringMVC Controller,框架只基于客戶端模板化 HTTP 請(qǐng)求調(diào)用。

這種情況下需接口定義與服務(wù)端 Controller 協(xié)商一致,這樣客戶端直接引用接口發(fā)起調(diào)用即可。

XML:使用私有 RPC 協(xié)議的都會(huì)選擇 XML 配置的方式來描述接口,比較高效,例如 Dubbo、Motan 等。

同樣服務(wù)端如上定義接口并實(shí)現(xiàn)接口,服務(wù)端通過 server.xml 將文件接口暴露出去。服務(wù)消費(fèi)者則通過 client.xml 引用需要調(diào)用的接口。

但這種方式對(duì)業(yè)務(wù)代碼入侵較高,XML 配置有變更時(shí)候,服務(wù)消費(fèi)者和服務(wù)提供者都需要更新。

IDL:是接口描述語言,常用于跨語言之間的調(diào)用,最常用的 IDL 包括 Thrift 協(xié)議以及 gRpc 協(xié)議。

例如 gRpc 協(xié)議使用 Protobuf 來定義接口,寫好一個(gè) proto 文件后,利用語言對(duì)應(yīng)的 protoc 插件生成對(duì)應(yīng) server 端與 client 端的代碼,便可直接使用。

但是如果參數(shù)字段非常多,proto 文件會(huì)顯得非常大難以維護(hù)。并且如果字段經(jīng)常需要變更,例如刪除字段,PB 就無法做到向前兼容。

一些 Tips:不管哪種方式,在接口變更的時(shí)候都需要通知服務(wù)消費(fèi)者。消費(fèi)者對(duì)api的強(qiáng)依賴性是很難避免的,接口變更引起的各種調(diào)用失敗也十分常見。

所以如果有變更,盡量使用新增接口的方式,或者給每個(gè)接口定義好版本號(hào)吧。在使用上,大多數(shù)人的選擇是對(duì)外 Restful,對(duì)內(nèi) XML,跨語言 IDL。

一些問題:在實(shí)際的服務(wù)發(fā)布與引用的落地上,還會(huì)存在很多問題,大多和配置信息相關(guān)。

例如一個(gè)簡(jiǎn)單的接口調(diào)用超時(shí)時(shí)間配置,這個(gè)配置應(yīng)該配在服務(wù)級(jí)別還是接口級(jí)別?是放在服務(wù)提供者這邊還是服務(wù)消費(fèi)者這邊?

在實(shí)踐中,大多數(shù)服務(wù)消費(fèi)者會(huì)忽略這些配置,所以服務(wù)提供者自身提供默認(rèn)的配置模板是有必要的,相當(dāng)于一個(gè)預(yù)定義的過程。

每個(gè)服務(wù)消費(fèi)者在繼承服務(wù)提供者預(yù)定義好的配置后,還需要能夠進(jìn)行自定義的配置覆蓋。

但是,比方說一個(gè)服務(wù)有 100 個(gè)接口,每個(gè)接口都有自身的超時(shí)配置,而這個(gè)服務(wù)又有 100 個(gè)消費(fèi)者,當(dāng)服務(wù)節(jié)點(diǎn)發(fā)生變更的時(shí)候,就會(huì)發(fā)生 100*100 次注冊(cè)中心的消息通知,這是比較可怕的,就有可能引起網(wǎng)絡(luò)風(fēng)暴。

服務(wù)的注冊(cè)與發(fā)現(xiàn)

假設(shè)你已經(jīng)發(fā)布了服務(wù),并在一臺(tái)機(jī)器上部署了服務(wù),那么消費(fèi)者該怎樣找到你的服務(wù)的地址呢?

也許有人會(huì)說是 DNS,但 DNS 有許多缺陷:

  • 維護(hù)麻煩,更新延遲
  • 無法在客戶端做負(fù)載均衡
  • 不能做到端口級(jí)別的服務(wù)發(fā)現(xiàn)

其實(shí)在分布式系統(tǒng)中,有個(gè)很重要的角色,叫注冊(cè)中心,便是用于解決該問題。

 

使用注冊(cè)中心尋址并調(diào)用的過程如下:

  • 服務(wù)啟動(dòng)時(shí),向注冊(cè)中心注冊(cè)自身,并定期發(fā)送心跳匯報(bào)存活狀態(tài)。
  • 客戶端調(diào)用服務(wù)時(shí),向注冊(cè)中心訂閱服務(wù),并將節(jié)點(diǎn)列表緩存至本地,再與服務(wù)端建立連接(當(dāng)然這兒可以 lazy load)。發(fā)起調(diào)用時(shí),在本地緩存節(jié)點(diǎn)列表中,基于負(fù)載均衡算法選取一臺(tái)服務(wù)端發(fā)起調(diào)用。
  • 當(dāng)服務(wù)端節(jié)點(diǎn)發(fā)生變更,注冊(cè)中心能感知到后通知到客戶端。

注冊(cè)中心的實(shí)現(xiàn)主要需要考慮以下這些問題:

  • 自身一致性與可用性
  • 注冊(cè)方式
  • 存儲(chǔ)結(jié)構(gòu)
  • 服務(wù)健康監(jiān)測(cè)
  • 狀態(tài)變更通知

①一致性與可用性

一個(gè)老舊的命題,即分布式系統(tǒng)中的 CAP(一致性、可用性、分區(qū)容錯(cuò)性)。

我們知道同時(shí)滿足 CAP 是不可能的,那么便需要有取舍。常見的注冊(cè)中心大致分為 CP 注冊(cè)中心以及 AP 注冊(cè)中心。

CP 注冊(cè)中心:比較典型的就是 Zookeeper、etcd 以及 Consul 了,犧牲可用性來保證了一致性,通過 Zab 協(xié)議或者 Raft 協(xié)議來保證一致性。

AP 注冊(cè)中心:犧牲一致性來保證可用性,感覺只能列出 Eureka 了。Eureka 每個(gè)服務(wù)器單獨(dú)保存節(jié)點(diǎn)列表,可能會(huì)出現(xiàn)不一致的情況。

從理論上來說,僅用于注冊(cè)中心,AP 型是遠(yuǎn)比 CP 型合適的。可用性的需求遠(yuǎn)遠(yuǎn)高于一致性,一致性只要保證最終一致即可,而不一致的時(shí)候還可以使用各種容錯(cuò)策略進(jìn)行彌補(bǔ)。

保障高可用性其實(shí)還有很多辦法,例如集群部署或者多 IDC 部署等。Consul 就是多 IDC 部署保障可用性的典型例子,它使用了 wan gossip 來保持跨機(jī)房狀態(tài)同步。

②注冊(cè)方式

有兩種與注冊(cè)中心交互的方式,一種是通過應(yīng)用內(nèi)集成 SDK,另一種則是通過其他方式在應(yīng)用外間接與注冊(cè)中心交互。

應(yīng)用內(nèi):這應(yīng)該就是最常見的方式了,客戶端與服務(wù)端都集成相關(guān)sdk與注冊(cè)中心進(jìn)行交互。

例如選擇 Zookeeper 作為注冊(cè)中心,那么就可以使用 Curator SDK 進(jìn)行服務(wù)的注冊(cè)與發(fā)現(xiàn)。

應(yīng)用外:Consul 提供了應(yīng)用外注冊(cè)的解決方案,Consul Agent 或者第三方 Registrator 可以監(jiān)聽服務(wù)狀態(tài),從而負(fù)責(zé)服務(wù)提供者的注冊(cè)或銷毀。

而 Consul Template 則可以做到定時(shí)從注冊(cè)中心拉取節(jié)點(diǎn)列表,并刷新 LB 配置(例如通過 Nginx 的 upstream),這樣就相當(dāng)于完成了服務(wù)消費(fèi)者端的負(fù)載均衡。

③存儲(chǔ)結(jié)構(gòu)

注冊(cè)中心存儲(chǔ)相關(guān)信息一般采取目錄化的層次結(jié)構(gòu),一般分為服務(wù)-接口-節(jié)點(diǎn)信息。

同時(shí)注冊(cè)中心一般還會(huì)進(jìn)行分組,分組的概念很廣,可以是根據(jù)機(jī)房劃分也可以根據(jù)環(huán)境劃分。

節(jié)點(diǎn)信息主要會(huì)包括節(jié)點(diǎn)的地址(ip 和端口號(hào)),還有一些節(jié)點(diǎn)的其他信息,比如請(qǐng)求失敗的重試次數(shù)、超時(shí)時(shí)間的設(shè)置等等。

當(dāng)然很多時(shí)候,其實(shí)可能會(huì)把接口這一層給去掉,因?yàn)榭紤]到接口數(shù)量很多的情況下,過多的節(jié)點(diǎn)會(huì)造成很多問題,比如之前說的網(wǎng)絡(luò)風(fēng)暴。

④服務(wù)健康監(jiān)測(cè)

服務(wù)存活狀態(tài)監(jiān)測(cè)也是注冊(cè)中心的一個(gè)必要功能。在 Zookeeper 中,每個(gè)客戶端都會(huì)與服務(wù)端保持一個(gè)長(zhǎng)連接,并生成一個(gè) Session。

在 Session 過期周期內(nèi),通過客戶端定時(shí)向服務(wù)端發(fā)送心跳包來檢測(cè)鏈路是否正常,服務(wù)端則重置下次 Session 的過期時(shí)間。

如果 Session 過期周期內(nèi)都沒有檢測(cè)到客戶端的心跳包,那么就會(huì)認(rèn)為它已經(jīng)不可用了,將其從節(jié)點(diǎn)列表中移除。

⑤狀態(tài)變更通知

在注冊(cè)中心具備服務(wù)健康檢測(cè)能力后,還需要將狀態(tài)變更通知到客戶端。在 Zookeeper 中,可以通過監(jiān)聽器 Watcher 的 Process 方法來獲取服務(wù)變更。

服務(wù)的遠(yuǎn)程通信

在上面,服務(wù)消費(fèi)者已經(jīng)正確引用了服務(wù),并發(fā)現(xiàn)了該服務(wù)的地址,那么如何向這個(gè)地址發(fā)起請(qǐng)求呢?

要解決服務(wù)間的遠(yuǎn)程通信問題,我們需要考慮一些問題:

  • 網(wǎng)絡(luò) I/O 的處理
  • 傳輸協(xié)議
  • 序列化方式

①網(wǎng)絡(luò) I/O 的處理

簡(jiǎn)單來說,就是客戶端是怎么處理請(qǐng)求?服務(wù)端又是怎么處理請(qǐng)求的?

先從客戶端來說,我們創(chuàng)建連接的時(shí)機(jī)可以是從注冊(cè)中心獲取到節(jié)點(diǎn)信息的時(shí)候,但更多時(shí)候,我們會(huì)選擇在第一次請(qǐng)求發(fā)起調(diào)用的時(shí)候去創(chuàng)建連接。此外,我們往往會(huì)為該節(jié)點(diǎn)維護(hù)一個(gè)連接池,進(jìn)行連接復(fù)用。

如果是異步的情況下,我們還需要為每一個(gè)請(qǐng)求編號(hào),并維護(hù)一個(gè)請(qǐng)求池,從而在響應(yīng)返回時(shí)找到對(duì)應(yīng)的請(qǐng)求。當(dāng)然這并不是必須的,很多框架會(huì)幫我們干好這些事情,比如 rxNetty。

從服務(wù)端來說,處理請(qǐng)求的方式就可以追溯到 Unix 的 5 種 IO 模型了。我們可以直接使用 Netty、MINA 等網(wǎng)絡(luò)框架來處理服務(wù)端請(qǐng)求,或者如果你有十分的興趣,可以自己實(shí)現(xiàn)一個(gè)通信框架。

②傳輸協(xié)議

最常見的當(dāng)然是直接使用 HTTP 協(xié)議,使用雙方無需關(guān)注和了解協(xié)議內(nèi)容,方便直接,但自然性能上會(huì)有所折損。

還有就是目前比較火熱的 HTTP2 協(xié)議,擁有二進(jìn)制數(shù)據(jù)、頭部壓縮、多路復(fù)用等許多優(yōu)良特性。

但從自身的實(shí)踐上看,HTTP2 要走到生產(chǎn)仍有一段距離,一個(gè)最簡(jiǎn)單的例子,升級(jí)到 HTTP2 后所有的 header names 都變成小寫,同時(shí)不是 case-insenstive 了,這時(shí)候就會(huì)有兼容性問題。

當(dāng)然如果追求更高效與可控的傳輸,可以定制私有協(xié)議并基于 TCP 進(jìn)行傳輸。私有協(xié)議的定制需要通信雙方都了解其特性,設(shè)計(jì)上還需要注意預(yù)留好擴(kuò)展字段,以及處理好粘包分包等問題。

③序列化方式

在網(wǎng)絡(luò)傳輸?shù)那昂?,往往都需要在發(fā)送端進(jìn)行編碼,在服務(wù)端進(jìn)行解碼,這樣主要是為了在網(wǎng)絡(luò)傳輸時(shí)候減少數(shù)據(jù)傳輸量。

常用的序列化方式包括文本類的,例如 XML/JSON,還有二進(jìn)制類型的,例如 Protobuf/Thrift 等。

在選擇序列化的考慮上:

一是性能,Protobuf 的壓縮大小和壓縮速度都會(huì)比 JSON 快很多,性能也更好。

二是兼容性上,相對(duì)來說,JSON 的前后兼容性會(huì)強(qiáng)一些,可以用于接口經(jīng)常變化的場(chǎng)景。

在此還是需要強(qiáng)調(diào),使用每一種序列化都需要了解過其特性,并在接口變更的時(shí)候拿捏好邊界。

例如 jackson 的 FAIL_ON_UNKNOW_PROPERTIES 屬性、kryo 的 CompatibleFieldSerializer、jdk 序列化會(huì)嚴(yán)格比較 serialVersionUID 等等。

微服務(wù)的穩(wěn)定性

當(dāng)一個(gè)單體應(yīng)用改造成多個(gè)微服務(wù)之后,在請(qǐng)求調(diào)用過程中往往會(huì)出現(xiàn)更多的問題,通信過程中的每一個(gè)環(huán)節(jié)都可能出現(xiàn)問題。

而在出現(xiàn)問題之后,如果不加處理,還會(huì)出現(xiàn)鏈?zhǔn)椒磻?yīng)導(dǎo)致服務(wù)雪崩。服務(wù)治理功能就是用來處理此類問題的。

我們將從微服務(wù)的三個(gè)角色:注冊(cè)中心、服務(wù)消費(fèi)者以及服務(wù)提供者一一說起。

注冊(cè)中心如何保障穩(wěn)定性

注冊(cè)中心主要是負(fù)責(zé)節(jié)點(diǎn)狀態(tài)的維護(hù),以及相應(yīng)的變更探測(cè)與通知操作。

一方面,注冊(cè)中心自身的穩(wěn)定性是十分重要的。另一方面,我們也不能完全依賴注冊(cè)中心,需要時(shí)常進(jìn)行類似注冊(cè)中心完全宕機(jī)后微服務(wù)如何正常運(yùn)行的故障演練。

這一節(jié),我們著重講的并不是注冊(cè)中心自身可用性保證,而更多的是與節(jié)點(diǎn)狀態(tài)相關(guān)的部分。

①節(jié)點(diǎn)信息的保障

我們說過,當(dāng)注冊(cè)中心完全宕機(jī)后,微服務(wù)框架仍然需要有正常工作的能力。這得益于框架內(nèi)處理節(jié)點(diǎn)狀態(tài)的一些機(jī)制。

本機(jī)內(nèi)存:首先服務(wù)消費(fèi)者會(huì)將節(jié)點(diǎn)狀態(tài)保持在本機(jī)內(nèi)存中。

一方面由于節(jié)點(diǎn)狀態(tài)不會(huì)變更得那么頻繁,放在內(nèi)存中可以減少網(wǎng)絡(luò)開銷。另一方面,當(dāng)注冊(cè)中心宕機(jī)后,服務(wù)消費(fèi)者仍能從本機(jī)內(nèi)存中找到服務(wù)節(jié)點(diǎn)列表從而發(fā)起調(diào)用。

本地快照:我們說,注冊(cè)中心宕機(jī)后,服務(wù)消費(fèi)者仍能從本機(jī)內(nèi)存中找到服務(wù)節(jié)點(diǎn)列表。那么如果服務(wù)消費(fèi)者重啟了呢?

這時(shí)候我們就需要一份本地快照了,即我們保存一份節(jié)點(diǎn)狀態(tài)到本地文件,每次重啟之后會(huì)恢復(fù)到本機(jī)內(nèi)存中。

②服務(wù)節(jié)點(diǎn)的摘除

現(xiàn)在無論注冊(cè)中心工作與否,我們都能順利拿到服務(wù)節(jié)點(diǎn)了。但是不是所有的服務(wù)節(jié)點(diǎn)都是正確可用的呢?

在實(shí)際應(yīng)用中,這是需要打問號(hào)的。如果我們不校驗(yàn)服務(wù)節(jié)點(diǎn)的正確性,很有可能就調(diào)用到了一個(gè)不正常的節(jié)點(diǎn)上。所以我們需要進(jìn)行必要的節(jié)點(diǎn)管理。

對(duì)于節(jié)點(diǎn)管理來說,我們有兩種手段,主要是去摘除不正確的服務(wù)節(jié)點(diǎn)。

注冊(cè)中心摘除機(jī)制:一是通過注冊(cè)中心來進(jìn)行摘除節(jié)點(diǎn)。服務(wù)提供者會(huì)與注冊(cè)中心保持心跳,而一旦超出一定時(shí)間收不到心跳包,注冊(cè)中心就認(rèn)為該節(jié)點(diǎn)出現(xiàn)了問題,會(huì)把節(jié)點(diǎn)從服務(wù)列表中摘除,并通知到服務(wù)消費(fèi)者,這樣服務(wù)消費(fèi)者就不會(huì)調(diào)用到有問題的節(jié)點(diǎn)上。

服務(wù)消費(fèi)者摘除機(jī)制:二是在服務(wù)消費(fèi)者這邊拆除節(jié)點(diǎn)。因?yàn)榉?wù)消費(fèi)者自身是最知道節(jié)點(diǎn)是否可用的角色,所以在服務(wù)消費(fèi)者這邊做判斷更合理,如果服務(wù)消費(fèi)者調(diào)用出現(xiàn)網(wǎng)絡(luò)異常,就將該節(jié)點(diǎn)從內(nèi)存緩存列表中摘除。

當(dāng)然調(diào)用失敗多少次之后才進(jìn)行摘除,以及摘除恢復(fù)的時(shí)間等等細(xì)節(jié),其實(shí)都和客戶端熔斷類似,可以結(jié)合起來做。

一般來說,對(duì)于大流量應(yīng)用,服務(wù)消費(fèi)者摘除的敏感度會(huì)高于注冊(cè)中心摘除,兩者之間也不用刻意做同步判斷,因?yàn)檫^一段時(shí)間后注冊(cè)中心摘除會(huì)自動(dòng)覆蓋服務(wù)消費(fèi)者摘除。

③服務(wù)節(jié)點(diǎn)是可以隨便摘除/變更的么

上一節(jié)我們講可以摘除問題節(jié)點(diǎn),從而避免流量調(diào)用到該節(jié)點(diǎn)上。但節(jié)點(diǎn)是可以隨便摘除的么?同時(shí),這也包含"節(jié)點(diǎn)是可以隨便更新的么?"疑問。

頻繁變動(dòng):當(dāng)網(wǎng)絡(luò)抖動(dòng)的時(shí)候,注冊(cè)中心的節(jié)點(diǎn)就會(huì)不斷變動(dòng)。這導(dǎo)致的后果就是變更消息會(huì)不斷通知到服務(wù)消費(fèi)者,服務(wù)消費(fèi)者不斷刷新本地緩存。

如果一個(gè)服務(wù)提供者有 100 個(gè)節(jié)點(diǎn),同時(shí)有 100 個(gè)服務(wù)消費(fèi)者,那么頻繁變動(dòng)的效果可能就是 100*100,引起帶寬打滿。

這時(shí)候,我們可以在注冊(cè)中心這邊做一些控制,例如經(jīng)過一段時(shí)間間隔后才能進(jìn)行變更消息通知,或者打開開關(guān)后直接屏蔽不進(jìn)行通知,或者通過一個(gè)概率計(jì)算來判斷需要向哪些服務(wù)消費(fèi)者通知。

增量更新:同樣是由于頻繁變動(dòng)可能引起的網(wǎng)絡(luò)風(fēng)暴問題,一個(gè)可行的方案是進(jìn)行增量更新,注冊(cè)中心只會(huì)推送那些變化的節(jié)點(diǎn)信息而不是全部,從而在頻繁變動(dòng)的時(shí)候避免網(wǎng)絡(luò)風(fēng)暴。

可用節(jié)點(diǎn)過少:當(dāng)網(wǎng)絡(luò)抖動(dòng),并進(jìn)行節(jié)點(diǎn)摘除過后,很可能出現(xiàn)可用節(jié)點(diǎn)過少的情況。

這時(shí)候過大的流量分配給過少的節(jié)點(diǎn),導(dǎo)致剩下的節(jié)點(diǎn)難堪重負(fù),罷工不干,引起惡化。

而實(shí)際上,可能節(jié)點(diǎn)大多數(shù)是可用的,只不過由于網(wǎng)絡(luò)問題與注冊(cè)中心未能及時(shí)保持心跳而已。

這時(shí)候,就需要在服務(wù)消費(fèi)者這邊設(shè)置一個(gè)開關(guān)比例閾值,當(dāng)注冊(cè)中心通知節(jié)點(diǎn)摘除,但緩存列表中剩下的節(jié)點(diǎn)數(shù)低于一定比例后(與之前一段時(shí)間相比),不再進(jìn)行摘除,從而保證有足夠的節(jié)點(diǎn)提供正常服務(wù)。

這個(gè)值其實(shí)可以設(shè)置的高一些,例如百分之 70,因?yàn)檎G闆r下不會(huì)有頻繁的網(wǎng)絡(luò)抖動(dòng)。當(dāng)然,如果開發(fā)者確實(shí)需要下線多數(shù)節(jié)點(diǎn),可以關(guān)閉該開關(guān)。

服務(wù)消費(fèi)者如何保障穩(wěn)定性

一個(gè)請(qǐng)求失敗了,最直接影響到的是服務(wù)消費(fèi)者,那么在服務(wù)消費(fèi)者這邊,有什么可以做的呢?

①超時(shí)

如果調(diào)用一個(gè)接口,但遲遲沒有返回響應(yīng)的時(shí)候,我們往往需要設(shè)置一個(gè)超時(shí)時(shí)間,以防自己被遠(yuǎn)程調(diào)用拖死。

超時(shí)時(shí)間的設(shè)置也是有講究的,設(shè)置的太長(zhǎng)起的作用就小,自己被拖垮的風(fēng)險(xiǎn)就大,設(shè)置的太短又有可能誤判一些正常請(qǐng)求,大幅提升錯(cuò)誤率。

在實(shí)際使用中,我們可以取該應(yīng)用一段時(shí)間內(nèi)的 P999 的值,或者取 p95 的值*2,具體情況需要自行定奪。

在超時(shí)設(shè)置的時(shí)候,對(duì)于同步與異步的接口也是有區(qū)分的。對(duì)于同步接口,超時(shí)設(shè)置的值不僅需要考慮到下游接口,還需要考慮上游接口。

而對(duì)于異步來說,由于接口已經(jīng)快速返回,可以不用考慮上游接口,只需考慮自身在異步線程里的阻塞時(shí)長(zhǎng),所以超時(shí)時(shí)間也放得更寬一些。

②容錯(cuò)機(jī)制

請(qǐng)求調(diào)用永遠(yuǎn)不能保證成功,那么當(dāng)請(qǐng)求失敗時(shí)候,服務(wù)消費(fèi)者可以如何進(jìn)行容錯(cuò)呢?

通常容錯(cuò)機(jī)制分為以下這些:

  • FailTry:失敗重試。就是指最常見的重試機(jī)制,當(dāng)請(qǐng)求失敗后視圖再次發(fā)起請(qǐng)求進(jìn)行重試。

這樣從概率上講,失敗率會(huì)呈指數(shù)下降。對(duì)于重試次數(shù)來說,也需要選擇一個(gè)恰當(dāng)?shù)闹?,如果重試次?shù)太多,就有可能引起服務(wù)惡化。

另外,結(jié)合超時(shí)時(shí)間來說,對(duì)于性能有要求的服務(wù),可以在超時(shí)時(shí)間到達(dá)前的一段提前量就發(fā)起重試,從而在概率上優(yōu)化請(qǐng)求調(diào)用。當(dāng)然,重試的前提是冪等操作。

  • FailOver:失敗切換。和上面的策略類似,只不過 FailTry 會(huì)在當(dāng)前實(shí)例上重試。而 FailOver 會(huì)重新在可用節(jié)點(diǎn)列表中根據(jù)負(fù)載均衡算法選擇一個(gè)節(jié)點(diǎn)進(jìn)行重試。
  • FailFast:快速失敗。請(qǐng)求失敗了就直接報(bào)一個(gè)錯(cuò),或者記錄在錯(cuò)誤日志中,這沒什么好說的。

另外,還有很多形形色色的容錯(cuò)機(jī)制,大多是基于自己的業(yè)務(wù)特性定制的,主要是在重試上做文章,例如每次重試等待時(shí)間都呈指數(shù)增長(zhǎng)等。

第三方框架也都會(huì)內(nèi)置默認(rèn)的容錯(cuò)機(jī)制,例如 Ribbon 的容錯(cuò)機(jī)制就是由 retry 以及 retry next 組成,即重試當(dāng)前實(shí)例與重試下一個(gè)實(shí)例。

這里要多說一句,Ribbon 的重試次數(shù)與重試下一個(gè)實(shí)例次數(shù)是以笛卡爾乘積的方式提供的噢!

③熔斷

上一節(jié)將的容錯(cuò)機(jī)制,主要是一些重試機(jī)制,對(duì)于偶然因素導(dǎo)致的錯(cuò)誤比較有效,例如網(wǎng)絡(luò)原因。

但如果錯(cuò)誤的原因是服務(wù)提供者自身的故障,那么重試機(jī)制反而會(huì)引起服務(wù)惡化。

這時(shí)候我們需要引入一種熔斷的機(jī)制,即在一定時(shí)間內(nèi)不再發(fā)起調(diào)用,給予服務(wù)提供者一定的恢復(fù)時(shí)間,等服務(wù)提供者恢復(fù)正常后再發(fā)起調(diào)用。這種保護(hù)機(jī)制大大降低了鏈?zhǔn)疆惓R鸬姆?wù)雪崩的可能性。

在實(shí)際應(yīng)用中,熔斷器往往分為三種狀態(tài),打開、半開以及關(guān)閉。引用一張 MartinFowler 畫的原理圖:

 

在普通情況下,斷路器處于關(guān)閉狀態(tài),請(qǐng)求可以正常調(diào)用。當(dāng)請(qǐng)求失敗達(dá)到一定閾值條件時(shí),則打開斷路器,禁止向服務(wù)提供者發(fā)起調(diào)用。

當(dāng)斷路器打開后一段時(shí)間,會(huì)進(jìn)入一個(gè)半開的狀態(tài),此狀態(tài)下的請(qǐng)求如果調(diào)用成功了則關(guān)閉斷路器,如果沒有成功則重新打開斷路器,等待下一次半開狀態(tài)周期。

斷路器的實(shí)現(xiàn)中比較重要的一點(diǎn)是失敗閾值的設(shè)置。可以根據(jù)業(yè)務(wù)需求設(shè)置失敗的條件為連續(xù)失敗的調(diào)用次數(shù),也可以是時(shí)間窗口內(nèi)的失敗比率,失敗比率通過一定的滑動(dòng)窗口算法進(jìn)行計(jì)算。

另外,針對(duì)斷路器的半開狀態(tài)周期也可以做一些花樣,一種常見的計(jì)算方法是周期長(zhǎng)度隨著失敗次數(shù)呈指數(shù)增長(zhǎng)。

具體的實(shí)現(xiàn)方式可以根據(jù)具體業(yè)務(wù)指定,也可以選擇第三方框架例如 Hystrix。

④隔離

隔離往往和熔斷結(jié)合在一起使用,還是以 Hystrix 為例,它提供了兩種隔離方式:

信號(hào)量隔離:使用信號(hào)量來控制隔離線程,你可以為不同的資源設(shè)置不同的信號(hào)量以控制并發(fā),并相互隔離。當(dāng)然實(shí)際上,使用原子計(jì)數(shù)器也沒什么不一樣。

線程池隔離:通過提供相互隔離的線程池的方式來隔離資源,相對(duì)來說消耗資源更多,但可以更好地應(yīng)對(duì)突發(fā)流量。

⑤降級(jí)

降級(jí)同樣大多和熔斷結(jié)合在一起使用,當(dāng)服務(wù)調(diào)用者這方斷路器打開后,無法再對(duì)服務(wù)提供者發(fā)起調(diào)用了,這時(shí)候可以通過返回降級(jí)數(shù)據(jù)來避免熔斷造成的影響。

降級(jí)往往用于那些錯(cuò)誤容忍度較高的業(yè)務(wù)。同時(shí)降級(jí)的數(shù)據(jù)如何設(shè)置也是一門學(xué)問。

一種方法是為每個(gè)接口預(yù)先設(shè)置好可接受的降級(jí)數(shù)據(jù),但這種靜態(tài)降級(jí)的方法適用性較窄。

還有一種方法,是去線上日志系統(tǒng)/流量錄制系統(tǒng)中撈取上一次正確的返回?cái)?shù)據(jù)作為本次降級(jí)數(shù)據(jù),但這種方法的關(guān)鍵是提供可供穩(wěn)定抓取請(qǐng)求的日志系統(tǒng)或者流量采樣錄制系統(tǒng)。

另外,針對(duì)降級(jí)我們往往還會(huì)設(shè)置操作開關(guān),對(duì)于一些影響不大的采取自動(dòng)降級(jí),而對(duì)于一些影響較大的則需進(jìn)行人為干預(yù)降級(jí)。

服務(wù)提供者如何保障穩(wěn)定性

①限流

限流就是限制服務(wù)請(qǐng)求流量,服務(wù)提供者可以根據(jù)自身情況(容量)給請(qǐng)求設(shè)置一個(gè)閾值,當(dāng)超過這個(gè)閾值后就丟棄請(qǐng)求,這樣就保證了自身服務(wù)的正常運(yùn)行。

閾值的設(shè)置可以針對(duì)兩個(gè)方面考慮:

  • QPS,即每秒請(qǐng)求數(shù)
  • 并發(fā)線程數(shù)

從實(shí)踐來看,我們往往會(huì)選擇后者,因?yàn)?QPS 高往往是由于處理能力高,并不能反映出系統(tǒng)"不堪重負(fù)"。

除此之外,我們還有許多針對(duì)限流的算法。例如令牌桶算法以及漏桶算法,主要針對(duì)突發(fā)流量的狀況做了優(yōu)化。

第三方的實(shí)現(xiàn)中例如 guava rateLimiter 就實(shí)現(xiàn)了令牌桶算法。在此就不就細(xì)節(jié)展開了。

②重啟與回滾

限流更多的起到一種保障的作用,但如果服務(wù)提供者已經(jīng)出現(xiàn)問題了,這時(shí)候該怎么辦呢?

這時(shí)候就會(huì)出現(xiàn)兩種狀況:一是本身代碼有 Bug,這時(shí)候一方面需要服務(wù)消費(fèi)者做好熔斷降級(jí)等操作,一方面服務(wù)提供者這邊結(jié)合 DevOps 需要有快速回滾到上一個(gè)正確版本的能力。

更多的時(shí)候,我們可能僅僅碰到了與代碼無強(qiáng)關(guān)聯(lián)的單機(jī)故障,一個(gè)簡(jiǎn)單粗暴的辦法就是自動(dòng)重啟。

例如觀察到某個(gè)接口的平均耗時(shí)超出了正常范圍一定程度,就將該實(shí)例進(jìn)行自動(dòng)重啟。

當(dāng)然自動(dòng)重啟需要有很多注意事項(xiàng),例如重啟時(shí)間是否放在晚上,以及自動(dòng)重啟引起的與上述節(jié)點(diǎn)摘除一樣的問題,都需要考慮和處理。

在事后復(fù)盤的時(shí)候,如果當(dāng)時(shí)沒有保護(hù)現(xiàn)場(chǎng),就很難定位到問題原因。所以往往在一鍵回滾或者自動(dòng)重啟之前,我們往往需要進(jìn)行現(xiàn)場(chǎng)保護(hù)。

現(xiàn)場(chǎng)保護(hù)可以是自動(dòng)的,例如:

  • 一開始就給 jvm 加上打印 gc 日志的參數(shù) -XX:+PrintGCDetails
  • 或者輸出 oom 文件 -XX:+HeapDumpOnOutOfMemoryError
  • 也可以配合 DevOps 自動(dòng)腳本完成,當(dāng)然手動(dòng)也可以

一般來說我們會(huì)如下操作:

  • 打印堆棧信息,jstak -l 'java進(jìn)程PID'
  • 打印內(nèi)存鏡像,jmap -dump:format=b,file=hprof 'java進(jìn)程PID'
  • 保留 gc 日志,保留業(yè)務(wù)日志

③調(diào)度流量

除了以上這些措施,通過調(diào)度流量來避免調(diào)用到問題節(jié)點(diǎn)上也是非常常用的手段。

當(dāng)服務(wù)提供者中的一臺(tái)機(jī)器出現(xiàn)問題,而其他機(jī)器正常時(shí),我們可以結(jié)合負(fù)載均衡算法迅速調(diào)整該機(jī)器的權(quán)重至 0,避免流量流入,再去機(jī)器上進(jìn)行慢慢排查,而不用著急第一時(shí)間重啟。

如果服務(wù)提供者分了不同集群/分組,當(dāng)其中一個(gè)集群出現(xiàn)問題時(shí),我們也可以通過路由算法將流量路由到正常的集群中。這時(shí)候一個(gè)集群就是一個(gè)微服務(wù)分組。

而當(dāng)機(jī)房炸了、光纜被偷了等 IDC 故障時(shí),我們又部署了多 IDC,也可以通過一些方式將流量切換到正常的 IDC,以供服務(wù)繼續(xù)正常運(yùn)行。

切換流量同樣可以通過微服務(wù)的路由實(shí)現(xiàn),但這時(shí)候一個(gè) IDC 對(duì)應(yīng)一個(gè)微服務(wù)分組了。

除此之外,使用 DNS 解析進(jìn)行流量切換也是可以的,將對(duì)外域名的 VIP 從一個(gè) IDC 切換到另一個(gè) IDC。

作者:fredalxin

編輯:陶家龍

出處:https://fredal.xin/

 

責(zé)任編輯:武曉燕 來源: redal.xin
相關(guān)推薦

2020-06-18 10:52:17

運(yùn)維架構(gòu)技術(shù)

2020-06-05 18:32:41

HBaseQAQHDFS

2019-06-17 13:34:17

Java程序員編程語言

2021-04-20 19:21:50

臟讀MySQL幻讀

2012-06-13 14:58:09

BYOD移動(dòng)辦公

2013-09-22 09:16:25

碼農(nóng)程序員黑客

2018-01-24 07:28:20

2017-04-27 13:30:14

AndroidWebView移動(dòng)應(yīng)用

2021-09-28 08:31:22

Flex:1 CSSflex屬性

2024-10-16 07:58:48

2024-12-03 08:16:57

2024-07-17 09:32:19

2024-09-23 09:12:20

2009-09-04 08:19:24

Windows 7優(yōu)缺點(diǎn)

2009-02-12 17:25:21

Windows7試用下載

2011-12-16 14:52:55

移動(dòng)互聯(lián)聯(lián)想

2023-08-31 22:17:15

JavaMySQLB+樹

2024-09-12 08:28:32

2024-10-17 13:05:35

神經(jīng)網(wǎng)絡(luò)算法機(jī)器學(xué)習(xí)深度學(xué)習(xí)

2019-10-30 21:27:51

Java中央處理器電腦
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)