【分佈式】服務調用的七種模式

  1. HardCode模式

    有些研發直接把要訪問的後端服務和端口寫死在代碼中,這種情況可以讓該研發去面壁思過三天,不討論了。

  2. 配置文件模式

    這是最通行和最簡單的模式,無論是後端web服務、還是mc、mysql等等,我們都可以配置在一個配置文件中(ini或者conf)等等。但後端服務發生變化了怎麼辦?此時運維要去做一次發佈,增加了運維變更成本。如果故障了怎麼辦?會直接增加故障影響時長。配置文件是一種靜態管理模式,需要對配置進行操作修改,帶來的成本都是很大的。

  3. 類LVS模式

    你一定見過很多內部服務都是走四層LVS模式,有些更可能走七層代理(nginx+keepalive),反正在我維護的業務中,我是嚴格拒絕這類業務請求。爲什麼會這樣?我把它轉換成幾個問題:

    A、引入一個組件是否就意味着架構變得複雜了?他的可靠性怎麼保證?;

    B、四層和七層代理模式所能探測的異常,是否和業務異常一致的?在四層情況下,四層正常不代表七層是正常的;在七層模式下,還有服務內部的異常情況,比如說服務器過載、socket句柄耗盡等等。

    C、代理模式作爲一個單點存在,特別對於包量和流量性服務來說,是否意味着是瓶頸;

    D、當一個七層服務異常的時候,除了運維干預就別無他法了,這個時候運維人是可靠的麼?運維還在下班路上故障怎麼辦?V**有異常怎麼辦?windows系統藍屏沒法打開怎麼辦?都會影響故障的處理。

    在內部服務調用上,我建議忘了它。

  4. DNS模式

    太經典的模式。DNS是公網訪問的常見模式,由於是標準協議,實現起來方便快捷。但依然是問題多多,

    A、DNS服務發現模式粒度太粗,只能到IP級別,端口依然需要自己去管理;

    B、對於後端一個異常服務來說,DNS無法提供自動容錯的能力,此時便需要運維的參與,特別在設置了TTL(10S)的情況下;

    C、DNS沒法實現服務的狀態收集,這部分信息反過來是可以爲運維提供指導的;

    D、DNS是一個靜態的資源發現,DNS指向的變更都需要依賴工具來完成。

    不過DNS還是有創新應用的地方,只是你我不知道,後面有個方案會介紹到。

    DNS只能解析。。

  5. 總線bus模式

    在以前很多SOA服務架構中,經常提到的是BUS總線架構,特別是在對歷史遺留系統的整合中,也常提及這種模式,比如說以前的CORBA架構。看似服務透明且解耦,並且服務動態可擴展,但依然問題多多。

    A、對總線的服務能力是個嚴峻的考驗,海量的服務請求進入到總線,意味着中總線需要海量的處理能力;

    B、總線還需要有QoS的服務能力,對不同的服務需要有不同的服務質量保證;

    C、總線的高可用,如果很集中,則需要總線發佈一致性保證,如果按照業務隔離總線,此時則對運維服務管理又是個負擔,太多的總線和業務做了綁定和耦合。

    在前不久和一個金融IT系統的人交流,他們早期採用的總線架構,後續逐漸下線掉,也都遇到了我說的幾個問題。

    總線很忙。。

  6. 類zk模式

    自從google的chubby介紹一出,在不久之後便有了開源實現—zookeeper。zk基於zab強一致性協議來實現,而chubby是基於paxos協議來實現的。爲了實現zk的高可用,其集羣必須是奇數個節點(3、5、7),一般是建議5個。奇數個節點有利於leade選舉,偶數個節點是無法公平投票的。由於集羣強一致性,當一個寫請求遞交到zk集羣中,此時可以保證寫入到所有節點中,那麼對於讀來說,讀任何一個節點取得的結果是一致的。類似流程如下:

    在這裏插入圖片描述

    zk有很多種使用場景:配置管理、名字服務、產生唯一序列號、鎖、權限控制等等。但也有問題,強一致性的要求,對性能有着很大的影響,其次zk集羣對於很多小業務來說,顯得太重了。

    不過ZK有個很不錯的地方,最小粒度的反向通知機制。當client attach的node發生變化的時候,zk cluster會反向事件通知到client,從而更新節點的信息。相比long-polling或者心跳機制來說,可以減少很多無效的消息通知。其次這種細粒度的消息控制,可以進一步減少消息發送。

  7. 類etcd模式

    這是基於最終一致性協議RAFT的go語言實現,目前是docker容器服務註冊和服務發現的標準實現。raft協議相比zab或者paxos來說,簡單多了,並且容易理解,網上也有非常多的基於raft的開源實現。它有個不是缺點的缺點:很多基於raft的實現都是非穩定版,不好直接線上直接使用。docker中的etcd的實現示意圖如下:

    在這裏插入圖片描述
    raft協議是一個非常輕量級靈巧的協議,值得大家深入看一下,無論是運維或者研發,我對它的關注是因爲etcd。很不錯的動畫演示【http://thesecretlivesofdata.com/raft/】,另外raft官網現在又很多不同語言的實現,具體請參考【https://raftconsensus.github.io】。

名字服務中心要解決的核心問題

1、我能訪問的服務實例是…

這個問題分解成兩個小問題,第一、服務註冊,對外宣告某服務的實例有哪些?第二、服務發現,我能訪問哪些服務,通過某種方式找到這些服務實例。

2、我最應該訪問誰

同樣分成兩個小問題,第一、我有權限訪問麼?第二、我能訪問的最優服務實例是?

3、當我訪問的服務實例有問題怎麼辦

這是服務的容錯要求,當後端服務實例出現問題的時候,需要有一個機制對問題服務實例進行自動剔除處理,避免人爲干預。

名字服務中心的業界實現

1、SmartStack

SmartStack是Airbnb開源的一個自動註冊和發現的框架,目前已經在github上開源,其核心服務組件:Nerve和Synapse,Nerve,用於服務註冊,Synapse,用於服務發現。其實現非常簡單,並且巧妙的利用了haproxy的代理能力,可以作爲TCP和HTTP服務的代理,架構圖如下:

在這裏插入圖片描述
A、Nerve

負責對後端的服務進行健康監測,並註冊到zk集羣。後端撥測的服務由兩種,一種是http服務、另外一種是TCP服務。http服務採用標準的方法,每個服務都需要暴漏一個/health的協議出來,供Nerve直接調用,返回200則算正常,其他則算異常。TCP協議的服務健康檢查,則可以由研發自定義。撥測通過的服務,則放到zk的節點池中,供後續的服務發現組件使用。

B、Synapse

服務發現組件,從zk集羣中獲取可用服務結果,產生haproxy進程,業務請求每次經過haproxy,此時haproxy做tcp或者http代理進行請求路由。

缺點:

A、撥測靈敏度如何與業務同步。如果以分鐘和秒級的服務要求來看,此時服務的撥測靈敏度則要求非常的高,必然會產生大量的無效請求到後端服務。並且這種粒度還需要隨着業務的要求變化而變化,必然說一個存儲服務,可能靈敏度要求則非常的高。

B、撥測的結果如何真實反應業務結果。業務health檢查正常,並不代表業務正常。業務的正常與否是有業務請求自身決定的。

C、ZK會成爲一個瓶頸。隨着集羣規模越來越大,nerve和synapse都需要zk集羣進行交互,此時zk集羣比如成爲瓶頸。

D、應用application缺少自決策的能力。application的業務訪問結果是最好的決策依據,而該方案恰恰忽略這個數據,把決策依據交給了外圍組件的一些旁路數據。

2、Eureka

Eureka是Netflix開源的一個名字服務架構,定位在中間層服務的負載均衡、容錯服務架構上。因爲Netflix的服務是運行在AWS上,我們都知道公有云沒有這塊的服務能力(公有云提供的都是類LVS的服務)。其架構圖如下:

在這裏插入圖片描述

A、Eureka Server

接受服務的註冊、更新、下線、管理等等。服務上線後通過心跳來維持與server的連接,當心跳丟失,此時自動剔除服務。

B、application Client和application service client

根據Eureka的API要求,自動向Eureka Server完成服務的註冊、上線、下線等生命週期的自管理能力。application Client同時根據獲取到的服務實例,發起遠端請求。服務的註冊是在appliction的代碼中嵌套Eureka的API自動完成。

Eureka只提供服務註冊和服務發現的能力,具體的負載均衡能力是由netflix另外一個開源項目Ribbon完成。Eureka server如果發生異常,此時client啓動自我保護機制,類似使用本地的cache提供服務能力,不影響線上服務運行。

3、Serf

Serf是一個有意思的項目,目的就是打造一個真正的通用性名字服務平臺,從解耦的角度(不含調度能力)來說是完全可以實現的。它從開始的設計目標就提出了去中心化、容錯且必須高可用和輕量化。

當前的Serf最新版本是0.6.4,應該還是一個演進中的項目,感覺國內青雲的公有云P2P服務器故障檢測平臺和這個項目的實現原理類似。該項目完全是基於去中心化協議gossip設計完成,gossip協議目前在mangodb、Cassandra集羣中都得到實際驗證和使用。

Serf會在每個機器上運行一個agent,agent註冊member的可以帶上name,同時也會帶上服務標籤的標識,這是服務的屬性,形如:

在這裏插入圖片描述
同一個agent可以帶上多標籤的標識,標籤內可以包含角色、數據中心、IP和端口等等。接下來gossip協議會負責把這個信息在集羣內進行傳遞。機器故障,自然也是gossip協議自動化剔除處理。具體的gossip協議內容,請自行檢索閱讀。

可以基於Serf的RPC協議標準和格式,封裝一個遠程訪問包,獲取服務實例信息。具體的請參考go語言的實現【https://github.com/hashicorp/serf/blob/master/client/rpc_client.go】。它的api文檔不是太好,有些要去看其cli的實現。

4、Spotify

Spotify 使用的是DNS來做服務發現,而不是引入一個新的技術方案。之前說過DNS實現有優雅的方案,就是說它。它巧妙的使用了DNS中的SRV記錄,你看看DNS中的SRV記錄便知他爲什麼能做到這點,如下:

在這裏插入圖片描述

接下來客戶單只需要使用一些DNS庫進行封裝即可完成服務發現及其後續的調用操作。雖然使用了一個已有的標準化協議,但問題依然不是,這個DNS記錄是一個靜態記錄,只能簡單的根據權重來進行負載均衡。其次DNS始終是個無狀態的服務,後端服務的異常沒法容錯或者負載均衡。

Spotify是完全依賴DNS的一個實現,我們都知道DNS是依賴靜態配置文件,因此算是一個靜態集羣。不過DNS的巧妙應用,也算是其平臺的一大特色。

5、NSQ

NSQ是有Bitly用go語言開發的去中心化、分佈式實時消息隊列。其實NSQ本來不是作爲服務發現和服務註冊系統來設計的,不過Bitly結合自己的業務需求增加了一個組件(Nsqlookupd)使之滿足了服務發現和服務註冊的功能。如果把服務註冊當作消息發佈,把服務發現當做服務訂閱,倒是和消息隊列有相似之處。NSQ由三部分組成:

Nsqd是一個daemon程序,負責接收消息、分發消息給客戶端。

Nsqlookupd是一個Daemon程序,負責管理top視圖信息,並且提供最終一致性的服務發現能力

NsqAdmin是一個webUI的管理端

Nsqlookupd是一個非常重要的程序。客戶端每次想Nsqlookupd發起請求,通過http接口查詢某個主題的Nsqd的生產者,而Nsqd節點負責向其廣播消息topic和消息channel的信息。該系統中有兩類接口:TCP接口被用來接受Nsqd的廣播請求,而http請求負責提供給客戶端做服務發現和管理動作。具體的請參見【http://nsq.io/components/nsqlookupd.html】

6、SkyDNS

SkyDNS是基於RAFT協議構建的一個名字服務,同時提供基於HTTP和DNS協議的訪問呢機制。服務註冊的時候,client端發起HTTP請求把服務以SRV記錄的形式註冊到skyDNS。註冊成功後,服務需要週期性的進行心跳交互,向服務端上報自己的狀態。etcd依然是最底層的服務發現,是有skyDNS和etcd交互獲取服務註冊的結果,然後寫進skyDNS以SRV記錄存在。對於服務發現,此時可以通過DNS協議向服務器組發起訪問,獲取相關的DNS SRV記錄。

SkyDns支持服務的動態註冊,這點和Spotify有所不同,它集成了zk和etcd的動態能力。該服務平臺同時柔和了DNS和RAFT技術來構建服務中心,屬於奇思妙想類。

7、騰訊L5模式

名字有點怪,不知道爲什麼叫L5?(後來求證到是5個9的意思)L5在騰訊某部門大面積的使用。具體的L5架構如下:

在這裏插入圖片描述
L5容錯組件包含DNS Server和L5_Agent兩個部分。

L5_Agent對後臺服務羣提供容錯訪問,並在此基礎上對後臺服務羣進行過載保護,避免後臺服務器因訪問量大,而引發故障.或雪崩效應。CGI或者Server通過API與L5_Agent通信,完成上報、獲取。是個旁路服務組件,業務訪問路徑無需經過L5 agent。

L5_DNS Server根據配置完成IP路由下發,保證所有L5_Agent獲取到最新、最完整的路由信息,在本地有個DNS agent接收,存放共享內存區域,L5agent可以通過共享內存獲取。

L5 agent裏面還有一個policy agent使用時間片內的訪問作爲統計單位,時間片內所有訪問的平均延時、成功率等信息作爲負載和容錯作爲下時間片內請求參照。

A、業務請求路徑

1)app或者cgi向L5 agent發起請求。

2)L5 agent根據之前時間片內的服務訪問(延時、失敗)情況返回最優的後端服務實例。

3)app或者cgi向該實例發起業務請求。

4)請求完成後,app或者cgi把訪問的狀態信息上報給L5_agent

5)L5_agent繼續根據時間算法進行運算確定下次訪問的最優實例。

B、名字服務請求路徑

兩種更新路徑:dns server如果有實例發生變化,此時會觸發下發變更操作;dns agent每次重新啓動,都會和dns server發起請求,確定配置版本號,確認是否需要更新。

8、我們的實踐

後面就開始詳細介紹,姑且用代號“SkyName”來代替,但內部不是這個名字哈。

五、SkyName的設計原則

其實對於大多服務設計來說,所遵循的原則是一致和類似的,比如說高可用、可擴展等等。當然在這種核心服務上,可擴展、高可用性幾乎是一個通用的要求,那麼對於名字服務中心來說,有自己的設計原則,如下:

1、簡單

對於很多業務新架構來說,確保服務接入的可平滑性是一個項目成功的關鍵。在我們名字服務中心這個項目上,也是牢牢的記住了這個準則。這個簡單體現在兩個方面,首先舊業務接入簡單,開發無需修改代碼完成(當然對於你們不是哈);其次新業務接入也非常簡單,只需要按照格式註冊且宣告服務即可,其他就由服務中心統一接管服務的整個生命週期管理。

2、高可用

此時需要兩種方式來確保高可用:

A、去中心化。對於一次正常的業務調用來說,名字服務可以本地緩存的機制,降低對中心節點的依賴,而對於選擇最優的調度節點決策也不應該依賴中心去完成,可以由client自己決策,進一步降低對中心點的依賴。

B,避免人的參與。人是不可靠的,可以看看一個結果【引用論文Recovery Oriented Computing Motivation, Definition】

在這裏插入圖片描述

在重大節假日期間,因封網會禁止變更恰恰是故障的最少時期,這是我們的最直接經驗。另外我們一定有過rm刪錯文件、配置文件修改錯的可怕運維經驗。

3、安全性

傳統的服務之間調用授權都是通過被調用服務端來控制的,此時則暴漏了兩個問題,調用acl權限規則是靜態化,配置文件中,因此它的安全性沒法保證,容易被人獲取信息,比如加密祕鑰;其次靜態的規則,也不利於安全的動態變更,就如操作系統需定時更換root密碼一樣。因此我們對服務的調用准入做了嚴格的控制,服務之間的訪問必須通過授權才能訪問,授權之後的服務,會獲取到被掉用方頒發的一個Access Token,利用這個Token才能進一步訪問,並且這個AccessToken可以任意時間更換。

4、容災容錯

skyName必須能滿足業務的智能調度的需求,在服務出現異常的情況下,調用端能夠自動容錯或啓用過載保護,這符合“前端保護後端”的設計思想。

六、SkyName的功能目標

SkyName在設計之初就設定了它的明確目標,具體分爲四大功能,這四大功能覆蓋了名字服務的全生命週期管理、調用管理、權限管理、服務反饋等等。

1、名字中心

統一接管服務註冊和服務發現,並且是越自動化越好。在第一期我們選擇的還是人工註冊的方式,在第二期我們要實現自動註冊,徹底實現對名字服務的生命週期自動化管理。在我們當前業務中,業務的調用都是http協議,通過正常的DNS接口獲取服務實例,但名字服務上線之後,我們會把DNS+URI映射服務實例集羣上,IP地址和端口對應的服務名後由名字中心提供。

2、調度中心

服務之間的調度必須提供容錯和過載保護的能力,並且這個能力需要獨立於名字服務中心存在,把這個決策能力下放到客戶端自決策。動態請求如果每次都經過服務中心,必然服務中心的壓力非常大。因實現靜態名字服務產生的價值和收益甚小,必須加入負載均衡的能力,並且要確保對研發的程序透明。

3、鑑權中心

我們都知道被調用服務需要加入ACL的權限控制(系統級別、業務級別),確保接口和數據安全。我們這個安全分兩個級別,第一個是機器級別,類似iptables的機制,不過這個IP過濾的能力是在程序級控制的;其次是服務級別,對接口的數據進行對稱加密控制,主被調服務兩側約定加密的方法(MD5、CRC)和祕鑰(類似干擾碼),服務端接收到請求之後,則服務被服務正常接收和處理。

4、服務反饋

所有的服務訪問和調用結構都需要上報到服務中心,供運維監控和運維分析使用。由於接入了統一調度系統,服務在調用流上染色成爲了可能,爲故障定位提供了快捷方便的能力。在宏觀層面上,服務之間的調用情況,需要上報,便於整體把握服務的運行狀況。比如說A機器訪問後端的服務實例延時和錯誤率如何?最後根據這份調用視圖,可以生成詳細的業務拓撲視圖,拓撲視圖的生成可以進一步打通自動化、數據化的運維體系。

七、SkyName的架構

整體架構示意圖如下:
在這裏插入圖片描述

1、架構設計要點

A、application端無agent設計

在application側,我們沒有放置任何agent,完全是通過client api來完成這些服務的註冊、更新和管理。利用一個hash容器存儲最近分鐘之內的請求情況,用於後續的請求分配依據。

B、服務中心服務端無狀態設計,無奇數節點要求,可平行擴展

考慮到名字服務中心的服務量,我們採用了輕量級的方案,採用了類似業務技術架構—前端WEB+緩存+DB。一致性的要求全部由數據庫底層保證。對於DB的高可用,我們使用了另外一套高可用機制,可以做到平滑遷移、升級和容災切換。

C、名字服務集羣、統計分析集羣分離設計

名字服務集羣訪問、讀寫模型都不同於調用統計分析集羣,在業務上做了集羣的分離,確保壓力分散,保證核心服務。

D、業務訪問路徑旁路化設計

業務訪問不經過名字服務中心,獲取服務實例之後,直接向後端服務實例發起請求。由於我們的請求是http協議,使用了一個http柔性調度框架,訪問的結果也是由框架自動收集。業務層面的調用遵循簡單的API要求,和http協議調用一樣簡單,做到徹底的無侵入。

2、調度算法

調度算法是客戶端自決策的算法,具體實現細節如下:

A、服務降級

app client根據錯誤數和錯誤率來進行降權。在實現中是根據配置文件指定的一個閾值,兩個配置項是“或”關係。接口異常分兩種情況:一種是接口訪問延時;另外一種是接口訪問連接不上。所以可以看到,降權的對象是接口,而非機器級別。

失敗和失敗率的檢測一定是週期內的,一般是分鐘級左右。

B、重連機制

在接口被置爲不可用之後,需要有一個方式確定接口已經恢復正常。常見的策略就是週期性檢測(時間策略)和用戶請求抽樣檢測策略。前者可以防止訪問量很小的情況,接口能夠正常恢復。後者是針對大事務請求,抽樣用戶請求去檢測接口,如果檢測接口狀態正常連續達到數量,則恢復接口對外提供服務。

C、算法缺點

接口級服務降級粒度過大,需要做到服務實例+接口級別;動態調度沒有考慮服務實例間的延時因素,從而確定請求基於服務實例間的分發策略,當然服務中心提供了靜態權重(類似wrr),但還是不夠。

八、SkyName的運維收益

1、自動化

後面至少服務的上下線可以自動進行,不需要人工干預。但我一個服務接入到線上服務時候,此時就是把一個程序包部署上去,然後直接調用名字服務中心的接口置爲啓用就可以了。如果經歷過很好的測試,其實和名字服務中心之間的交互都可以不要。

同時服務之間的祕鑰頒發也不依賴配置文件了,工作流程大大簡化,所以在之前我強調了服務中心對運維自動化的簡化能力。

2、架構透明化

所有的服務關係的調用都統一上報到名字服務中心,名字服務中心,可以做拓撲視圖的構建。另外基於http協議,我們可以無侵入的增加http header頭,做服務染色,進一步繪製服務之間的調用關係,最終完成端到端的服務調用關係繪製。

我們都知道,拓撲視圖對於小的架構來說,一般都是手工維護,而對於頻繁變更或者大的服務架構來說,根本就無法維護。而運維的故障分析、變更都都需要參照這個拓撲視圖。

3、服務負載均衡

服務調度通過服務中心提供的api進行,服務負載均衡能力都在底層接管完成。也是根據接口訪問的延時和connection的連接情況來做實時的請求分配,這個和L5的算法類似。提供幾個權衡指標,超時和連接中斷,然後確認失敗率是否超過閾值,過閾值則直接做異常節點處理。

4、運維監控和分析

我們不是爲了酷而去做這個事情,是真正切切的覺得有運維收益。試想一下我們把底層所有的服務調用數據都採集上來了,一則我們可以做服務監控,二則我們可以做服務狀態DashBoard,同時還可以提供給自動化變更服務使用。具體視圖如下:

在這裏插入圖片描述
在視圖中,我們知道每個調用的接口延時和失敗情況,做到真正業務流可視化。

九、SkyName的未來

1、有狀態服務調度

當前我們還不做有狀態服務的調度控制。對於業務架構來說,運維是先希望服務去狀態化,然後再接入服務中心。比如說類似redis和mc服務,我們都希望中間接入proxy代理,把代理作爲服務節點接入到服務中心。但依然還存在一些HASH的分區要求,需要在未來的版本規劃中解決。

2、服務中心SDK化

可以提供更多的sdk給其他業務使用,比如說PHP業務,erlnag和其他非標準JAVA服務框架的服務等等,確保這個名字服務可以成爲跨平臺的標準接入組件。

3、跨IDC的服務調度

當前我們的服務中心是獨立IDC的部署,特別對於雙中心的業務來說服務之間的調度通過調度中心物理部署隔離的方式來實現調度的隔離。其實可以根據一些IDC、機架等就近訪問的規則來設置調度策略,規劃在未來的版本之中。這個方案也類似之前在SkyDNS提到的tag概念。

堅持一個判斷:在一個分佈式系統中,沒有名字服務中心調度實現的架構都不是一個好架構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章