通過 Dapr 實現一個簡單的基於 dotnet 的微服務電商系統——一步一步教你如何擼 Dapr 之狀態管理
狀態管理和上一章的訂閱發佈都算是 Dapr 相較於其他服務網格框架來講提供的比較特異性的內容,今天我們來講講狀態管理。
目錄:
一、通過 Dapr 實現一個簡單的基於. net 的微服務電商系統
二、通過 Dapr 實現一個簡單的基於. net 的微服務電商系統 (二)——通訊框架講解
三、通過 Dapr 實現一個簡單的基於. net 的微服務電商系統 (三)——一步一步教你如何擼 Dapr
四、通過 Dapr 實現一個簡單的基於. net 的微服務電商系統 (四)——一步一步教你如何擼 Dapr 之訂閱發佈
附錄:(如果你覺得對你有用,請給個 star)
一、電商 Demo 地址:https://github.com/sd797994/Oxygen-Dapr.EshopSample
二、通訊框架地址:https://github.com/sd797994/Oxygen-Dapr
什麼是狀態?簡單來講就是數據狀態。比如我訪問 / api/user/1, 返回 {userid:1,name:xiaoming},無論我的實例有多少個,我通過接口訪問這個 url 都能夠得到該條信息。一般的設計中我們的應用是無狀態的,所謂無狀態就是每一個實例並不握持狀態 (排除緩存的情況),而是通過第三方組件可能是緩存組件也可能是數據庫來維持狀態。數據庫維持狀態這個大家都比較熟悉了畢竟都是靠 CRUD 起家的。另外一種則就是緩存狀態,緩存狀態有多種方式,最簡單以及最熟悉的就是我們 asp.net 的 session 以及 system.cache。到了. netcore 時代,微軟給我們很貼心的提供了 Extensions.Caching.xxx 來對我們的緩存提供一些外部支持(雖然在 fx 時代也有,不過相對比較麻煩)。而到了 dapr,則對其提供了更進一步的支持包括更廣泛的組件支持列表、非介入性 SDK 集成 (可直接通過 http 訪問)。通過這樣的方式讓我們的服務很容易對外提供併發安全的、一致性的狀態體驗。
狀態管理和上一章講到的訂閱發佈一樣,主要是依賴於 Dapr 強大的 Component 來連接 Dapr 適配的各種各樣的緩存中間件,同時對上層 (應用) 抽象爲一組 rest api 作爲讀 / 寫操作入口, 它的讀寫操作格式如下(僅列出部分,完整的 API 參考這裏):
GET http://localhost:<daprPort>/v1.0/state/<storename>/<key>
POST http://localhost:<daprPort>/v1.0/state/<storename>
DELETE http://localhost:<daprPort>/v1.0/state/<storename>/<key>
state 代表我們將調用 dapr 的狀態服務,storename 則是我們申明的類型爲 state 的 component,key 則是我們需要存取到 redis 的 kv 鍵值對 (值在 post body 中以 json 格式發送)
一份標準的狀態 component 如下(此處依然以 redis 爲例,查看這裏是所有支持列表):
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis.infrastructure.svc.cluster.local:6379
- name: keyPrefix
value: none
選擇 Dapr 爲我們託管狀態管理的好處是什麼呢?1、我們屏蔽了技術複雜性,避免了在基礎設施層去集成各種類型的狀態中間件 SDK,2、Dapr 爲我們實現了分佈式併發和數據一致性,具體來講在併發控制方面 Dapr 提供了一套 OCC 樂觀併發控制機制,通過附加的 etag 來做版本校驗確保用戶回寫過程和服務器端的版本一致才能操作。3、dapr 爲我們提供了 bulk 批處理,可以批量插入 / 刪除數據,這部分 demo 沒有涉及,大家可以看看這裏
同樣的我們來看看代碼,狀態管理相較比較簡單,首先我們還是打開之前的解決方案,在 RPC 項目裏創建一個 model,該 model 繼承一個 StateStore,主要是強制規範統一命名必須包含 key,data。
public class TestState : Oxygen.Client.ServerSymbol.Store.StateStore
{
public TestState()
{
Key = "TestState";
}
public override string Key { get; set; }
public override object Data { get; set; }
}
接着我們再在 ClientCallService 的構造函數引入 IStateManager 依賴,同時在 Call 方法中我們寫入一個狀態(其他代碼隨上一章內容不變)
private readonly IServiceProxyFactory serviceProxyFactory;
private readonly IStateManager stateManager;
public ClientCallService(IServiceProxyFactory serviceProxyFactory, IStateManager stateManager) {
this.serviceProxyFactory = serviceProxyFactory;
this.stateManager = stateManager;
}
public async Task<dynamic> Call()
{
var result1 = new OutDto();
var result2 = new OutDto();
var remoteService = serviceProxyFactory.CreateProxy<IHelloService>();
await stateManager.SetState(new TestState() { Data = new OutDto() { Word = "123" } });try
{
result1 = await remoteService.HelloWorld();
result2 = await remoteService.HelloWorldByName(new InputDto() { Name = "xiaoming" });
}
catch(Exception e)
{
}
return new { result1, result2 };
}
接着我們在 servicesample 打印出來:
var result = await stateManager.GetState<OutDto>(new TestState());
Console.WriteLine(result.Word);
啓動項目,打開 postman 訪問並打印控制檯,可以看到狀態被正確的從 clientsaample 寫入,並從 servicesample 讀取打印到了控制檯上(這裏注意如果不想狀態被其他服務讀寫也就是僅能在當前服務的 scope 內被讀寫可以在設置 component 時刪除 keyPrefix 節即可)
狀態管理就講到這裏,整體使用上比較簡單,開發者只需要考慮持久化設備的可用性以及可擴展性,其他都可以交給 Dapr 即可。
今天補一個小的功能點,在 oxygen 框架中我爲 AOP 提供了一個入口,可以在 ConfigureServices 時通過 LocalMethodAopProvider 這個靜態類的 RegisterPipelineHandler 方法註冊請求前、方法前、方法後、方法異常四個匿名委託。請求前主要是注入了一個 OxygenHttpContextWapper 的包裝器類,該類包含了原始請求中的 path/header/cookie 等等原始 data, 並提供了一個當前請求的 ILifetimeScope 用於用戶進行對象注入。在方法前則提供了一個 object 類型的入參,方便用戶做方法前校驗。方法後則是攔截的方法體返回的 result。而異常則是方法內拋出的所有 unhandle 異常都會被這個委託捕獲,方便用戶統一處理。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ra9-pQnnSjXqBQ4OEnAnNQ