Actor模型是如何讓編寫併發系統變得更簡單的?

在上週Dapr的直播中,小夥伴提了很多關於

Actor模型

的問題。Actor模型作為Dapr中重要的部分,大大簡化了併發程式設計的複雜度,但其能解決什麼問題,工作原理又是啥?

Actor模型

Actor模型起源於Carl Hewitt在1973年提出的作為併發計算的概念模型,這種形式的計算會同時執行多個計算。當時並沒有高度並行的計算機,但多核Cpu和分散式系統的最新進步使得Actor模型變得流行。

在Actor模型中,Actor是一個計算和狀態獨立的單元。Actors完全彼此隔離,它們永遠不會共享記憶體。Actors 使用訊息相互通訊。當一個Actor 收到訊息時,它可以更改其內部狀態,並將訊息傳送到其他 (可能是新的) Actors。

Actor模型使得編寫併發系統變得更簡單

,它提供了基於 turn-based 的 (或單執行緒) 訪問模型。多個Actors可以同時執行,但每個Actor 一次只處理一個接收的訊息。這意味著,在任何時候,都可以確保在Actors 中最多有一個執行緒處於活動狀態,這使得編寫正確的併發系統和並行系統變得更加容易。

Actor模型能解決啥問題

Actor 模型的實現通常繫結到特定語言或平臺。使用 Dapr Actor 構建塊可以從任何語言或平臺來使用 Actor 模型。

Dapr 的實現基於專案 “奧爾良” 中引入的虛擬Actor模式。對於虛擬Actor模式,不需要顯式的建立Actor。第一次將訊息傳送到Actor時,Actor將被隱式啟用並放置在群集中的節點上。當不執行操作時,Actor 會以靜默方式從記憶體中解除安裝。如果某個節點出現故障,Dapr 會自動將啟用的Actor 移到正常的節點。除了在Actor之間傳送訊息以外,Dapr Actor模型還支援使用計時器和提醒排程將來的工作。

雖然Actor模型提供了很大的優勢,但必須仔細考慮Actor的設計

。例如,如果多個客戶端呼叫相同的Actor,則會導致效能不佳,因為Actor 操作會按順序執行。下面的檢查清單是是否適用於 Dapr Actor的一些標準:

問題空間涉及併發性。如果沒有Actor,則需要在程式碼中引入顯式鎖定機制。

可以將問題空間分割槽為小、獨立和隔離的狀態和邏輯單元。

不需要低延遲的讀取Actor 狀態。因為Actor 操作是按順序執行,不能保證低延遲讀取。

不需要在一組Actor 之間查詢狀態。跨Actor 的查詢效率低下,因為每個Actor 的狀態都需要單獨讀取,並且可能會導致不可預測的延遲。

滿足這些條件的一種設計模式就是基於業務流程的saga或流程管理器設計模式。Saga管理必須執行的一系列步驟才能達到某些結果。Saga (或程序管理器) 維護序列的當前狀態,並觸發下一步。如果一個步驟失敗,saga可以執行補償操作。利用Actor,可以輕鬆處理 saga 中的併發,並跟蹤當前狀態。EShopOnDapr 參考應用程式使用 saga 模式和 Dapr Actor來實現排序過程。

工作原理

Dapr 提供了用於呼叫Actor 的 HTTP/gRPC API。

這是HTTP API的基URL:

http://localhost:daprPort/v1。0/actors/actorType/actorId/

daprPort: Dapr 偵聽的 HTTP 埠

actorType:執行元件型別

actorId:要呼叫的特定Actor的ID

Actor管理每個Actor的執行時間和位置,以及在Actor之間路由訊息的方式。如果一段時間未使用某個Actor,則執行時將停用該執行元件,並將其從記憶體中刪除。Actor所管理的任何狀態都將被保留,並在Actor 重新啟用時可用。Dapr 使用空閒計時器來確定何時可以停用Actor。當在Actor 上呼叫操作時 (透過方法呼叫或提醒觸發) ,會重置空閒計時器,並保持啟用執行元件例項。

Actor API 只是公式的一部分。服務本身還需要實現 API規範,因為你為Actor編寫的實際程式碼將在服務本身內執行。下圖顯示了服務和它之間的各種 API 呼叫:

Actor模型是如何讓編寫併發系統變得更簡單的?

actor服務和 Dapr Actor之間的 API 呼叫

為了提供可伸縮性和可靠性,將在Actor服務的所有例項中對actor進行分割槽。Dapr placement服務負責跟蹤分割槽資訊。啟動Actor服務的新例項時,會將支援的Actor 型別註冊到placement服務。placement 服務計算給定Actor型別的更新分割槽資訊,並將其廣播給所有例項。下圖顯示了將服務擴充套件到第二個副本時發生的情況:

Actor模型是如何讓編寫併發系統變得更簡單的?

Actor 處理單元編排服務 placement service

啟動時,Actor呼叫actor服務以獲取註冊的Actor型別和Actor的配置設定。

將註冊的Actor型別的列表傳送到placement 服務。

placement服務會將更新的分割槽資訊廣播到所有Actor服務例項。每個例項都將保留分割槽資訊的快取副本,並使用它來呼叫Actor。

由於actor是在各服務例項間隨機分發的,因此Actor 始終需要呼叫網路中的其他節點。

下圖顯示了在 Pod 1 中執行的ordering 服務例項呼叫ship OrderActor ID 為的例項的方法 3 。由於 ID 的actor 3 放在不同的例項中,因此將導致呼叫群集中的不同節點:

Actor模型是如何讓編寫併發系統變得更簡單的?

呼叫執Actor方法

服務在Actor上呼叫Actor API。請求正文中的JSON有效負載包含要傳送到Actor的資料。

使用placement 服務中的本地快取的分割槽資訊來確定哪個執行元件服務例項 (分割槽) 負責託管 ID 為的Actor 。在此示例中,它是 pod 2中的服務例項。呼叫將轉發到相應的例項 3。

Pod 2 中的例項呼叫服務例項以呼叫Actor。如果Actor尚未並執行Actor方法,則該服務例項將啟用該執行元件。

計時器和提醒 Timers and reminders

Actors 可以使用計時器和提醒來排程自身的呼叫。這兩個概念都支援配置截止時間。不同之處在於回撥註冊的生存期:

只要啟用Actor,計時器就會保持活動狀態。計時器 不會 重置空閒計時器,因此它們不能使Actor 處於活動狀態

提醒長於Actor啟用。如果停用了某個Actor,則會重新啟用該執行元件。提醒 將 重置空閒計時器

計時器是透過呼叫Actor API 來註冊的。在下面的示例中,在時間為0的情況下注冊計時器,時間為10秒。

curl -X POST http://localhost:3500/v1。0/actors///timers/ \ -H “Content-Type: application/json” \ -d ‘{ “dueTime”: “0h0m0s0ms”, “period”: “0h0m10s0ms” }’

此提醒將在5分鐘後激發。由於給定時間段為空,這將為一次性提醒。計時器和提醒均遵循turn-based 的訪問模型。當計時器或提醒觸發時,直到任何其他方法呼叫或計時器/提醒回撥完成後才會執行回撥。

State persistence

使用 Dapr 狀態管理構建塊儲存Actor 狀態。由於執行元件可以一輪執行多個狀態操作,因此狀態儲存元件必須支援多項事務。撰寫本文時,以下狀態儲存支援多項事務:

Azure Cosmos DB

MongoDB

MySQL

PostgreSQL

Redis

RethinkDB

SQL Server

若要配置要與Actors 一起使用的狀態儲存元件,需要將以下元資料附加到狀態儲存配置:

- name: actorStateStore value: “true”

下面是 Redis 狀態儲存的完整YAML示例:

apiVersion: dapr。io/v1alpha1kind: Componentmetadata: name: statestorespec: type: state。redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: “” - name: actorStateStore value: “true”

總結

Dapr actors 構建基塊可以更輕鬆地編寫正確的併發系統。actors 是狀態和邏輯的小單元。它們使用基於輪次的訪問模型,無需使用鎖定機制編寫執行緒安全程式碼。actors 是隱式建立的,在未執行任何操作時以無提示方式從記憶體中解除安裝。重新啟用actors 時,自動持久儲存並載入actors 中儲存的任何狀態。actors 模型實現通常是為特定語言或平臺建立的。但是,藉助 Dapr 執行元件構建基塊,可以從任何語言或平臺利用執行actors 模型。

Actor 支援計時器和提醒來排程將來的工作。計時器不會重置空閒計時器,並且允許Actor 在未執行其他操作時停用。提醒會重置空閒計時器,並且也會自動保留。計時器和提醒都遵守基於輪次的訪問模型,確保在處理計時器/提醒事件時無法執行任何其他操作。

使用 Dapr 狀態管理構建基塊持久儲存執行元件狀態。支援多項事務的任何狀態儲存都可用於儲存執行元件狀態。

參考資料:

https://www。cnblogs。com/shanyou/

https://docs。dapr。io/developing-applications/building-blocks/actors/

https://docs。dapr。io/reference/components-reference/supported-state-stores/