計解析:從 API-002 決策記錄看狀態(tài)化 Actor 編程模型的一等公民化)
Dapr Actor API 設(shè)計解析從 API-002 決策記錄看狀態(tài)化 Actor 編程模型的一等公民化【免費下載鏈接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.項目地址: https://gitcode.com/GitHub_Trending/da/dapr導(dǎo)讀本文基于 Dapr 倉庫中的設(shè)計決策記錄 API-002: Actor API design系統(tǒng)講解 Dapr 如何將 Actor 提升為運行時的一等公民first-class citizen包括獨立的 Actor 接口設(shè)計、多 Reminder/Timer 支持、狀態(tài)訪問方法內(nèi)聚、批量狀態(tài)更新與 Actor 刪除語義等關(guān)鍵決策。通過對照當(dāng)前倉庫中的 HTTP API 路由定義、Actor 運行時接口與狀態(tài)/定時器實現(xiàn)源碼讀者可以理解這些設(shè)計決策如何落地為可調(diào)用的actors/{actorType}/{actorId}/...API 端點并掌握面向 Service Fabric 狀態(tài)化 Actor 用戶遷移場景的編程模型要點。背景為什么 Dapr 需要正式的 Actor API決策上下文當(dāng) Dapr 計劃隨各語言推出官方 Actor SDK 時團隊面臨一個關(guān)鍵架構(gòu)問題Actor 此前更多是作為運行時的一個附屬能力存在而要讓各語言 SDK 有統(tǒng)一、穩(wěn)定的編程契約就必須在 Dapr 內(nèi)核中正式引入 Actor API使 Actor 成為 Dapr 中的一等公民API-002 決策記錄。決策記錄明確提出了本次評審的核心目標(biāo)確保 Dapr 能夠強力支持Service Fabric 狀態(tài)化 Actor 編程模型從而為大多數(shù)既有 Actor 用戶提供遷移路徑。這意味著 Dapr 的 Actor 設(shè)計并不是憑空發(fā)明新模型而是以業(yè)經(jīng)驗證的 Service Fabric Reliable Actors 語義為基準(zhǔn)讓遷移用戶能夠以最小的心智成本平移到 Dapr 上。這一背景決定了后續(xù)所有 API 決策的走向獨立的 Actor 接口、單線程保證、狀態(tài)內(nèi)聚、批量更新與刪除語義都是在對齊狀態(tài)化 Actor這一編程范式。編程模型對齊的落地證據(jù)從當(dāng)前倉庫的運行時接口可以看到Actor 子系統(tǒng)的確以獨立、內(nèi)聚的方式存在于 Dapr 運行時中。在 pkg/actors/actors.go 中定義了完整的 Actor 運行時Interface涵蓋初始化、運行、路由、狀態(tài)、定時器、Reminder、Placement 等子能力// pkg/actors/actors.go type Interface interface { Init(InitOptions) error Run(context.Context) error Router(context.Context) (router.Interface, error) Table(context.Context) (table.Interface, error) State(context.Context) (actorstate.Interface, error) Timers(context.Context) (timers.Interface, error) Reminders(context.Context) (reminders.Interface, error) Placement(context.Context) (placement.Interface, error) RuntimeStatus() *runtimev1pb.ActorRuntime RegisterHosted(context.Context, hostconfig.Config) error UnRegisterHosted(ctx context.Context, actorTypes ...string) error WaitForRegisteredHosts(ctx context.Context) error OnActorStateStoreChanged() }其中State、Timers、Reminders分別返回獨立的狀態(tài)、定時器與 Reminder 接口這與決策記錄中Actor 應(yīng)支持多個 Reminder 和多個 Timer以及狀態(tài)訪問方法封裝在 Actor 接口之內(nèi)的決策一一對應(yīng)——這些能力不是散落在運行時各處的旁路邏輯而是 Actor 接口自身的組成部分。核心設(shè)計決策逐條解析決策一定義獨立的 Actor 接口決策Dapr 定義一個獨立的 Actor 接口A separate Actor interface is defined。解讀Actor 不應(yīng)被當(dāng)作附帶的狀態(tài)存取或特殊的服務(wù)調(diào)用來處理而應(yīng)擁有自己獨立的 API 契約與生命周期管理。獨立接口帶來兩個直接收益各語言 SDK 可以圍繞同一套語義實現(xiàn)避免 SDK 之間行為漂移運行時可以針對 Actor 的特性狀態(tài)、激活/停用、單線程調(diào)度做專門優(yōu)化而不污染通用服務(wù)調(diào)用路徑。在 HTTP API 層這一點體現(xiàn)為/v1.0/actors/{actorType}/{actorId}/...這一獨立的 URL 命名空間。見 pkg/api/http/actors.go 中的端點構(gòu)造// pkg/api/http/actors.go { Methods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}, Route: actors/{actorType}/{actorId}/method/{method}, Version: apiVersionV1, Group: endpoints.EndpointGroup{ Name: endpoints.EndpointGroupActors, Version: endpoints.EndpointGroupVersion1, AppendSpanAttributes: appendActorInvocationSpanAttributesFn, MethodName: methodNameFn, }, Handler: a.onDirectActorMessage, Settings: endpoints.EndpointSettings{ Name: InvokeActor, }, },同時請求對象層面也保持了獨立的類型體系。在 pkg/actors/api/api.go 中每個 Actor 請求都定義了ActorID、ActorType字段并通過DaprSeparator||拼接出全局唯一的 Actor 鍵// pkg/actors/api/api.go const ( DaprSeparator || ) // ActorHostedRequest is the request object for checking if an actor is hosted on this instance. type ActorHostedRequest struct { ActorID string json:actorId ActorType string json:actorType } func (r ActorHostedRequest) ActorKey() string { return r.ActorType DaprSeparator r.ActorID }ActorKey()的ActorType || ActorID拼接方式是整個 Actor 查找、路由、狀態(tài)隔離的基礎(chǔ)鍵也體現(xiàn)了Actor 類型 Actor ID這一狀態(tài)化 Actor 模型的核心尋址思想。決策二Actor 支持多個 Reminder 和多個 Timer決策Actor 應(yīng)支持多個 Reminder 和多個 TimerActors should support multiple reminders and timers。解讀Timer 與 Reminder 是狀態(tài)化 Actor 模型的兩類定時觸發(fā)機制兩者語義不同Timer定時器純粹的內(nèi)存中回調(diào)actor 激活期間有效去激活即丟失開銷低、延遲低Reminder提醒持久化在狀態(tài)存儲中即使 actor 或宿主進(jìn)程重啟到期后仍會觸發(fā)適合需要可靠周期性執(zhí)行的場景。決策要求兩者都支持多個即同一個 Actor 實例可以同時注冊多個不同名稱的 Timer/Reminder互不干擾。倉庫中的 HTTP 端點完整地實現(xiàn)了多 Timer 多 Reminder的注冊/注銷/查詢能力pkg/api/http/actors.go方法路由端點名說明POST / PUTactors/{actorType}/{actorId}/reminders/{name}RegisterActorReminder創(chuàng)建/更新一個 ReminderPOST / PUTactors/{actorType}/{actorId}/timers/{name}RegisterActorTimer創(chuàng)建/更新一個 TimerDELETEactors/{actorType}/{actorId}/reminders/{name}UnregisterActorReminder刪除指定 ReminderDELETEactors/{actorType}/{actorId}/timers/{name}UnregisterActorTimer刪除指定 TimerGETactors/{actorType}/{actorId}/reminders/{name}GetActorReminder查詢單個 Reminder每個端點都以{name}作為資源標(biāo)識天然支持一個 Actor 掛載多個定時任務(wù)注冊接口同時支持 POST 與 PUT方便冪等更新。在運行時層面Reminder 與 Timer 被拆分為獨立的接口子系統(tǒng)pkg/actors/reminders、pkg/actors/timers、pkg/actors/internal/timers并通過pkg/actors/actors.go的Interface暴露為Timers(ctx)與Reminders(ctx)兩個入口。從源碼結(jié)構(gòu)看Timer 的存儲與執(zhí)行internal/timers/inmemory與 Reminder 的持久化調(diào)度依賴 scheduler/placement 體系是兩條獨立實現(xiàn)路徑這也印證了決策中分別支持而非混為一談的設(shè)計取向。決策三狀態(tài)訪問方法封裝在 Actor 接口之內(nèi)決策Actor 狀態(tài)訪問方法被封裝在 Actor 接口自身Actor state access methods are encapsulated in the Actor interface itself。解讀這是Actor 是一等公民在接口設(shè)計上的直接體現(xiàn)——狀態(tài)不是 Actor 外部的一個附加存儲服務(wù)而是 Actor 接口的固有組成。SDK 使用者直接通過 Actor 對象或 Actor 運行時 API讀寫狀態(tài)而無須關(guān)心底層 state store 的細(xì)節(jié)。具體到運行時狀態(tài)訪問統(tǒng)一收斂到actorstate包并通過actors.Interface.State(ctx)暴露pkg/actors/actors.go。請求模型在 pkg/actors/api/api.go 中定義了完整的狀態(tài)操作類型族GetStateRequest單鍵讀取GetBulkStateRequestBulkStateResponse批量讀取SaveStateRequest單鍵寫入DeleteStateRequest單鍵刪除StateResponse讀取結(jié)果攜帶Data、Metadata與ETag。值得注意的是StateResponse中的ETag字段pkg/actors/api/api.go// StateResponse is the response returned from getting an actor state. type StateResponse struct { Data []byte json:data Metadata map[string]string json:metadata // ETag is the state stores row-version token for the returned value, when // the store supports it. Callers that perform optimistic concurrency must // pass it back unchanged on the next TransactionalUpsert/Delete to detect // concurrent writes. ETag *string json:etag,omitempty }ETag 支持樂觀并發(fā)控制為后續(xù)的狀態(tài)一致性保障提供了底層基礎(chǔ)。HTTP 層對應(yīng)的單鍵讀取端點為GET actors/{actorType}/{actorId}/state/{key}端點名GetActorState。決策四單次操作批量更新一組鍵值狀態(tài)決策Actor 接口應(yīng)支持在單次操作中更新一組鍵值狀態(tài)Actor interface shall support updating a group of key-value states in a single operation。解讀狀態(tài)化 Actor 的常見場景是在一次方法調(diào)用中同時更新多個狀態(tài)字段例如同時更新余額與交易流水。如果只能逐鍵寫入既增加網(wǎng)絡(luò)往返也無法保證這組更新的整體性。因此決策要求 Actor 接口提供批量狀態(tài)更新原語。在 HTTP 層這一能力落地為POST / PUT actors/{actorType}/{actorId}/state端點端點名ExecuteActorStateTransaction見 pkg/api/http/actors.go請求體為事務(wù)操作數(shù)組支持 Upsert插入或更新與 Delete 兩種操作類型例如[ { operation: upsert, request: { key: balance, value: 100 } }, { operation: delete, request: { key: temp-cache } } ]該端點由onActorStateTransaction處理器承接內(nèi)部將一組操作作為一個整體提交給 actor 狀態(tài)存儲。批量語義與決策記錄中在單次操作中更新一組鍵值狀態(tài)的要求完全吻合同時也呼應(yīng)了決策記錄中事務(wù)相關(guān)的討論——批量狀態(tài)更新本身就是一種輕量級的事務(wù)邊界比跨多次 API 調(diào)用的分布式事務(wù)成本低得多詳見下文非 Dapr 側(cè)決策。決策五Actor 刪除語義——讓在途事務(wù)完成決策Actor 接口應(yīng)支持刪除一個 Actor。如果在調(diào)用刪除方法時該 Actor 正處于激活狀態(tài)則允許在途事務(wù)先完成隨后 Actor 被停用deactivated、刪除并清除其關(guān)聯(lián)狀態(tài)API-002 決策記錄。解讀這是決策記錄中最精細(xì)的一條語義約定它回答了一個棘手問題刪除 Actor 時正在執(zhí)行的邏輯怎么辦選擇允許在途事務(wù)完成意味著不粗暴中斷已進(jìn)入 Actor 方法執(zhí)行的請求不會被硬性取消避免半更新狀態(tài)或臟數(shù)據(jù)有序收斂在途邏輯結(jié)束后Actor 進(jìn)入停用流程再執(zhí)行刪除并清理全部關(guān)聯(lián)狀態(tài)狀態(tài)徹底清理刪除不僅移除 Actor 實例還移除其持久化狀態(tài)避免殘留孤兒狀態(tài)。在倉庫實現(xiàn)中Actor 實例的生命周期由 actor table 管理pkg/actors/table/table.go其Interface暴露了GetOrCreate(actorType, actorID)、ActorExists(actorType, actorKey)、RegisterActorTypes、HaltAll、HaltNonHosted等生命周期與托管管理方法。從源碼結(jié)構(gòu)看Actor 實例的激活/停用、托管類型注冊與去注冊、以及宿主hosting的掛起/恢復(fù)SuspendHosting/ResumeHosting都被統(tǒng)一收斂到這張 table 上為先完成在途邏輯、再停用、再刪除的語義提供了承載結(jié)構(gòu)。需要說明的是刪除 Actor 的完整 HTTP/客戶端 API 形態(tài)在后續(xù)版本中持續(xù)演進(jìn)但決策記錄確立的語義契約在途事務(wù)完成 → 停用 → 刪除 → 清理狀態(tài)始終是各語言 SDK 刪除接口必須遵守的行為基準(zhǔn)。非 Dapr 側(cè)決策跨 API 調(diào)用的分布式事務(wù)暫緩決策跨多個 API 調(diào)用的事務(wù)邊界留給未來版本如果被證明必要的話。由于 Actor 具備單線程保證single-threaded guarantee這種事務(wù)范圍可能并不必要。然而如果開發(fā)者期望 Actor 代碼在隱含事務(wù)范圍內(nèi)原子性地運行我們可能不得不實現(xiàn)它API-002 決策記錄。解讀這是決策記錄中最具前瞻性的部分值得展開為什么可能不需要Actor 模型的核心保證是同一 Actor 實例的方法調(diào)用被串行執(zhí)行單線程語義。在沒有并發(fā)交錯的情況下一個方法內(nèi)對狀態(tài)的多次讀寫天然不會互相干擾因此為每一次 Actor 方法調(diào)用隱式開啟事務(wù)的收益有限——單線程本身已經(jīng)消除了大部分競態(tài)為什么又可能必須做單線程保證只覆蓋同一 Actor 內(nèi)部的交錯并不能保證崩潰原子性——如果在方法執(zhí)行到一半時進(jìn)程崩潰狀態(tài)可能停留在中間態(tài)。如果開發(fā)者把 Actor 方法視為要么全部生效、要么全不生效的原子單元就需要真正的事務(wù)日志支持。從當(dāng)前實現(xiàn)看Dapr 通過兩個機制部分回應(yīng)了這一權(quán)衡單線程調(diào)度Actor 路由與調(diào)度層保證每個 Actor 實例的方法調(diào)用串行執(zhí)行這正是決策記錄中單線程保證的落地批量狀態(tài)事務(wù)決策四ExecuteActorStateTransaction允許一組狀態(tài)更新作為一個整體提交提供了方法內(nèi)的輕量事務(wù)邊界是分布式事務(wù)的一種低成本替代。這一決策的實際效果是Dapr 優(yōu)先保障模型簡單與性能把跨 API 的強事務(wù)語義作為演進(jìn)選項保留而非一開始就背上分布式事務(wù)的復(fù)雜度包袱。決策影響與遷移價值Consequences為 Service Fabric 用戶鋪路決策記錄給出的核心結(jié)論是Dapr 可以強力支持 Service Fabric 狀態(tài)化 Actor 編程模型從而為大多數(shù)既有 Actor 用戶提供遷移路徑。這一結(jié)論在倉庫中得到了系統(tǒng)性落實。綜合前文可以看到從 pkg/actors/actors.go 的運行時接口到 pkg/api/http/actors.go 的 HTTP 端點再到 pkg/actors/api/api.go 的請求/響應(yīng)模型Dapr 構(gòu)建了一條完整的 Actor 能力鏈路HTTP/客戶端 SDK │ 通過 /v1.0/actors/{actorType}/{actorId}/... 調(diào)用 ▼ pkg/api/http/actors.go端點注冊與參數(shù)解析 │ ▼ pkg/actors/actors.goActor 運行時 InterfaceRouter / State / Timers / Reminders / Table │ ├── pkg/actors/tableActor 實例生命周期、激活/停用、托管管理 ├── pkg/actors/state狀態(tài)訪問批量 upsert/deleteETag 樂觀并發(fā) ├── pkg/actors/reminders持久化 Reminder 調(diào)度 └── pkg/actors/timers內(nèi)存 Timer 執(zhí)行對于從 Service Fabric 遷移的開發(fā)者這套模型提供了他們熟悉的要素ActorType ActorId的尋址方式、方法調(diào)用端點method/{method}、多 Reminder/Timer、內(nèi)聚的狀態(tài)訪問、批量狀態(tài)更新與明確的刪除語義——遷移時主要工作被壓縮為把 Actor 代碼平移到 Dapr SDK 的編程模型而無需重新設(shè)計架構(gòu)。設(shè)計取舍的啟示回顧 API-002 的六條決策可以提煉出 Dapr Actor API 設(shè)計的三個方法論以成熟模型為基準(zhǔn)對齊 Service Fabric 狀態(tài)化 Actor 編程模型降低遷移門檻而非另起爐灶接口內(nèi)聚、能力獨立狀態(tài)、Timer、Reminder 全部封裝進(jìn) Actor 接口同時各自擁有獨立實現(xiàn)子系統(tǒng)兼顧統(tǒng)一契約與模塊解耦語義先行、復(fù)雜度后置刪除語義、事務(wù)范圍等棘手問題先在決策層面定下行為契約在途事務(wù)完成后再刪除跨 API 事務(wù)暫緩讓實現(xiàn)與 SDK 有據(jù)可依避免語義漂移。延伸閱讀決策記錄原文docs/decision_records/api/API-002-actor-api-design.md以及同目錄下其他 API 決策狀態(tài)存儲 API、批量狀態(tài) API、消息命名等可對照閱讀API-001-state-store-api-design.md、API-008-multi-state-store-api-design.mdActor 運行時接口與生命周期pkg/actors/actors.goActor HTTP API 端點定義pkg/api/http/actors.goActor 請求/響應(yīng)類型與狀態(tài)模型含 ETag 樂觀并發(fā)pkg/actors/api/api.goActor 實例托管與激活/停用pkg/actors/table/table.go端到端測試示例tests/e2e/actor_state/actor_state.go、tests/e2e/actor_reminder/actor_reminder.go【免費下載鏈接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.項目地址: https://gitcode.com/GitHub_Trending/da/dapr創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考