死生之地不可不察:論API標準化對Dapr的重要性

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 作爲新興的雲原生項目,以\"應用運行時\"之名圍繞雲原生應用的各種分佈式需求,致力於打造一個通用而可移植的抽象能力層。這個願景有着令人興奮而嚮往的美好前景,然而事情往往沒這麼簡單,API 的標準化之路異常的艱辛而痛苦,Dapr 的分佈式能力抽象在實踐中會遇到各種挑戰和困擾。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"快速回顧:什麼是 Dapr?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 的全稱是 “Distributed Application Runtime”,即 “分佈式應用運行時”, 是一個由微軟發起的開源項目,目前已經進入 CNCF 孵化器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們首先來對 Dapr 進行一個簡單的介紹。首先來看 ServiceMesh,和傳統 RPC 框架相比,ServiceMesh 的創新之處在於引入了 Sidecar 模式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/4b\/4bc75cd1f930a154fb938a7fde1524a6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 ServiceMesh 只解決了服務間通訊的需求,而現實中的分佈式應用存在更多的需求,Multi-Runtime 理論將這些需求歸結爲四大類:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/90\/904127e550c1571f3bbb0b4ec917da27.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ServiceMesh 初步成功並且 Sidecar 模式被雲原生社區普遍接受之後,各企業效仿 ServiceMesh 將應用需要的其他分佈式能力外移到各種 Runtime,這逐漸演變成了一個趨勢。這些 Runtime 會逐漸整合,最後和應用 Runtime 共同組成微服務,形成所謂的 “Multi-Runtime” 架構,又稱 Mecha:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/e4\/e49b1289f9416b7d8052c164ed8d6817.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 項目是目前業界第一個 Multi-Runtime  \/ Mecha 實踐項目,下圖來自 Dapr 官方,比較完善的概括了 Dapr 的能力和層次架構:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/93\/93ccecd9a241b5c6921b988b18f0362e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"本質差異:Dapr vs ServiceMesh"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在快速回顧完 Dapr 之後,我們來正式展開本文的內容。首先需要回答這樣一個問題:"},{"type":"text","marks":[{"type":"strong"}],"text":"Dapr 和 ServiceMesh 有什麼區別?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個問題也是大多數讀者在瞭解 Dapr 之後最常問到的一個問題,原因是 Dapr 和 ServiceMesh 在架構上實在太像了,都是以 Sidecar 模式爲核心。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/4b\/4bc75cd1f930a154fb938a7fde1524a6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 的基本思路也是和 ServiceMesh 保持一致:通過引入 sidecar 來實現 "},{"type":"text","marks":[{"type":"strong"}],"text":"關注點分離 + 獨立維護。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/4c\/4c0c8e0a7a49ea6b29032ae84e045be9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 和 ServiceMesh 最明顯的不同之處是 "},{"type":"text","marks":[{"type":"strong"}],"text":"Dapr 的場景比 ServiceMesh 要複雜:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ServiceMesh 關注於服務間通訊,即下圖中虛線部分;而 Dapr 除了服務間通訊之外,還適用於諸多其他的場景,如狀態存儲、消息的發佈訂閱、資源綁定、配置等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/7f\/7f8da002657441f29841389315a131a5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖是 Dapr 目前已有的構建塊,以及對他們所提供的能力的簡單描述(其中 Service Invocation 對應 ServiceMesh 的服務間通訊能力):"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/6b\/6b7c0da37002d031e42063b9062caaf9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"功能的差異是很直白的,容易理解。但是,如果只是功能上的差異,那麼問題來了:"},{"type":"text","marks":[{"type":"strong"}],"text":"如果 ServiceMesh 也提供同樣的能力,是不是就和 Dapr 一樣了?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看 Envoy 的功能,Envoy 原生支持 HTTP1.1\/REST 和 HTTP2 \/ gRPC 協議,社區增加了對 Dubbo、Thrift 等 RPC 協議的支持,而 Envoy 也在提供對 Kafka、Redis 等原生協議的支持,未來 Envoy 提供更多原生協議支持也是可以預期的。那是不是意味着隨着功能的增加,以 Envoy、Istio 爲代表的 ServiceMesh 就和 Dapr 一樣了呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案當然是否定的—— Dapr 和 ServiceMesh 的本質差異在於其"},{"type":"text","marks":[{"type":"strong"}],"text":"工作模式"},{"type":"text","text":"。ServiceMesh 的工作模式是"},{"type":"text","marks":[{"type":"strong"}],"text":"原協議轉發"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/df\/df1a3e3a6f8679b7cb92e685591cc184.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ServiceMesh 的 Sidecar 接收到是 App 發出的原生協議的請求,然後轉發給另一端的 Sidecar 進而轉發給目標 App;或者,對於 Redis\/Kafka 等的支持是 Sidecar 將原協議轉發給 Redis\/Kafka 服務器。在這個過程中,ServiceMesh 的 Sidecar 原則上不修改協議,只做"},{"type":"text","marks":[{"type":"strong"}],"text":"轉發"},{"type":"text","text":"(“Forwarding”),Sidecar 扮演的是"},{"type":"text","marks":[{"type":"strong"}],"text":"代理"},{"type":"text","text":"(“Proxy”)的角色。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Dapr 的工作模式是"},{"type":"text","marks":[{"type":"strong"}],"text":"能力抽象"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/b6\/b6c4a904f300cb3b14ec6bd4dc73e9eb.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr Sidecar 接收到是 App 發出的遵從標準 API 的請求,這些標準 API 對能力進行了抽象;對於服務間通訊的場景,Dapr Sidecar 會將請求轉發給另一端的 Dapr Sidecar;對於服務間通訊之外的能力的支持則需要將標準 API 轉換爲底層組件對應的原生協議。在這個過程中,Dapr Sidecar 原則上對應用只暴露抽象之後的分佈式能力,屏蔽了底層具體的實現和通訊協議,不做\"轉發\"而是提供\"能力\",Dapr Sidecar 扮演的是"},{"type":"text","marks":[{"type":"strong"}],"text":"運行時"},{"type":"text","text":" (“Runtime”) 的角色。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作模式不同的背後,反映出 Dapr 和 ServiceMesh 在設計目標上的差異:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ServiceMesh 的主要設計目標是 "},{"type":"text","marks":[{"type":"strong"}],"text":"低侵入"},{"type":"text","text":",採用原協議轉發的方式可以儘可能的降低對應用的侵入,爲了達到無侵入的目標甚至採用了流量劫持的方式。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 的主要設計目標是 "},{"type":"text","marks":[{"type":"strong"}],"text":"可移植性"},{"type":"text","text":",即在跨雲跨平臺的前提下實現無廠商綁定,採用的方式是在將分佈式能力抽象爲標準 API,並在各種開源項目和雲平臺上提供這套標準 API 的不同實現,從而達到在不同平臺上運行的目標,這即 Dapr 願景中的 “"},{"type":"text","marks":[{"type":"strong"}],"text":"anywhere"},{"type":"text","text":"”。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結一下 Dapr 和 ServiceMesh 的差異以及適用場景:ServiceMesh 強調對應用無侵入,支持原協議轉發和流量劫持,不僅僅適用於新應用,相比 Dapr 也更加適用於老應用。而 Dapr 提供 “標準 API”、“語言 SDK” 和 “Runtime”,需要應用進行適配(這意味着老應用需要進行改造),侵入性比較大。因此 Dapr 更適合新應用開發 (所謂 Green Field),對於現有的老應用(所謂 Brown Field)則需要付出較高的改造代價。但在付出這些代價之後,Dapr 就可以提供跨雲跨平臺的可移植性,這是 Dapr 的核心價值之一。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,在決策是否該採用 Dapr 時,可移植性是一個非常關鍵的考慮因素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"死生之地:API 標準化的價值"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章 Dapr v1.0 展望:從 ServiceMesh 到雲原生  中,我曾經指出:Dapr 的本質是面向雲原生應用的 "},{"type":"text","marks":[{"type":"strong"}],"text":"分佈式能力抽象層"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/59\/59cb81cd6c00ecabad0facd6e3589c9e.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"可移植性"},{"type":"text","text":" 是 Dapr 的重要目標和核心價值。Dapr 的願景, “any language, any framework, "},{"type":"text","marks":[{"type":"strong"}],"text":"anywhere"},{"type":"text","text":"”,這裏的 anywhere 包括公有云、私有云、混合雲和邊緣網絡。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Dapr 可移植性的基石在於 "},{"type":"text","marks":[{"type":"strong"}],"text":"標準 API + 可拔插可替換的組件"},{"type":"text","text":",下面這張來自 Dapr 官方網站的圖片非常形象的展示了 Dapr 的這一特性:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/77\/772449a23a0fca0d168abadc8c2038d5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從架構設計的角度看,Dapr 的精髓在於:通過"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象 \/ 隔離 \/ 可替換,解耦能力和實現"},{"type":"text","text":",從而實現可移植性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/b8\/b864a95b292a37404cc8345edf4d7405.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在傳統的應用開發方式中,應用需要面向具體的實現編程,即當應用需要使用到某個能力時,就需要找到能提供該能力的底層組件,如上圖中的 Redis \/ Consul \/ Memcached \/ Zookeeper 都可以提供分佈式狀態的存儲能力。應用在選擇具體組件之後,就需要針對該組件進行編程開發,典型如引入該組件的客戶端 SDK,然後基於這些 SDK 實現需要的分佈式能力,如緩存、狀態、鎖、消息通訊等具體功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而在 Dapr 中,Dapr 倡導 “"},{"type":"text","marks":[{"type":"strong"}],"text":"面向能力編程"},{"type":"text","text":"”:Dapr API 提供了對分佈式能力的"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象"},{"type":"text","text":",並提取爲標準 API;Dapr 的 Runtime "},{"type":"text","marks":[{"type":"strong"}],"text":"隔離"},{"type":"text","text":"應用和底層組件的具體實現 ; 而這些組件都是"},{"type":"text","marks":[{"type":"strong"}],"text":"可替換"},{"type":"text","text":"的,可以在運行時才進行綁定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 通過這樣的方式,實現了能力和實現的解耦,並給出了一個美好的願景:在有一個業界普遍認可並遵循的標準化 API 的基礎上,用戶可以自由選擇編程語言開發雲原生,這些雲原生可以在不同的平臺上運行,不被廠商和平臺限制——終極目標是使得雲原生應用真正具備跨雲跨平臺的可移植性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理想很美好,但現實依然殘酷:和 ServiceMesh 相比,Dapr 在落地時存在一個無可迴避的問題——應用改造是有成本的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/bb\/bbd7f72bef6ceebf7ea32f7c78db6d8e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從落地的角度來看, ServiceMesh 的低侵入性使得應用在遷移到 ServiceMesh 時無需太大的改動,只需要像往常一樣向 Sidecar 發出原生協議的請求即可,甚至在流量劫持的幫助下可以做到應用完全無感知。從工作模式上說,基於原生協議轉發的 ServiceMesh 天然對舊有應用友好。而 Dapr 出於對可移植性目標的追求,需要爲應用提供一個標準的分佈式能力抽象層來屏蔽底層分佈式能力的具體實現方式,應用需要基於這個抽象層進行開發,才能獲得跨雲跨平臺無廠商綁定等可移植性方面的收益。因此,在 Dapr 落地過程中,新應用需要基於 Dapr API 全新開發,老應用則不可避免的需要進行改造以對接 Dapr API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API 標準化是 Dapr 成敗的關鍵,爲 Dapr 的發展建立起良性循環:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/73\/73a0dd180567883ff4049157250ba41c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"API 標準化:定義 Dapr API,對某一個分佈式能力進行良好的抽象,覆蓋日常使用的大部分場景,滿足應用的常見需求。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"提供組件支持:基於標準 Dapr API,爲開源產品和公有云商業產品提供支持組件,覆蓋主流產品和廠商。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"具備可移植性:基於標準 Dapr API 開發的應用,可以在主流開源產品和公有云商業產品之間自行選擇適合的組件,不受平臺和廠商的限制。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"API 得到更多認可:可移植性爲 Dapr 構建核心價值,Dapr API 得到更多的認可,逐漸成爲業界的事實標準。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"更廣泛的組件支持:Dapr API 越接近業界標準,就會有越多的產品和廠商願意提供支持 Dapr API 的組件。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"可移植性更強越來越多的組件支持,可以覆蓋更多的開源產品和廠商,從而更接近 anywhere 的願景。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理想情況下,“標準化” \/ “組件支持” \/ “可移植性” 之間的相互促進和支撐將成爲 Dapr 發展源源不斷的動力。反之,如果 API 標準化出現問題,則組件的支持必然受影響,大大削弱可移植性,Dapr 存在的核心價值將受到強烈挑戰。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"左右爲難:取捨之間何去何從"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然 API 標準化如此重要,那 Dapr 該如何去定義 API 並推動其標準化呢?我們以 Dapr State API 爲例,介紹在 API 定義和標準化過程中常見的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"State API 的基本定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"State 形式上是 key-value 存儲,即狀態信息被序列化爲 byte[] 然後以 value 的形式存儲並關聯到 key,當然實踐中非 kv 存儲也可以實現 State 的功能,比如 mysql 等關係型數據庫。Dapr 的 State API 的定義非常簡單明瞭,除了基於 key 的 CRUD 基本操作外,還有 CRUD 的批量操作,以及一個原子執行多個操作的事務操作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"css"},"content":[{"type":"text","text":"rpc GetState(GetStateRequest) returns (GetStateResponse) {}\nrpc GetBulkState(GetBulkStateRequest) returns (GetBulkStateResponse) {}\nrpc SaveState(SaveStateRequest) returns (google.protobuf.Empty) {}\nrpc DeleteState(DeleteStateRequest) returns (google.protobuf.Empty) {}\nrpc DeleteBulkState(DeleteBulkStateRequest) returns (google.protobuf.Empty) {}\nrpc ExecuteStateTransaction(ExecuteStateTransactionRequest) returns (google.protobuf.Empty) {}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述 API 定義貌似非常簡單,畢竟 kv 基本操作的語義非常容易理解。但是,一旦各種高級特性陸續加入之後,API 就會逐漸複雜:數據一致性 \/ 併發保護 \/ 過期時間 \/ 批量操作等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"State API 的高級特性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以 GetState() 爲例,我們展開 GetStateRequest 和 GetStateResponse 這兩個消息的定義,瞭解一下數據一致性 \/ 併發保護這兩個高級特性:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/8e\/8e2ef9d398900fb3df32a34c703ba868.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GetStateRequest 中的字段 key 和 GetStateResponse 中以 bytes[] 格式定義的字段 data,對應於 key-value 中的 key 和 value。語義非常明顯:請求中給出 key,在應答中返回對應的 value。除此之外,在 API 的設計中還有三個字段:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.請求中的 consistency 字段用於數據一致性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/70\/70ef9a2dcccabce31f99bcc90e7e25b7.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當組件支持多副本時,consistency 字段將用於指定對數據一致性的要求,其取值有兩種:eventual:(最終一致性)和 strong:(強一致性)。除了 getState() 方法外,這個參數也適用於 saveState() 和 deleteState() 方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.應答中的 etag 字段用於併發,實現樂觀鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/52\/52304f83a82b0d2c3b138820d327eeb5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"樂觀鎖的工作原理如上圖所示,假定有三個請求同時查詢同一個 key,三個應答中都會返回當前 key 的 etag(值爲\"10\")。當這三個線程同時進行併發修改時,在 saveState() 的請求中需要設置之前獲取到的 etag,第一個 save 請求將被接受然後對應 key 的 etag 將修改爲\"11\",而後續的兩個 save 請求會因爲 etag 不匹配而被拒絕。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"etag 參數在 getState() 方法中返回,在 saveState() 方法中設置,每次對 key 進行寫操作都要求必須修改 etag。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"concurrency 參數在 saveState() 方法中設置,有兩個值可選:first_write(啓用樂觀鎖) 和 last_write(無樂觀鎖,簡單覆蓋)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.請求和應答中都有的 metadata"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類型定義爲 map,可以方便的傳遞未在 API 中定義的參數,爲 API 提供擴展性:即提供實現個性化功能(而不是通用功能)的擴展途徑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"對批量操作的處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"State API 提供的批量操作,用於一次性操作多個 key,和應用多次調用單個操作的 API 相比,減少了多次往返的性能開銷和延遲。考慮到組件原生對批量操作的支持程度,Dapr 中的批量操作的實現方式有兩種:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/1a\/1a136c0cf41587c9b4d29e31b1fd935b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"原生支持批量操作:Dapr 組件將多個 key 一起打包提交給組件的後端實現,此時批量操作的實現由後端完成,Dapr 只是簡單轉發了多個 key。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"原生不支持批量操作:Dapr 組件將多次調用組件的後端實現,此時批量操作的實現由 Dapr 組件完成。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們展開細節看一下 Dapr 中 GetBulkState() 方法的代碼實現,忽略細節代碼和加密處理,只看主體邏輯:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequest) (*runtimev1pb.GetBulkStateResponse, error) {\n \/\/ try bulk get first\n bulkGet, responses, err := store.BulkGet(reqs)\n\n \/\/ if store supports bulk get\n if bulkGet {\n return bulkResp, nil\n }\n \n \/\/ if store doesn't support bulk get, fallback to call get() method one by one\n limiter := concurrency.NewLimiter(int(in.Parallelism))\n n := len(reqs)\n for i := 0; i < n; i++ {\n fn := func(param interface{}) {\n req := param.(*state.GetRequest)\n r, err := store.Get(req)\n item := &runtimev1pb.BulkStateItem{\n ......\n }\n }\n limiter.Execute(fn, &reqs[i])\n }\n limiter.Wait()\n ......\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 的 GetBulkState() 方法先嚐試調用組件實現的 BulkGet(),如果組件支持批量操作則直接返回結果。而當組件不支持批量操作時,GetBulkState() 方法會做兩個事情:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"兜底:Dapr 通過多次調用單個 getState() 方法來模擬實現批量操作,對於應用來說是沒有感知的"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"優化:如果只是簡單的循環調用,當 key 比較多時延遲累加會比較大,因此 Dapr 做了一個並行查詢的優化,容許啓動多線程同時發起多個查詢,然後將結果彙總起來後再一起返回。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 這樣做的好處是:對於支持批量操作的組件可以充分發揮其功能,同時對於不支持批量操作的組件,由 Dapr 模擬出了批量操作的功能並提供了基本的性能優化。最終使得批量操作的 API 可以被所有組件都支持,從而讓使用者在使用批量 API 時可以有統一的體驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"對事務操作的處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相對於批量操作的簡單處理方式,事務的支持在 Dapr 中就要麻煩的多,是目前 State API 在實現中最大的挑戰,其根源在於:很多組件不支持事務!而且,事務性也無法像批量操作那樣在 Dapr 側進行簡單補救。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下是實現了 Dapr State API 的組件對事務支持的情況,其中支持事務的組件有:Cosmosdb、Mongodb、Mysql、Postgresql、Redis、Rethinkdb 和 Sqlserver。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不支持事務的組件有:Aerospike、Aws\/dynamodb、Azure\/blobstorage、Azure\/tablestorage、Cassandra、Cloudstate、Couchbase、Gcp\/firestore、Hashicorp\/consul、Hazelcase、Memcached 和 ZooKeeper。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,Dapr State API 的組件被是否支持事務分成了兩大類。這些組件在開發時和運行時調用上需要就是否支持事務進行區分:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.組件在初始化時需要指明是否支持事務"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/e8\/e81f1505dab86dea26d9ead9f5a2ee01.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.Dapr 在啓動時進行過濾,支持事務的組件單獨放在一個集合中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/d9\/d95605261012740e563f031e74408fc8.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.Dapr 在收到事務請求時,會檢查當前組件是否支持事務"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/84\/84dd10cb72716af501ac4b09f1305abc.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這直接導致了一個嚴重的後果:當用戶使用 Dapr State API 時,就必須先明確自己是否會使用到事務操作,如果是,則只能選擇支持事務的組件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"殘酷的現實:高級特性的支持度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在前面我們講述 API 標準化的價值時,是基於一個基本假設:在能力抽象和 API 標準化之後,各種組件都可以提供對 Dapr API 的良好實現,從而使得基於這些標準 API 開發的應用在功能得到滿足的同時也可以獲得可移植性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一個非常美好的想法,但這個假設的成立是有前提條件的:1. "},{"type":"text","marks":[{"type":"strong"}],"text":"API 定義全部特性"},{"type":"text","text":":即 API 提供的完整的能力,包括各種高級特性,從功能的角度滿足用戶對分佈式能力的各種需求;2. "},{"type":"text","marks":[{"type":"strong"}],"text":"所有組件都完美支持"},{"type":"text","text":":每個組件可以完整的實現 API 抽象和標準化的這些能力,不存在功能缺失,從而保證在任意一個平臺上都可以以相同的體驗獲取同樣的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/2f\/2f96eb1cfb128335f8ee40f577ea35f7.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而現實是殘酷的:"},{"type":"text","marks":[{"type":"strong"}],"text":"特性越是高級,就越難於讓所有組件都支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以 Dapr State API 爲例,如上圖所示從做向右的各種特性,組件的支持程度越來越差:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基本操作:這些是基本的 KV 語義,CURD 操作,而且是每次操作單個 key。所有組件都支持,支持度 =100%。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"批量操作:在基本操作的基礎上增加對多個 key 同時操作的支持,部分組件不能原生支持,但是 Dapr 可以在單個的基本操作上模擬出批量操作來進行彌補,因此也可以視爲都支持,支持度~=100%。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"過期時間:可選特性,設置過期時間可以讓 key 在該時間之後自動被清理,有部分組件原生支持這個特性,但也有部分組件無法支持。這是一個可選特性,Dapr 的設計是通過在請求中提供名爲 TtlInSeconds 的 metadata 來指定。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"併發支持:樂觀鎖機制,要求組件爲每個 key 提供一個 etag 字段(或者稱爲 version),每次修改時都要比對 etag,修改後要更新 etag。這個特性也是隻有部分組件支持,需要在組件支持特性中明確指出是否支持。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據一致性:容許在請求中提供參數指定操作對數據一致性的要求,可以是強一致性或最終一致性,組件如果支持就可以依照這個參數的指示進行操作。這個特性同樣只有部分組件支持。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務:提供對多個寫操作的原子性支持,只有部分組件支持(按照前面列出來的組件支持情況,大概是 40%),需要在組件支持特性中明確指出是否支持。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但實際上,在 API 定義和標準化的過程中,我們不得不面對這樣一個殘酷的現實:"},{"type":"text","marks":[{"type":"strong"}],"text":"API 定義全部特性 和 所有組件都完美支持"},{"type":"text","text":" 無法同時滿足!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這導致在定義 Dapr API 時不得不面對這麼一個痛苦的抉擇:向左?還是向右?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/0c\/0c4dc0da8a8858861dc17de24e0677ff.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"向左,只定義基本特性,最終得到的 API 傾向於功能最小集,優點是所有組件都支持,可移植性好;缺點是功能有限,很可能不滿足需求。向右,定義各種高級特性,最終得到的 API 傾向於功能最大集,優點是功能齊全,可以很好的滿足需求;缺點是組件只提供部分支持,可移植性差。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"API 定義的核心挑戰"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr API 定義的核心挑戰在於:"},{"type":"text","marks":[{"type":"strong"}],"text":"功能豐富性和組件支持度難於兼顧。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下圖所示,當 API 定義的功能越豐富時,組件的支持度越差,越來越多的組件出現無法支持某個定義的高級特性,導致可移植性下降:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/2a\/2a92f5f1dd7d8dae44df981e3afa5368.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 現有的各種 API,包括上面我們詳細介紹的 State API,基本都經歷過這樣一個流程:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個 Dapr 構建塊的 API 在初始創建時,通常會從基本功能開始,相對偏左側。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着時間的推移,爲了滿足更多場景下的用戶需求,會向右移動,在 API 中增加新功能。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新增的功能可能會導致部分組件無法提供支持,損害可移植性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此 Dapr API 在定義和後續演進時需要做權衡和取捨:不能過於保守:太靠近左側,雖然可移植性得以體現,但功能的缺失會影響使用;也不能過於激進:太靠近右側,雖然功能非常齊備,但是組件的支持度會變差,影響可移植性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Metadata 的引入和實踐"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Dapr 現有的設計中,爲了在標準 API 定義之外提供擴展功能,引入請求級別的 metadata 來進行自定義擴展:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/39\/391a3a9fa724a81fb53ac409adb4e3f9.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"metadata 字段的類型定位爲 map,可以方便的攜帶任意的 key-value,在不改變 API 定義的情況下,組件和使用者可以約定在請求級別的 metadata 中通過傳遞某些參數來使用更多的底層能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖是在阿里雲在內部落地 Dapr 時,對 Dapr State API 的各種 metadata 自定義擴展:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/fd\/fdef11dbe18b53240a247fe5facc1066.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意:State API 中,expire 的功能在通過名爲 ttlInSeconds 的 metadata 來實現,而沒有直接在 getStateRequest 中定義固定字段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"metadata 的引入解決了 API 功能不足的問題,但是也造成了另外一個嚴重問題:"},{"type":"text","marks":[{"type":"strong"}],"text":"破壞可移植性"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"可移植性是 Dapr 的核心價值:因此定義 Dapr API 時應"},{"type":"text","marks":[{"type":"strong"}],"text":"儘量滿足可移植性"},{"type":"text","text":"的訴求。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"API 設計時會偏"},{"type":"text","marks":[{"type":"strong"}],"text":"功能最小集"},{"type":"text","text":":爲了提供最大限度的可移植性,設計時往往會傾向於從功能最小集出發,如下圖所示:"}]}]},{"type":"listitem","attrs":{"listStyle":"none"},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"出現"},{"type":"text","marks":[{"type":"strong"}],"text":"功能缺失"},{"type":"text","text":":功能最小集合意味着 Dapr API 只定義基本功能,自然會導致缺乏各種高級特性,落地時會遇到無法滿足應用需求的情況。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"進行自定義擴展"},{"type":"text","text":":爲了滿足需求,使用請求級別的 metadata 進行自定義擴展,提供 Dapr API 沒有定義的功能。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"優點是可以"},{"type":"text","marks":[{"type":"strong"}],"text":"滿足功能需求"},{"type":"text","text":":metadata 的使用擴展了功能,使得底層組件的能力得以釋放。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"但缺點是"},{"type":"text","marks":[{"type":"strong"}],"text":"嚴重破壞可移植性"},{"type":"text","text":":自定義擴展越多,在遷移到其他組件時可能丟失的功能就越多,可移植性就越差。"}]}]},{"type":"listitem","attrs":{"listStyle":"none"},"content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖上看,當我們從可移植性爲出發點進行 API 設計時,由於功能缺失引入 metadata 進行自定義擴展,在解決功能問題的同時,造成了可移植性的嚴重破壞。從而偏離了我們的初衷,也造成整個 API 設計和落地打磨的流程無法形成閉環,無法建立良性循環。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Dapr 在阿里內部落地時遭遇的重大挑戰"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阿里是 Dapr 最早期的用戶之一,在阿里內部落地 Dapr 時,遭遇了重大挑戰:"},{"type":"text","marks":[{"type":"strong"}],"text":"之前有的功能現在都要有!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是來自業務團隊的普遍需求,其背景是落地時遇到的三個現狀:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"十餘年打磨下來,阿里內部中間件各種五花八門的功能都有。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"社區開源版本 \/ 其他雲平臺提供的產品往往沒有這些功能。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr 在內部落地時功能方面的 GAP 非常大。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其結果就是在落地時不得不引入 request 級別 metadata —— 這在短期內滿足了功能需求,但從長期考慮損害了可移植性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"反思:左右爲難,何去何從?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回顧前面我們談及的 Dapr API 定義的核心挑戰:"},{"type":"text","marks":[{"type":"strong"}],"text":"功能豐富性和組件支持度難於兼顧。"},{"type":"text","text":"因此,在 Dapr API 定義和標準化過程中,在功能豐富性和組件支持度(可移植性)上如何取捨,就成爲必須慎重考慮和嚴謹對待的關鍵問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"實踐爲先:在落地中探索打磨"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"空想無益,實踐爲先,讓我們以目前 state API 爲例分析 Dapr 在 API 定義和標準化上的一些實踐,讓我們對此有更加深刻的理解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"換角度看問題:組件提供的能力不平齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,讓我們換一個角度看待這個問題,功能豐富性和組件支持度難於兼顧的核心在於"},{"type":"text","marks":[{"type":"strong"}],"text":"組件提供的能力不是平齊的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/29\/298af0137732cd1fb526bd46229a71fc.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示,不同組件所能提供的能力是不同的,因此他們能夠支持的 API 高級特性也有所不同:有些組件支持的特性多一些,有些組件支持的特性少一些。體現在上圖的左側,當我們把這些組件的能力都羅列出來時,得到的時一個高低不平的圖形。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最小功能集和最大功能集對應了這個圖形中的最低處和最高處(簡單起見我們假定最小功能集和最大功能集都剛好有組件可以直接對應),而 Dapr API 的設計關鍵就在於如何權衡功能豐富性和組件支持度,最終選擇一個合適的功能集合。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決思路一:Dapr Runtime 彌補組件缺失能力"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還記得我們前面詳細看過的 GetBulkState() 方法的實現代碼嗎?爲了彌補部分組件無法支持批量操作的問題,Dapr 採用的方式是在 Dapr Runtime 中通過多次調用來模擬出批量操作的功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (a *api) GetBulkState(ctx context.Context, in *runtimev1pb.GetBulkStateRequest) (*runtimev1pb.GetBulkStateResponse, error) {\n \/\/ try bulk get first\n bulkGet, responses, err := store.BulkGet(reqs)\n\n \/\/ if store supports bulk get\n if bulkGet {\n return bulkResp, nil\n }\n \n \/\/ if store doesn't support bulk get, fallback to call get() method one by one\n limiter := concurrency.NewLimiter(int(in.Parallelism))\n n := len(reqs)\n for i := 0; i < n; i++ {\n fn := func(param interface{}) {\n req := param.(*state.GetRequest)\n r, err := store.Get(req)\n item := &runtimev1pb.BulkStateItem{\n ......\n }\n }\n limiter.Execute(fn, &reqs[i])\n }\n limiter.Wait()\n ......\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用\"讓子彈飛\"的話來解釋這段代碼的功能,就是這個畫風:我給了他(組件)一把手槍 (部署了 Dapr Sidecar),他要是體面(支持批量操作),你就讓他體面(調用組件的批量方法);他要是不體面(不支持批量操作),你就幫他體面(多次調用單個操作的方法來模擬批量操作)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我們在介紹 Dapr 和 ServiceMesh 的本質差異時強調過:Dapr 的工作模式是"},{"type":"text","marks":[{"type":"strong"}],"text":"能力抽象"},{"type":"text","text":",Sidecar 原則上對應用只暴露抽象之後的分佈式能力,屏蔽了底層具體的實現和通訊協議,不做\"轉發\"而是提供\"能力\",Sidecar 扮演的是"},{"type":"text","marks":[{"type":"strong"}],"text":"運行時"},{"type":"text","text":" (“Runtime”) 的角色。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂\"組件能力缺失\",是指底層組件原生無法提供 API 定義的功能,如上面 State API 中定義的批量操作,現實中就是有不少 Dapr State 的組件原生不支持批量操作。而 Dapr 的工作模式使得 Dapr 有機會對組件缺失的能力進行彌補,請牢記:Dapr 的 Sidecar 是 Runtime, 而不是 Proxy。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/8e\/8ed3f6bb8f032c0caa04b7d4432c5f05.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖右側所示,Dapr Sidecar 對批量操作的實現進行了\"干預\":當發現底層組件不支持批量操作時,Dapr Runtime 會改用多次調用組件的單個操作方法的方式來模擬批量操作,從而在功能上實現了 State API 定義中的批量操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是 Dapr 中解決功能缺失比較理想的方式,應優先採納,當然這個代價是 Dapr 需要做更多的工作。但成效時非常明顯的,如下圖所示,在 Dapr 進行功能補齊之後,左側組件功能不對齊的問題會得以緩解,相關的這些功能也就可以放心的納入 Dapr API 的範圍而無需擔心組件支持度。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/ca\/caa5f9fc42715d24967cbeed433bd870.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決思路二:Dapr Component 彌補組件缺失能力"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"思路和前面是一致的,功能出現缺失時由 Dapr Sidecar 來進行補救。但差異在於進行補救的地方的不是 Dapr 的通用代碼,而是 Dapr 中該組件對應的組件實現代碼,因此這個補救只能在當前組件中生效,無法複用到其他組件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以併發支持爲例,Dapr State API 通過 etag 的方式定義了樂觀鎖的功能,但是在使用 Redis 實現 State API 時就會遇到這樣一個問題:Redis 原生是不支持 etag (或者叫做 version)的。而且這個功能缺失還無法在 Dapr 中通過通用的方式(如前面通過多次調用單個操作的方式來模擬批量操作)來進行彌補。爲了支持 etag,Dapr 中的 Redis state component 在代碼實現中就採用了 hashmap 類型的 value,通過 data 和 version 兩個 hashmap key 來實現 etag 的功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/1d\/1d677414bef20a8148aafb953759dec5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而爲保證對 data 和 version 這兩個 hashmap 值操作的原子性,引入了 LUA 腳本:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/73\/7369bacaa738a906ef188cc5ed0762b1.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類似的實現在 Dapr 各組件的代碼中有不少,這種方式也可以很好的彌補功能缺失。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決思路三:Dapr 無法彌補,但可以模糊處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"State API 中的 saveState() 方法,請求中的 consistency 字段用於數據一致性,當組件支持多副本時,consistency 字段將用於指定對數據一致性的要求,其取值有兩種:eventual:(最終一致性)和 strong:(強一致性)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/70\/70ef9a2dcccabce31f99bcc90e7e25b7.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是支持強一致性的 Redis state 實現代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (r *StateStore) setValue(req *state.SetRequest) error {\n ......\n if req.Options.Consistency == state.Strong && r.replicas > 0 {\n _, err = r.client.Do(r.ctx, \"WAIT\", r.replicas, 1000).Result()\n if err != nil {\n return fmt.Errorf(\"Redis waiting for %v replicas to acknowledge write, err: %s\", r.replicas, err.Error())\n }\n }\n ......\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果組件不支持強一致性,或者當前組件並沒有配置集羣不存在多副本,則可以忽略 consistency 參數。即使請求中的 consistency 字段明確要求強一致性,在不能實現或者無需實現時,可以簡單忽略該參數而無需報錯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類似的,超時的實現在 State API 中是通過名爲 ttlInSeconds 的 metadata 來實現。如果組件不支持超時,則可以簡單的忽略該 metadata 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決思路四:無法彌補又不能模糊處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些功能的缺失,是前面三種解決方式都無法解決的,例如前面提到的 State API 中的事務支持:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"剛需:從需求上說,這個功能必須有,比如 Dapr Secret Store 就嚴重依賴 State API 的事務方法。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"硬傷:從實現上說,的確很多組件原生是不支持事務。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"彌補:Dapr 目前無力彌補。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模糊:不能模糊處理,必須明確支持或者不支持,在有事務需求的場景下必須選擇支持事務的組件不然無法保證多個操作的事務性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dapr State API 目前的做法是按照是否支持事務來區分 state components:用戶如果需要事務支持,必須選擇支持事務的組件。而需要事務支持時,可移植性的範圍被限制爲支持事務的組件列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個解決方案的缺陷在於會對可移植性造成災難性後果:如上面 State API,一旦要求支持事務,則只有約 40% 的組件可以支持。因此必須嚴格限制使用:只能爲個別關鍵特性開特例,不能濫用,不然使用者選擇組件時會非常痛苦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"更大挑戰:比 State API 複雜的多的 Configuration API"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然我們前面詳細列出了在 State API 中爲了實現各種高級特性而進行的各種解決方式,但 State API 其實本質上還是一個比較簡單的 API,高級特性也屈指可數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Dapr API 中還有其他比 State API 要複雜的多的 API,如 Dapr 社區在今年開始深入討論和嘗試加入的 Configuration API。由於目前各種配置產品的差異性實在太大,甚至連最基本的配置模型都存在巨大差異,導致 Configuration API 在制定時遇到非常大的阻力。簡而言之,和 State API 相比, Configuration API 要複雜 10 倍。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對此感興趣的同學請瀏覽:"},{"type":"link","attrs":{"href":"https:\/\/github.com\/dapr\/dapr\/issues\/2988","title":"","type":null},"content":[{"type":"text","text":"https:\/\/github.com\/dapr\/dapr\/issues\/2988"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"路阻且長:但行好事莫問前程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然 Dapr 還很稚嫩,雖然多運行時 (Mecha) 的理論還在早期實踐的過程中,但我堅信 Dapr 作爲多運行時理論的第一個實踐項目是符合雲原生的大方向,Dapr 能爲雲原生應用帶來巨大的價值。而從產品形態來說,目前 Dapr 是走在雲原生社區的前面,作爲 Dapr 的早期實踐者、代碼貢獻者和 Dapr 項目的 Approver,我很驕傲的說:我們是雲原生的開拓者,我們正在創造雲原生新的歷史。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Dapr API 是 Dapr 成敗的關鍵之一,從雲原生髮展的角度未來也需要這麼一個通用的分佈式能力的 API 標準,誠然目前的 Dapr API 需要在不斷實踐中補充和完善,而且這個過程註定會很艱難,就像前面這個遲遲未能順產的 Configuration API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎更多的公司和個人參與到 Dapr 項目。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"延伸閱讀:"},{"type":"link","attrs":{"href":"https:\/\/skyao.io\/talk\/202103-dapr-from-servicemesh-to-cloudnative\/","title":"","type":null},"content":[{"type":"text","text":"https:\/\/skyao.io\/talk\/202103-dapr-from-servicemesh-to-cloudnative\/"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章