如何設(shè)計(jì)一個(gè)無(wú)狀態(tài)應(yīng)用
概述
無(wú)狀態(tài)應(yīng)用 (服務(wù)) 是一種微服務(wù)架構(gòu)設(shè)計(jì)原則,通過(guò)將服務(wù)的業(yè)務(wù)邏輯與狀態(tài)數(shù)據(jù)進(jìn)行分離來(lái)提高應(yīng)用本身的擴(kuò)展性,保證應(yīng)用以更可靠的方式運(yùn)行,同時(shí)降低了業(yè)務(wù)功能實(shí)現(xiàn)復(fù)雜度。
狀態(tài)隔離
保持服務(wù)內(nèi)部邏輯簡(jiǎn)單 (例如只做計(jì)算并返回結(jié)果數(shù)據(jù)),將服務(wù)中和狀態(tài)相關(guān)的數(shù)據(jù)保存到服務(wù)的實(shí)例之外 (例如用戶(hù)購(gòu)物車(chē)),這樣每個(gè)服務(wù)實(shí)例都可以處理所有的請(qǐng)求。
不可變對(duì)象
使用不可變的數(shù)據(jù)結(jié)構(gòu)來(lái)表達(dá)語(yǔ)義,確保請(qǐng)求中的相關(guān)數(shù)據(jù)不會(huì)發(fā)生變化且全局唯一。
常見(jiàn)的不可變對(duì)象:
-
RequestId: 請(qǐng)求 ID -
EventId: 事件 ID -
TransactionId: 訂單 ID -
LogId: 日志 ID -
TraceId: 鏈路 ID

Auth
使用基于令牌 Token 的身份驗(yàn)證機(jī)制 (例如 JWT),這樣每個(gè)服務(wù)都可以驗(yàn)證用戶(hù)的單個(gè)請(qǐng)求,不需要維護(hù)用戶(hù)相關(guān)狀態(tài)信息。

水平伸縮
將應(yīng)用設(shè)計(jì)為可以水平伸縮,每個(gè)實(shí)例可以獨(dú)立運(yùn)行并接收處理請(qǐng)求,并且各實(shí)例之間沒(méi)有依賴(lài)的共享資源或全局會(huì)話(huà) (例如傳統(tǒng)的 Session 機(jī)制)。對(duì)于緩存數(shù)據(jù),可以有專(zhuān)用的集群供單個(gè)服務(wù)組中所有實(shí)例使用,對(duì)于緩存中的熱點(diǎn)數(shù)據(jù),每個(gè)實(shí)例可以單獨(dú)在本地存儲(chǔ)一份。
冪等
確保服務(wù) API 操作是冪等的,這樣調(diào)用方因?yàn)榫W(wǎng)絡(luò)問(wèn)題請(qǐng)求失敗時(shí),可以直接重新發(fā)起請(qǐng)求。
常見(jiàn)的保證冪等性的方法:
-
唯一的 RequestId (請(qǐng)求 ID): 每個(gè)請(qǐng)求都應(yīng)該有一個(gè)唯一的請(qǐng)求 ID, 在發(fā)起重復(fù)請(qǐng)求的時(shí)候,服務(wù)方可以根據(jù)該標(biāo)志符確認(rèn)操作是否已經(jīng)完成 -
操作事務(wù)性執(zhí)行: 將多個(gè)操作組合成事務(wù),如果其中的某個(gè)操作在事務(wù)中執(zhí)行失敗,可以使用回滾機(jī)制 -
樂(lè)觀鎖并發(fā)控制: 調(diào)用方請(qǐng)求更新資源時(shí)必須提供類(lèi)似 “版本號(hào)機(jī)制” 中的版本號(hào)字段,服務(wù)方只有在版本號(hào)匹配時(shí)才會(huì)執(zhí)行操作 -
操作日志: 將已經(jīng)處理的請(qǐng)求和操作寫(xiě)入日志,重復(fù)請(qǐng)求時(shí)可以查找以避免重復(fù)處理
業(yè)務(wù)組件與代碼分離
將服務(wù)的基礎(chǔ)組件 (例如日志、監(jiān)控、鏈路追蹤) 與服務(wù)實(shí)例進(jìn)行分離,例如使用 Prometheus 監(jiān)控整個(gè)集群中的所有服務(wù)。

基于消息的通信
使用消息隊(duì)列作為服務(wù)間的事件通知方式,解耦服務(wù)間的耦合并使服務(wù)間通信變?yōu)楫惒椒绞健?/p>
配置和代碼分離
將服務(wù)的代碼和配置進(jìn)行分離,這樣可以分別進(jìn)行管理,服務(wù)內(nèi)部可以定時(shí)拉取配置實(shí)現(xiàn)熱更新,配置數(shù)據(jù)修改后不需要重新部署服務(wù)。常見(jiàn)的實(shí)現(xiàn)方案為建立專(zhuān)用的服務(wù)配置中心 (如 etcd),或者使用 Kubernetes 中的 ConfigMap 實(shí)現(xiàn)熱更新[1]。
靜態(tài)資源分離
將服務(wù)中所有靜態(tài)資源直接通過(guò) CDN 進(jìn)行分發(fā)。
Kubernetes 中的無(wú)狀態(tài)
Kubernetes 推崇盡可能將服務(wù)應(yīng)該盡可能設(shè)計(jì)為無(wú)狀態(tài),因?yàn)?
-
容器重啟后數(shù)據(jù)會(huì)丟失 -
調(diào)度會(huì)引發(fā) Pod 的 IP 和主機(jī)名發(fā)生變化 -
節(jié)點(diǎn)故障后,該節(jié)點(diǎn)上面所有的 Pod 的數(shù)據(jù)都會(huì)丟失 -
等等 ...
Kubernetes 官網(wǎng)提供了一個(gè) 無(wú)狀態(tài)的留言板示例應(yīng)用[2]。
擴(kuò)展閱讀
-
Stateful and Stateless Applications and its Best Practices[3] -
Stateful vs. Stateless Web App Design[4] -
Cloud Native Application Architecture[5]
鏈接
實(shí)現(xiàn)熱更新: https://dbwu.tech/posts/k8s/best_practice/base/
[2]無(wú)狀態(tài)的留言板示例應(yīng)用: https://kubernetes.io/zh-cn/docs/tutorials/stateless-application/guestbook/
[3]Stateful and Stateless Applications and its Best Practices: https://www.xenonstack.com/insights/stateful-and-stateless-applications
[4]Stateful vs. Stateless Web App Design: https://blog.dreamfactory.com/stateful-vs-stateless-web-app-design/
[5]Cloud Native Application Architecture: https://medium.com/walmartglobaltech/cloud-native-application-architecture-a84ddf378f82
