
Intro
Dapr 官方團隊已於最近(2021.2.17)正式發布Dapr v1.0,Dapr已正式生產可用,可以部署到自托管環境或 Kubernetes 集群。對於絕大多數開發者來說,想必對Dapr只是有所耳聞,而具體是什么(What),可以解決什么樣的問題(Why&How),有怎樣的應用場景(Where),並不知悉。本文就嘗試簡要梳理下Dapr,並嘗試回答以上問題。
What's Dapr
Distributed Application Runtime. An event-driven, portable runtime for building microservices on cloud and edge.
分布式應用運行時。一個事件驅動、可移植的運行時用於在雲上和邊緣計算上構建微服務。
以上是Dapr官方GitHub倉庫上對Dapr的簡介。文字雖短,口氣卻很大,因為其除了涵蓋了當前所有的技術熱點:分布式、雲、微服務,還自我標榜為:分布式應用運行時。分布式應用我們或多或少有些了解,運行時也聽到不少,比如常見的語言運行時:Java 運行時,.NET 運行時,Go 運行時等等,那運行時又是什么東西?簡要來說:運行時是程序運行依賴的執行環境。以.NET 程序運行時CLR為例,它為.NET應用程序提供了一個托管的代碼執行環境負責應用程序在整個執行期間的內存管理、線程管理、安全管理、遠程管理、即時編譯等。
那分布式應用運行時,就是提供分布式應用運行所依賴的的執行環境。那運行分布式應用需要哪些環境依賴呢?回答這個問題,我們要先思考開發分布式應用的挑戰是什么?明確了挑戰,那就找到了答案。
從單機到分布式,是追求更快和更高的性能,但也帶來了更多的不確定性。比如,不確定計算機何時異常,不確定磁盤何時損壞,不確定網絡通信的延遲,也不確定消息是否被正常消費。這些不確定性構成了分布式應用的挑戰,簡而言之:
- 異構的機器與網絡:穩定性問題
- 普遍的節點故障:可靠性問題
- 不可靠的網絡:一致性問題
面對這些挑戰,業界提出了諸多的分布式理論、協議,如CAP定理,BASE理論,一致性協議2PC/3PC/ZAB,來保證系統的正常運行。雖然問題貌似是有了解決方案,但是應用的復雜度升高了。應用除了需要實現業務需求,還要兼顧非業務需求,集成諸如服務發現、負載均衡、失效轉移、動態擴容、數據分片、調用鏈路監控等分布式系統的核心功能,對應用有很強的侵入性,這就是以Spring Cloud為代表的微服務框架的常見做法。
那如何解決侵入性的問題呢?這個問題隨着容器編排技術的成熟有了新的解法。Kubernetes可以不侵入應用層,在容器層解決問題,比如K8S Service就具有服務發現、負載均衡的能力,HPA具有動態擴容的能力。隨着K8S的快速發展,雲原生的概念,也就越來越深入人心,那如何利用好K8S提供的基座能力,將更多的分布式能力下沉,讓應用開發回歸業務呢?其中Service Mesh提出的Sidecar模式,就很好的解決了微服務架構中網絡通信的問題。Sidecar主要就是用來處理諸如服務發現、負載均衡、請求熔斷等一系列非業務需求,應用在部署時動態插入Sidecar,服務間的通信通過Sidecar進行代理,以完成對服務間網絡通信的接管。
到這里,微服務開發在Service Mesh的幫助下,已經漸漸回歸業務本身,讓更多的開發者看到了一絲曙光。It's enough? 來看下Bilgin Ibryam在Multi-Runtime Microservices Architecture文章中提及的分布式應用的四大需求:

從上圖可以看出,除了網絡(Networking)外,生命周期(Lifecycle)、狀態(State)、捆綁(Binding)也是分布式應用要解決的問題之一。網絡問題可以借由Service Mesh 比如Istio予以解決。那其他三個該如何解決呢?又要應用自行開發集成嗎?顯然不符合應用回歸業務本身的訴求。這時,Dapr登場了,Dapr提出的分布式應用運行時就是實現了以上四個需求並將其下沉作為分布式應用的運行環境。
簡而言之:Dapr將分布式能力進行封裝下沉作為運行時以簡化分布式應用開發的技術復雜度。
How Dapr Works
那Dapr如何簡化分布式應用的開發呢?下面我們來看一看Dapr的主要特性。

一圖勝千言:Dapr通過以HTTP/gRPC API這種與語言無關的方式暴露封裝的分布式能力供應用調用,從而支持使用任意語言或框架進行開發集成。目前官方已經提供了Go,Node,Python,.NET,Java, C++,PHP,Rust,Javascript的Sdk,簡化Dapr的集成。
其中Dapr的核心構建塊(Building Block)就是用來提供各種不同的分布式能力,我們來分別看一看。

1. Service-to-service invocation(服務調用)
提到跨服務方法調用,這個大家肯定會想,這簡單啊,不就是服務暴露API就好了嘛。是,但不完全是。比如nodeapp暴露了一個API:http://10.0.0.2:8000/neworder,按照傳統的方式,直接HTTP POST這個API訪問就得了,但在Dapr中,其提供了服務間方法調用的接口規范,需要按照
POST/GET/PUT/DELETE http://localhost:<daprPort>/v1.0/invoke/<appId>/method/<method-name> 的格式進行訪問。那假設pythonapp需要訪問nodeapp的方法,就需要POST一個請求到http://localhost:3500/v1.0/invoke/nodeapp/method/neworder。你可能會想為何多此一舉呢?此舉的意義何在呢?目的很簡單,就是為了實現對服務間網絡通信的控制以完成諸如服務發現、流量控制、重試熔斷、安全訪問等,而這相關的網絡控制功能就是集成在Dapr的Sidecar中,以對應用透明的方式集成進來的。整體的服務調用流程如下圖所示:

PS:如果對Istio熟悉的同學需要注意,二者雖然都是通過Sidecar的模式進行網絡控制,但二者是有有區別的。Dapr是以API的方式,而Istio是以代理的方式(不改變HTTP請求URI)。
2. State management(狀態管理)
在進行微服務開發時,繞不開的話題就是服務間的狀態共享、並發一致性問題。對於狀態共享,你可能會說,各個服務連接到同一個Redis實例就OK了。是,但不得不考慮潛在的更新沖突的問題。Dapr 以更友好的HTTP API的方式進行狀態的存儲和讀取,同時支持通過ETags進行並發控制,並支持通過選項設置並發和一致性行為。
- 存儲:
POST http://localhost:<daprPort>/v1.0/state/<storename> - 讀取:
GET http://localhost:<daprPort>/v1.0/state/<storename>/<key> - 刪除:
DELETE http://localhost:<daprPort>/v1.0/state/<storename>/<key>
以下是保存狀態的舉例:
concurrency用於指定並發選項:first-write-wins/last-write-wins(以第一次寫入為准/以最后一次寫入為准),默認以最后一次寫入為准。consistency用於指定一致性選項:strong/eventual(強一致性/最終一致性),默認為最終一致性。
curl -X POST http://localhost:3500/v1.0/state/starwars \
-H "Content-Type: application/json" \
-d '[
{
"key": "weapon",
"value": "DeathStar",
"etag": "xxxxx",
"options": {
"concurrency": "first-write",
"consistency": "strong"
}
}
]'
目前支持使用Azure CosmosDB、 Azure SQL Server、 PostgreSQL,、AWS DynamoDB、Redis 作為狀態存儲介質。
3. Publish and subscribe(消息發布及訂閱)
發布訂閱模式,老生常談了,主要是用於微服務間基於消息進行相互通信。你可能也會說,這也要拿出來說,我搞個RabbitMQ/RocketMQ就是了。是的,但我還是要說,Dapr提供了一致性的消息發布、訂閱API,而無需關注具體使用的是何種Message Broker,從而和底層基礎設施解耦。
- 發布:
POST http://localhost:<daprPort>/v1.0/publish/<pubsubname>/<topic>[?<metadata>] - 獲取可訂閱主題:
GET http://localhost:<appPort>/dapr/subscribe - 訂閱:
POST http://localhost:<appPort>/<path>

4. Resource bindings and triggers (資源綁定及事件觸發)
Dapr的Bindings與Azure Functions很類似,其是建立在事件驅動架構的基礎之上的。通過建立觸發器與資源的綁定,可以從任何外部源(例如數據庫,隊列,文件系統等)接收和發送事件,而無需借助消息隊列,即可實現靈活的業務場景。Dapr的Bindings分為兩種:
- Input Bindings(輸入綁定):當外部資源的事件發生時,借助輸入綁定,你的應用即可通過特定的API:
POST http://localhost:<appPort>/<name>收到外部資源的事件,用於處理特定邏輯。 - Output Bindings(輸出綁定):輸出綁定允許你調用外部資源。比如,在訂單處理場景中,在訂單創建成功后,可以將訂單信息通過Dapr的綁定API:
POST/PUT http://localhost:<daprPort>/v1.0/bindings/<name>輸出到Kafka特定隊列上。

5. Actors
Dapr中的Actor模型,和Orleans的Virtual Actor一脈相傳,之前寫過一篇文章Orleans 知多少 | .NET Core 分布式框架介紹過。簡單來講:Actor模型 = 狀態 + 行為 + 消息。一個應用/服務由多個Actor組成,每個Actor都是一個獨立的運行單元,擁有隔離的運行空間,在隔離的空間內,其有獨立的狀態和行為,不被外界干預,Actor之間通過消息進行交互,而同一時刻,每個Actor只能被單個線程執行,這樣既有效避免了數據共享和並發問題,又確保了應用的伸縮性。
Actor模型大大簡化了並發編程的復雜度,Dapr在Actor運行時中提供了許多功能,包括並發控制,狀態管理,生命周期管理如Actor的激活/停用以及用於喚醒Actor的Timer(計時器)和Reminder(提醒)。這些功能同樣也是通過API的方式予以提供。
- 調用Actor 方法:
POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method> - 創建 Timer:
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name> - 創建 Reminder:
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
6. Observability(遙測)
Dapr記錄指標,日志,鏈路以調試和監視Dapr和用戶應用的運行狀況。 Dapr支持分布式跟蹤,其使用W3C跟蹤上下文標准和開放式遙測技術,可以輕松地診斷在生產環境中服務間的網絡調用,並發送到不同的監視工具,如Prometheus。
7. Secrets(安全)
Dapr 提供了Secret管理,不過不同於K8S中的Secret,其支持與公有雲和本地的Secret存儲集成,以供應用檢索使用。
What Can We Do With Dapr
了解了Dapr是什么,以及其提供的特性,那Dapr的應用場景就一目了然了。也就是官網首頁的Slogan:Simplify cloud-native application development--Focus on your application’s core logic and keep your code simple and portable。
簡化雲原生應用的開發,確保應用專注於業務,並保證代碼簡單可移植。
因此,在考慮雲原生應用開發的技術選型時,盡情嘗試吧,目前在國內阿里雲也已采用。
Last
在雲原生如火如荼發展之際,Dapr V1.0 的正式發布,為開發者指明了雲原生時代微服務的開發方向。相信Dapr 在未來的微服務架構選型中必將占有一席之地!
