本文內(nèi)容較多,分為如下部分
無狀態(tài)服務(wù)和有狀態(tài)服務(wù)定義
無狀態(tài)服務(wù)應(yīng)用場景
有狀態(tài)服務(wù)應(yīng)用場景
有無狀態(tài)倆種服務(wù)的架構(gòu)質(zhì)量對比
實現(xiàn)有狀態(tài)服務(wù)的挑戰(zhàn)
有狀態(tài)服務(wù)重構(gòu)成無狀態(tài)服務(wù)
警告:任何情況下都優(yōu)先考慮使用無狀態(tài)服務(wù),即使你閱讀本文,包括以后寫的有狀態(tài)服務(wù)實現(xiàn)模式后。
高可用4大原則我自認(rèn)為分別是 端到端[1],有狀態(tài)vs無狀態(tài)[2],可觀測,萬一,其他微服務(wù)框架,容器化我認(rèn)為是次要的。
分布式系統(tǒng)里,節(jié)點服務(wù)包含了業(yè)務(wù)處理和數(shù)據(jù)倆部分, 如果數(shù)據(jù)是從持久化系統(tǒng)加載,則認(rèn)為是此節(jié)點是狀態(tài)的。 如果數(shù)據(jù)常駐于節(jié)點內(nèi),業(yè)務(wù)邏輯是從外部加載進(jìn)來的,則認(rèn)為是有狀態(tài)節(jié)點。
![]()
state_definition
絕大多數(shù)系統(tǒng)都是無狀態(tài)服務(wù),服務(wù)不需要在服務(wù)本地(JVM)記錄同一個客戶端(如用戶,設(shè)備,或者其他作為客戶端的服務(wù)等)請求的的歷史狀態(tài)或者記錄客戶端的信息,服務(wù)需要的這些數(shù)據(jù)將從數(shù)據(jù)庫或者Redis,MongDB加載。這些服務(wù)類似如下
登錄服務(wù),服務(wù)節(jié)點根據(jù)請求用戶名,從數(shù)據(jù)庫查詢用戶名和HASH后的密碼,然后判斷請求的密碼是否與數(shù)據(jù)庫查詢的密碼一致
搜索服務(wù) ,服務(wù)節(jié)點會把查詢條件轉(zhuǎn)為ElasticSearch查詢JSON,請求Elastic Search查詢結(jié)果。
電商中庫存查詢,服務(wù)節(jié)點會根據(jù)商品SKU以及查詢所在的地區(qū),查詢存放在Redis的庫存信息。同時庫存服務(wù)節(jié)點也會請求商品查詢服務(wù)查詢商品的名稱和價格等信息。
物聯(lián)網(wǎng)中查詢家庭設(shè)備的狀態(tài),服務(wù)節(jié)點會查詢倆個Redis,一個是緩存設(shè)備信息的Redis,一個設(shè)備在離線狀態(tài)的Redis
無狀態(tài)服務(wù)的技術(shù)實現(xiàn)非常成熟,如果你的服務(wù)是無狀態(tài),僅僅需要增加節(jié)點既可實現(xiàn)系統(tǒng)高可用和高性能。 如下圖,代理服務(wù)采用輪詢(Round Robin)或則隨機(jī),對于客戶端C,每個服務(wù)節(jié)點都能提供服務(wù)。如果需要擴(kuò)容,簡單增加服務(wù)節(jié)點4即可。
![]()
state_stateless
需要注意的是,隨著流量增加,而服務(wù)節(jié)點增加,導(dǎo)致代理服務(wù)和數(shù)據(jù)庫本身可能是高可用高性能的瓶頸。因此代理服務(wù)和數(shù)據(jù)庫節(jié)點也應(yīng)該支持?jǐn)U展節(jié)點
無狀態(tài)服務(wù)的主要優(yōu)點
優(yōu)點
實現(xiàn)簡單
像編寫CURD代碼那樣簡單
擴(kuò)容簡單
增加節(jié)點即可,代理服務(wù)可以使用輪詢,隨機(jī)等路由技術(shù)實現(xiàn)。
與無狀態(tài)是服務(wù)對應(yīng)的是有狀態(tài)服務(wù),要求服務(wù)端記錄同一個客戶端的交互歷史數(shù)據(jù)和狀態(tài)數(shù)據(jù),有狀態(tài)服務(wù)通常從外部加載執(zhí)邏輯而不是數(shù)據(jù)。比如
用戶前端流程表單在最終提交前保留在服務(wù)端的臨時表單數(shù)據(jù)
用戶登錄后的會話信息放在Session,Session信息存放在JMV內(nèi)存里
物聯(lián)網(wǎng)的接入網(wǎng)關(guān),需要記錄長鏈接到此網(wǎng)關(guān)的設(shè)備的版本信息。網(wǎng)關(guān)服務(wù)有可能加載規(guī)則腳本,規(guī)則引擎會根據(jù)設(shè)備上報的數(shù)據(jù)來執(zhí)行特定的業(yè)務(wù)邏輯
游戲服務(wù),一群用戶會被路由到特定的地圖服務(wù)器上協(xié)作完成游戲
通過互聯(lián)網(wǎng)協(xié)作編寫一份文檔
導(dǎo)航系統(tǒng),用戶的信息和旅程信息都在一個服務(wù)器上。實時導(dǎo)航信息也在這一臺服務(wù)器上更新
Zookeeper 內(nèi)存中保持的有ZTree數(shù)據(jù)結(jié)構(gòu),它通過ZAB協(xié)議保持所有節(jié)點數(shù)據(jù)都一致。
Redis 本身也是有個有狀態(tài)服務(wù)節(jié)點,它支持在內(nèi)存存儲各種數(shù)據(jù)結(jié)構(gòu)以及加載執(zhí)行腳本。它通過從節(jié)點來實現(xiàn)高可用
有狀態(tài)需要客戶端始終連接到同一個服務(wù)節(jié)點(持久化連接),需要客戶端或者代理服務(wù)采取的路由策略保證同一個客戶任何請求都在同一個節(jié)點執(zhí)行,比如來自同一個IP的用戶
![]()
state_statefull.png
有狀態(tài)服務(wù)的主要優(yōu)點如下
優(yōu)點
高性能
主要避免了從數(shù)據(jù)庫加載數(shù)據(jù)的延遲; 也避免了從持久化系統(tǒng)反復(fù)加載數(shù)據(jù),比如節(jié)點1加載數(shù)據(jù)執(zhí)行業(yè)務(wù)邏輯完畢后,當(dāng)請求路由到節(jié)點2的時候,又會在節(jié)點2再次加載同樣數(shù)據(jù)
任意狀態(tài)數(shù)據(jù)結(jié)構(gòu)
不再需要持久化系統(tǒng)保存狀態(tài)數(shù)據(jù),其狀態(tài)不限制于JSON,表數(shù)據(jù)結(jié)構(gòu)。
CAP
分布式常見的AP能得到滿足外,由于數(shù)據(jù)在同一個節(jié)點,其C也能滿足。需要注意,但是如果主節(jié)點宕機(jī)(或者客戶端認(rèn)為C宕機(jī)路由到從節(jié)點),從節(jié)點接管,則C不成立。
避免使用性能不佳的數(shù)據(jù)庫
無狀態(tài)服務(wù)的擴(kuò)容,也會導(dǎo)致其用的數(shù)據(jù)庫節(jié)點擴(kuò)容。無狀態(tài)節(jié)點因為不需要數(shù)據(jù)庫,則省去了數(shù)據(jù)庫的使用成本,以及其導(dǎo)致的性能瓶頸
有狀態(tài)服務(wù)不難實現(xiàn),但要達(dá)成高可用目標(biāo),則難得多。 下圖是一個通常有狀態(tài)服務(wù)高可用的部署架構(gòu)。
![]()
state_statefull_slave
這個架構(gòu)里,有非常多的技術(shù)挑戰(zhàn)性,列表如下。 這些挑戰(zhàn)是分布式系統(tǒng)高可用的深水區(qū),后面章節(jié)簡述如何攻克這些挑戰(zhàn)。
難點
舉例
持久連接
代理服務(wù)采用何種策略讓客戶端保持持久連接到同一個節(jié)點,包括客戶端的每次請求,客戶端重啟,服務(wù)節(jié)點重啟,以及擴(kuò)容縮容后通知客戶端重連新節(jié)點。如果沒有代理服務(wù),則需要客戶端實現(xiàn)持久化連接
設(shè)備總是鏈接到同一個網(wǎng)關(guān),游戲玩家總是鏈接到同一個區(qū)域服務(wù)器
復(fù)制
為節(jié)點增加一個從節(jié)點,數(shù)據(jù)從主節(jié)點備份到從節(jié)點。當(dāng)節(jié)點宕機(jī),,從節(jié)點接替主節(jié)點工作。從節(jié)點也可收接收只讀服務(wù)請求,緩解主節(jié)點負(fù)載
Redis主從數(shù)據(jù)復(fù)制,Zk的主從數(shù)據(jù)復(fù)制
讀寫一致
如果從節(jié)點提供讀服務(wù),如何保證客戶端讀到最新寫到此分區(qū)的數(shù)據(jù)。
阿里云Redis可以配置只有主分片提供服務(wù)以避免讀寫不一致。
分區(qū)
把有狀態(tài)服務(wù)分成多個節(jié)點,解決有狀態(tài)服務(wù)的存儲瓶頸和訪問性能。
物聯(lián)網(wǎng)網(wǎng)關(guān)集群,Redis數(shù)據(jù)分片,數(shù)據(jù)庫分區(qū),Kafka分區(qū)等
分區(qū)再平衡
在擴(kuò)容時候,如果不差錢,每個分區(qū)都再次拆分2個分區(qū),此分區(qū)數(shù)據(jù)只需要復(fù)制到倆分區(qū)。這種擴(kuò)容成本極高但數(shù)據(jù)均勻。如果擴(kuò)容時候是增加少量節(jié)點。則因為持久化連接緣故,新增節(jié)點負(fù)載長期都較低。擴(kuò)容效果不能立即顯現(xiàn)出來。
阿里云的Redis擴(kuò)容采用的是成倍增長,支持4,8,16....2048個分片。不支持增加少量分區(qū)
分布式共識
數(shù)據(jù)備份給多個節(jié)點,當(dāng)主節(jié)點掛掉,采用那個節(jié)點的數(shù)據(jù)為主節(jié)點。另外,新當(dāng)選的的主節(jié)點需要繼續(xù)同步數(shù)據(jù)到從節(jié)點
ZK通過ZAB實現(xiàn)分布式共識
集群管理
需要感知整個集群的節(jié)點狀態(tài),其主從的工作狀態(tài)。代理服務(wù)需要獲取這些數(shù)據(jù)以實現(xiàn)路由
物聯(lián)網(wǎng)設(shè)備的主網(wǎng)關(guān),Redis集群的代理
業(yè)務(wù)邏輯加載
如果有狀態(tài)服務(wù)的業(yè)務(wù)邏輯通過外部加載實現(xiàn),如果管理這些業(yè)務(wù)邏輯
物聯(lián)網(wǎng)的規(guī)則鏈,通過配置其規(guī)則節(jié)點和執(zhí)行邏輯實現(xiàn)設(shè)備在離線,屬性上報等執(zhí)行邏輯。Reids作為有狀態(tài)節(jié)點,可以加載和執(zhí)行Lua腳本
基于無狀態(tài)服務(wù)和有狀態(tài)服務(wù)各自的優(yōu)缺點,下表總結(jié)了倆種服務(wù)的架構(gòu)質(zhì)量
架構(gòu)特點
關(guān)注點
無狀態(tài)服務(wù)
有狀態(tài)服務(wù)
高可用
API網(wǎng)關(guān)
API網(wǎng)關(guān)可以實現(xiàn)各種負(fù)載均衡策略。如隨機(jī),輪詢,負(fù)載
API網(wǎng)關(guān)實現(xiàn)較為復(fù)雜,需要識別客戶端,保證每次路由到同一個有狀態(tài)服務(wù),依據(jù)信息可能是客戶端IP和端口,也可能是HTTP頭中包含的客戶ID
故障恢復(fù)
重啟服務(wù)即可恢復(fù)故障
有狀態(tài)服務(wù)重啟后,狀態(tài)丟失。需要重新建立這些狀態(tài),如設(shè)備的版本信息,用戶的購物車。因此故障恢復(fù)較慢這些狀態(tài)需要從持久化系統(tǒng)加載,或者依賴客戶端重置狀態(tài)。
故障容錯
無狀態(tài)服務(wù)支持重試,當(dāng)服務(wù)宕機(jī),API網(wǎng)關(guān)可以將請求路由到另外其他服務(wù)。不存在單機(jī)故障
有狀態(tài)服務(wù)宕機(jī),狀態(tài)丟失。需要在其他節(jié)點重建狀態(tài),比如有狀態(tài)服務(wù)保持的有用戶登錄信息,當(dāng)宕機(jī)后,用戶再訪問其他服務(wù)前,需要再次登錄。
高性能
啟動性能
無狀態(tài)服務(wù)不需要加載狀態(tài),因此重啟后訪問較快。
有狀態(tài)服務(wù),服務(wù)重啟后,需要初始化端狀態(tài),訪問性能第一次較慢。重啟可能造成服務(wù)響應(yīng)延遲
訪問性能
無狀態(tài)服務(wù)通常將狀態(tài)維護(hù)在Redis中,性能稍微慢一些,但考慮到Redis這些延遲都是毫秒級的,整體性能很高。如果狀態(tài)數(shù)據(jù)維護(hù)在傳統(tǒng)數(shù)據(jù),則延遲較高
有狀態(tài)服務(wù)在JVM內(nèi)存在保持狀態(tài),訪問速度更快。如游戲地圖,所有玩家都在一個游戲地圖(JVM)里有狀態(tài)服務(wù)適合游戲,導(dǎo)航等場景。
可修改
易于修改
通常無狀態(tài)服務(wù),不需要在JVM里維護(hù)狀態(tài),實現(xiàn)更簡單,易于修改。
在JVM里維護(hù)的狀態(tài),數(shù)據(jù)結(jié)構(gòu)私有,難以修改,另外涉及到并發(fā)訪問,編寫代碼容易出錯
熱發(fā)布
無狀態(tài)服務(wù)可以采用任何熱發(fā)布技術(shù),而沒有風(fēng)險
有狀態(tài)服務(wù),需要把狀態(tài)數(shù)據(jù)遷移到熱發(fā)布后的結(jié)構(gòu),難度較大
可伸縮性
節(jié)點擴(kuò)容
集群環(huán)境,無狀態(tài)服務(wù)可以任意增加或者減少,無狀態(tài)服務(wù)擴(kuò)容后,API網(wǎng)關(guān)可以通過負(fù)載均衡策略,讓負(fù)載少的這些擴(kuò)容節(jié)點能很快達(dá)到滿負(fù)荷
容易造成單點過載。有狀態(tài)服務(wù)擴(kuò)容后,由于持久化鏈接需要,至少少量請求能立即路由到新的節(jié)點。擴(kuò)容后需要較長時間新老節(jié)點的容量才能達(dá)到一個均衡值。
既然無狀態(tài)服務(wù)有如此多的優(yōu)點,除非有高性能要求,架構(gòu)中應(yīng)該優(yōu)先使用無狀態(tài)服務(wù),如果是有狀態(tài)的服務(wù),需要改成無狀態(tài)服務(wù),這里有4個辦法
有狀態(tài)服務(wù)的狀態(tài)數(shù)據(jù)存放在Redis等更為可靠的存儲介質(zhì)。比如用戶Session,訂單,購物車等信息存放到redis,一些長期存在的數(shù)據(jù)存放到數(shù)據(jù)庫。 一些高可用基礎(chǔ)設(shè)施的改進(jìn)采用了此方案,比如Kafka新一代方案 Diskless Kafka [3] 的實現(xiàn)AutoMQ ,消息本地存儲變成存儲到對象存儲服務(wù)里。 Nacos將配置數(shù)據(jù)放到Mysql數(shù)據(jù)庫中。
服務(wù)端的狀態(tài)每次都回傳給客戶端,客戶端下次調(diào)用攜帶這些狀態(tài),比如JWT,Cookie
有狀態(tài)服務(wù)把有狀態(tài)部分單獨隔離出來,把其他部分放在無狀態(tài)服務(wù)里。
使用Zookeeper,數(shù)據(jù)庫等強(qiáng)一致工具來實現(xiàn)投票,元數(shù)據(jù)管理,二階段提交等,而無需自己實現(xiàn)
下圖是Spring Boot提供的Session實現(xiàn)方式,代替?zhèn)鹘y(tǒng)的保存會話到內(nèi)存,Spring Boot 配置spring.session.store-type
spring.session.store-type=Redis配置后,存在內(nèi)存中的的用戶會話數(shù)據(jù)將序列化后存放在redis中。
![]()
state_springsession
其他配置還允許使用數(shù)據(jù)庫,Hazelcast等 存儲系統(tǒng)。需要注意,必須確保存儲系統(tǒng)的高性能和高可用。在我的一個項目里,用此方案把有狀態(tài)服務(wù)改成無狀態(tài)服務(wù),額外引入了一個512分片,容量是1024G的Redis集群以避免系統(tǒng)性能問題。
另外一種把有狀態(tài)服務(wù)改成無狀態(tài)服務(wù)的方法是服務(wù)器每次把狀態(tài)回傳給客戶端。適合用戶狀態(tài)數(shù)據(jù)較少情況。
![]()
state_request
交互過程如下
服務(wù)在接受客戶端請求后,將數(shù)據(jù)放回到Cookie中,如加密的用戶的信息,或者訂單信息
客戶端下次請求,服務(wù)端從Cookie中取出的狀態(tài)數(shù)據(jù),處理請求后新的狀態(tài)數(shù)據(jù)再次保存到Cookie中
在還沒有無紙辦公時代,我在派出所辦理業(yè)務(wù)的時候,按照要求需要跑多次派出所和其他相關(guān)單位,派出所工作人員會把需要跑的單位記錄在一張紙上,每次交互后的結(jié)果和剩下需要辦的事情都有記錄,再次去派出所,其他工作人員即使沒有給我辦理過業(yè)務(wù),也會讓我拿出這張紙查看,以了解我辦理的進(jìn)度
第三種是在設(shè)計有狀態(tài)服務(wù)時候,拆分有狀態(tài)服務(wù)。這樣好處是讓大部分功能保持在無狀態(tài)服務(wù)里。
![]()
state_split
第四種方法與第三種類似,使用zookeeper來管理有狀態(tài),相比于自己實現(xiàn)有狀態(tài)服務(wù),zookeeper/etcd這些基礎(chǔ)中間件更為可靠。
![]()
有狀態(tài)服務(wù)的高可用涉技術(shù)實現(xiàn)包括大量內(nèi)容,本書將用后續(xù)一節(jié)內(nèi)容說明實現(xiàn)有狀態(tài)服務(wù)的高可用有哪些模式,再次警告,有狀態(tài)服務(wù)高可用實現(xiàn)難度較大。類似你正在實現(xiàn)一個Redis,Kafka這樣的中間件。你需要承擔(dān)為了性能引入的復(fù)雜性。
參考資料
端到端: https://my.oschina.net/u/567839/blog/19641027
有狀態(tài)vs無狀態(tài): https://my.oschina.net/u/567839/blog/19658871
Diskless Kafka: https://www.toutiao.com/article/7580613850573636096/
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.