通過 Dapr 實現一個簡單的基於- net 的微服務電商系統

本來想在 Dpar 1.0GA 時發佈這篇文章,由於其他事情耽擱了放到現在。時下微服務和雲原生技術如何如荼,微軟也不甘示弱的和阿里一起適時推出了 Dapr(https://dapr.io/),園子裏關於 dapr 的文章不太多,所以今天就借這篇文章分享一下如何通過 dapr 跑起來一個簡易的電商系統,讓大家通過這個系統來觀察 dapr 如何運作的,權當拋磚引玉。

首先第一個話題,什麼是 dapr?

要說 dapr,首先我們得了解什麼是服務網格,而要說服務網格還是得先講講微服務。微服務的概念相信現在大家已經耳熟能詳了。在微服務體系中,開發者通過拆分設計不同的服務,通過服務間協同作業的方式聚合業務實現相關的功能。

服務與服務之間涉及服務調用、事件傳播、狀態流轉等等概念,再細分相關功能會涉及到服務調用時限流、重試、降級,事件傳播時確保一致性,對整個服務系統的拓撲追蹤、監控等等功能。以 java 爲例,一般是採用 dubbo 或者 springcloud 這樣的侵入性框架,通過開發人員手動集成到項目裏。並在外部搭建諸如 zookeeper、eureka 這樣的分佈式協調器來實現服務的註冊、發現。通過網關如 zuul、kong 實現對外部對內部服務的調用,通過 feign ribbon hystrix 這樣或那樣的組件實現服務間通訊時負載均衡、熔斷、限流、降級。通過設計 eventbus 來實現事件在服務間流轉。

說了這麼一大堆,和服務網格有什麼關係呢?服務網格本質上就是微服務架構在雲原生基礎上對網絡通訊相關的功能做了解耦和下沉。讓運行於雲原生之上的應用 (一般呈現方式主要是容器) 可以不再關心服務間通訊相關的一大堆技術實現。通過對每一個應用附加一個獨立進程的代理 (也叫 sidecar) 實現。

 

這樣開發人員只關心應用如何實現具體的業務,而不用去關心具體的服務間治理,服務網格幫應用完成服務註冊、發現、負載均衡、重試、限流等等相關功能。

所以 dapr 是什麼就一目瞭然了,dapr 就是服務網格的一種實現方式。只不過相比傳統的服務網格關注的可能是流量治理來講 dapr 更關注服務間狀態變化,用官方的原話來講 “Dapr 是一個可移植的、事件驅動的運行時,它使任何開發人員能夠輕鬆構建出彈性的、無狀態和有狀態的應用程序,並可運行在雲平臺或邊緣計算中,它同時也支持多種編程語言和開發框架。”

什麼是事件驅動?

什麼是事件驅動? 我們知道在一個分佈式系統裏,當某個服務需要其他服務協同作業時,有兩種辦法,一種就是通過強一致性的方式調用,比如 RPC call 或者 restapi。這種方式有一個弊端,就是必須確保下游服務必須可用,否則可能會導致同步調用時調用過程超時、或者下游服務不響應導致的失敗。

在分佈式系統裏由於網絡 IO 不可靠等等因素,我們往往很難 100% 確保同步調用能夠將一個業務在多個服務間協同完成,所以一般會採用訂閱 - 發佈的方式。也就是大家比較熟悉的事件總線這樣的異步模型,通過將我們的請求以發佈事件的方式灌入消息隊列後不等待立即返回,通過訂閱方訂閱消息完成後續操作,若需要回滾同樣發佈事件,由發送方訂閱失敗消息進行補償操作即可。

通過這樣的方式我們可以構建一個以事件來驅動的異步的,最終一致性的響應式系統。而 dapr 則是將事件驅動在雲原生層面發揚光大,通過對不同中間件的集成屏蔽了構建事件驅動架構的各種複雜性,讓開發人員幾乎不寫或者少寫代碼的情況下完成一個事件驅動架構的分佈式系統。

Dapr 如何助力微服務設計落地的?

Dapr 提供了哪些功能來助力我們微服務落地呢?可以從上圖看到有 7,8 種功能,我們從左至右一個一個來講。

 

第一個就是服務間調用,也就是常見的同步調用。dapr 在服務間調用封裝了服務註冊發現、ssl、自動重試、熔斷、限流 (需單獨配置支撐)。一般當應用請求下游服務時 daprd 會發起一個 https 請求、若超時會重試數次,若下游服務下線不可用則會返回一個友好的 json 格式的 50x 供上游服務做異常冗餘處理。

第二個是狀態管理,提供了對於存儲鍵 / 值對的狀態管理,同時對大部分主流的狀態存儲中間件進行了支持,而無需開發者去對特定中間件做相應的 sdk 集成。

第三個是訂閱發佈,通過該 api 可以輕鬆實現一個異構的語言無關的事件總線,同時和狀態管理一樣,它的訂閱發佈中間件是可插拔的。

第四個是資源綁定,帶觸發器的資源綁定通過接收和發送事件到任何外部源(如數據庫、隊列、文件系統等)來進一步構建事件驅動架構,以實現擴展性和彈性,此特性有一點 Serverless 的思想。

第五個是大名鼎鼎的 actor 模型,很多沒接觸過 actor 的同學可能會一頭霧水的問 actor 模型是什麼呢?簡單來講就是一個分佈式的併發的無鎖線程同步對象。舉一個簡單的例子它可以解決在併發下商品超賣的問題,假設我們有一個 api 可以通過訪問來扣商品庫存,在無鎖無事務的情況下,由於讀寫數據庫時間差的問題,一定會導致商品超賣,即便是我們將商品庫存放在對象上通過內存保存,如果不引入原子操作,也一樣會有線程同步導致的數據不一致問題,而 actor 則可以在不引入任何內部或外部 api/sdk 的情況下實現多線程訪問下數據的完全一致性。 簡單來講就是每一個 actor 是通過對 mailbox 隊列來阻塞消費實現多線程訪問下數據一致性的,具體大家可以多瞭解一下這個模型。 而 Dapr 在其 actor 運行時提供了很多能力,包括併發,狀態管理,用於 actor 激活 / 停用的生命週期管理,以及喚醒 actor 的計時器和提醒器。

第六個是可觀測性,dapr 通過一些三方組件提供了諸如鏈路追蹤和應用監控等等相關觀測手段來方便開發人員更加直觀的定位網絡問題等等。

第七個是安全性,默認 dapr 之間調用不管是同步調用還是 actor 調用或者訂閱發佈,均會通過雙向 https 的方式加密通訊,避免明文傳遞消息。

最後一個是豐富的擴展組件,熟悉 dapr 的開發者可以自定義各種組件通過中間件的方式插入到 sidecar 的 pipline 中去實現自定義功能的擴展。

另外需要關注的是 dapr 對上層應用提供了兩種請求模式,一種是 http api 一種是 grpc api,通過這種語言無關的方式我們可以輕易的在不同語言之間通過 dapr 搭建起一個異構的分佈式系統而不用關心不同語言之間的差異。

Dapr 與其他服務網格的區別

目前市場上的服務網格框架基本都被諸如 linkerd、istio 這樣的老牌服務網格佔據。這些服務網格的關注點和 dapr 有一定區別。如果非要說相同或者相似點的話那就是他們的架構都是由數據平面和控制平面組成,其中數據平面主要是由集羣內的各種 sidecar 組成,而控制平面就是調度中心。 另外一個相同點就是他們都實現了對應用程序的代碼無侵入性 (dapr 提供了簡單的 sdk,只是對 dapr api 的簡易封裝,不是必選項),但是從功能層面來講,兩者的關注點則完全不一樣了,例如 service mesh 霸主 istio 他提供了對流量切分、流量鏡像、監控、智能路由、故障注入,自動化的度量指標、日誌以及追蹤等等功能,可以看到它更關心流量代理這部分邏輯。而 dapr 在這部分目前來講還稍弱,但是 dapr 提供了其他服務網格幾乎沒有關心的狀態服務、事件、actor 模型等等功能,兩者可以說是互補的(dapr 是可以和 istio 這樣的服務網格集成工作的,未來可期)

更多瞭解 dapr

訪問 https://dapr.io 瞭解更多

talk is cheap, show me the code!

說了那麼多概念,最終還是需要落地到具體的系統設計上,我們就從這個電商系統開始吧。

技術概要、設計規範

整個電商系統分爲兩個具體的 repo

https://github.com/sd797994/Oxygen-Dapr 

該 repo 是針對 Dapr 通訊相關的 API 統一了編程模型封裝實現的一個 rpc 框架,類似於 dapr 提供的. net SDK,該 repo 我已經將打包到了 nuget,所以電商系統不需要依賴該 repo 的源代碼,有興趣的朋友可以 copy 下來,可以的話請 star 一下。該框架基於. net5

https://github.com/sd797994/Oxygen-Dapr.EshopSample

該 repo 爲本次演示電商系統源代碼,其結構如下:

 

Depoly 主要是一些 yaml 和 bat 方便讀者朋友通過 k8s 快速啓動之用。

Public 包含一些領域業務的抽象 (DomainBase) 和工具層 (InfrastructureBase) 以及 RPC 的接口層 (Remote-IApplicationService) 以及前端 (WebPage-www) 及後端 (WebPage-Admin) 頁面

Services 文件夾包含後端的微服務,分爲賬戶服務、商品服務、商城公共服務以及交易服務,另外包含兩個通用支撐服務:圖片服務以及作業服務。

業務微服務類主要是以清潔架構分層,清潔架構是領域驅動設計的一個概念:

 

整個代碼結構是以 Domain 爲核心,外部依次是應用層、基礎設施,是一個從內及外的設計。

Domain 包含了整個服務所需的具體的業務聚合,Domain 的核心數聚合,包含聚合根、實體以及值對象,剩餘部分則是圍繞聚合形成的規約、DTO、領域服務、倉儲抽象等等。
應用層通過讀寫分離的方式來實現對領域層的操作和對查詢業務的操作,另外包含事件訂閱器用於接收其他應用發起的領域事件。其結構如下:

用例 (UseCaseService) 類型的應用服務主要作用就是聚合操作當前服務的領域,同時調用基礎設施層實現持久化以及事務,同時可以發送領域事件亦或是調用遠程 RPC。

查詢 (QueryService) 類型的應用服務主要是對各種客戶端發起的業務指令調用基礎設施層的 ORM 或 es 或者 dapr 的 statemanager 或者遠程 RPC 進行具體的操作查詢。

事件訂閱器 (EventHandler) 類型的應用服務主要是接收事件並進行業務操作,其操作邏輯和用例類似。

基礎設施層則包含了對上層的一系列支撐,包括各種通用組件、工具、ORM、持久化實體、倉儲實現等等,其中用到的外部持久化設備有 postgres、elasticsearch 以及 redis,所有的業務表存儲主要依賴於 postgres,elasticsearch 主要是包含移動端的商品列表查詢,redis 則主要是對 dapr 以及作業系統的持久化支撐。此處不再贅述。

Host 作爲服務啓動的入口,主要是啓動通用主機注入依賴注入框架,注入 Oxygen 框架初始化各種配置、AOP、鑑權服務等等來啓動 RPC 服務。不再贅述

部署網絡拓撲圖

Tips: 如何部署可以參考 repo 的 readme.md

整個系統前、後端以及各種通用服務都是以容器化的方式運行在 k8s 之上的。其中在最前端是 k8s 的 ingress-controller,由於這個場景相對比較簡單,不需要各種複雜的流量切分邏輯,所以我目前選型的是 k8s 官方推薦的 nginx。當請求從客戶端發起的時候,流量最先流入 ingress-controller,通過已配置好的 ingress 規則會再次發送到各個 k8s service 再流入具體的 pod 進行作業。

其中對 www.dapreshop.com 的訪問會直接請求到後端頁面 pod、對 m.dapreshop.com 的訪問會請求到移動端頁面 pod, 這兩個頁面上發起的 api.dapreshop.com 則會統一先流入到一個叫 apigateway 的 pod 上,該 pod 附加了一個 dapr 的 sidecar,該 pod 只是一個包含路由重寫規則的 nginx, 它的作用是將源地址請求重新組裝爲 dapr 可識別的 api 地址並調用 sidecar, 這樣通過 sidecar 和內網的其他掛載了 sidecar 的各種子服務進行相應的互操作。

如何運行它?

Tips: 如何運行可以參考 repo 的 readme.md

通過 kubectl 查詢兩個命名空間看到如下情況則代碼系統已經完全啓動完畢。

 

這個時候訪問 admin.dapreshop.com 會進入該頁面:

 

當初始化後會自動創建一個管理員、一個用於模擬下單的用戶以及相關的權限、角色。

 

同時會自動創建商城的基礎設置、商品分類、商品,以及隨機創建幾個商品的折扣活動。

 

 

此時訪問 m.dapreshop.com 就能看到一個包含商品的下單頁面了

 

隨意選擇幾個商品,選擇結算後,後端即可創建一個訂單,後臺就可以模擬訂單的剩餘流程、注意該訂單會在 5 分鐘內被作業系統取消,庫存會回滾。

觀察

當我們在前後端做了各種操作後,登錄 zipkin.dapreshop.com 即可觀察到流量的變化

 

可以觀察到相應的請求和鏈路細節

 

以前端下訂單爲例:

 

流量通過網關路由到交易服務,交易服務會調用賬戶服務獲取一個 mock account、然後會調用商品 RPC 查詢商品基礎信息,接下來調用商品 Actor 對象做具體的庫存減扣,最後發佈事件,同時交易服務的交易記錄訂閱器和作業訂閱器會訂閱該事件做後續相關操作。由於所有的請求都通過 daprd 這個 sidecar 完成,所以所有的請求和流量都能通過 zipkin 直觀的觀察到。

最後我們可以在 daprcli 中通過 dapr dashborad -k 來觀測 dapr 目前控制平面的基本情況,此部分就不贅述了,大家可以登錄 dapr 的官網多瞭解

以上就是本次關於 dapr 如何落地一個電商 demo 的入門級的相關分享,Dapr 本身包含了太多的內容由於時間關係無法一一呈現還需要讀者朋友們在實際使用中去挖掘,如果喜歡的話請給 repo 一個 star,歡迎轉發 fork~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://www.cnblogs.com/gmmy/p/14606109.html