1. 程式人生 > 其它 >資料管理類設計與實現

資料管理類設計與實現

在開發新需求時,需要基於現有資料管理類進行擴充套件,在完成該專案的過程中,發現需要更加全面深入的瞭解,因此有了本篇文章,全面分析現有資料管理類的設計思路,分析現有設計優缺點,為後續改進提供方向。

背景介紹

上層業務繁多,各個業務和各個資料之間形成網狀交叉,存在多個業務方依賴相同資料的場景,如果不加以統一管理,會增加流量消耗,減慢響應速度。

較為理想的資料請求邏輯是,同一類資料請求在只維護一個例項,各個業務方按需請求,支援資料快取以及釋出訂閱。

為了達到上述目標,抽象出 資料管理類,它的核心職責如下:

  1. 管理資料請求的全生命週期:包括建立請求、傳送請求、取消請求以及響應請求
  2. 管理請求快取:同類型資料已存一份,對業務方提供統一讀取介面
  3. 支援釋出訂閱:通過定時驅動資料查詢,並主動推送給各業務方

整體設計

通過梳理現有實現,去掉無關程式碼,提煉出如下類圖:

核心類為 CDataManagerBase,每類業務資料的管理,都需要以它為父類,並納入到 CDataAskDispatch 類中,以便融入現有的請求響應處理框架和定時處理框架。

上圖中的 CLgtDataManager 類,就是負責 Lgt 業務資料的具體類,資料快取以及讀寫介面在該類中提供。

核心介面

從業務使用方來看,核心介面有以下五個:

  • CanDealAsk: 決定自身能否處理該請求
  • DealCommitAsk:處理請求傳送
  • DealCancelAsk
    :處理請求取消
  • OnReply: 處理請求響應
  • OnTime: 處理定時任務

接下來分析如何通過核心介面,實現上面提到的三個核心職責

管理資料請求的全生命週期

  • 請求傳送

在專案中,使用 引用計數 (具體由 CReferenceBase 類實現)來管理請求的生命週期。由各個業務方建立請求,轉交給資料管理類。這裡需要注意,資料管理類是直接使用業務方的請求還是根據業務方資料建立新的請求?

剛開始實現時,筆者採用直接使用業務方的請求,增加引用後儲存起來的方法。初版實現提交後,經過評審,上級提出這樣不妥,原因如下:

  1. 管理類持有上層業務方的請求引用
    • 不採用引用計數儲存。那麼如果上層介面銷燬該指標,那麼管理類就持有無效指標,這是非常危險的。
    • 採用引用計數儲存,則請求在管理類中始終保留一份引用,業務方失去了對該請求生命週期的掌控,業務方釋放該請求,本以為釋放了,其實還沒釋放,這個會帶來隱藏的記憶體洩露。

因此,採用方案2,根據業務方的請求資料,資料管理類建立自己的請求。這樣一來,與業務方解耦。業務方對請求的管理能做到一一對應。

晚上回去想了想,這裡想要達到的是資料層持有一份ask,但又不增加對應引用,std::weak_ptr可以用在這個場景中。但 std::weak_ptr 是配合 std::shared_ptr 來使用的,目前專案中沒有使用智慧指標,所以,此方案作罷。

  • 請求取消

業務方可以隨時取消請求,這裡可進一步細分為 取消請求響應 和 取消請求訂閱。

  1. 取消請求響應

    • 該功能由請求響應框架負責。當取消請求響應時,框架會刪除底層請求與傳送方的關聯關係,當響應資料到了,由於找不到響應資料對應的傳送方,也就無法通知傳送方。
  2. 取消請求訂閱

    • 由實際的資料管理類實現。當取消請求訂閱時,會根據請求,找到對應的訂閱例項並刪除。這樣,當訂閱請求的響應資料到達時,找不到對應的訂閱例項,無法通知訂閱方。

看了下,在目前的取消實現中,只處理了情況2,未處理情況1,可能會造成不必要的資料請求,在後續實現中可以改進。

  • 請求響應

請求響應,正常來說是哪個業務方發起的請求,響應資料就回調給哪個業務方。這個由請求響應框架負責,在資料管理類中不用考慮。

管理請求快取

不同業務方對於資料的時效性不同,有的只接受最新資料,有的接收記憶體快取資料,有的接收硬碟快取資料。因此,需要在請求時,根據不同時效性要求,選擇不同的快取方案。

  1. 只接收最新資料: 資料管理類不進行資料快取,任何請求來了發給後臺。
  2. 接受記憶體快取資料:當有資料請求時,如記憶體中有快取資料,則先返回給業務方,同時,開啟定時請求,並推送資料給業務方。
  3. 接受硬碟快取資料:程式啟動時,讀取硬碟資料來初始化記憶體快取,後續流程同2)。

這裡需要注意:不能假定快取資料的生產者和消費者在同一執行緒,因此,對快取的操作要加鎖,為進一步提高效率,可進行如下優化:

  1. 考慮到快取資料是讀多寫少,採用讀寫鎖,提高加鎖效率。
  2. 一條資料有很多子項,業務方存在同時使用所有子項,也存在在不同函式使用不同子項,為減少加鎖消耗,可進一步做快取,針對每條資料,首次訪問時加鎖,後續再訪問其中子項時,不再加鎖;訪問新的資料時再加鎖。
  3. 快取持久化。根據時效性策略,將響應資料持久化到硬碟。

支援釋出訂閱

資料管理類對每個業務方的請求,自動增加訂閱功能。如果業務方不希望訂閱,可在響應後取消訂閱。

具體的釋出功能,通過每個指令設定好的定時間隔,配合定時驅動來實現,當同類資料響應後,通知各個訂閱方,以達到節省流量和實時通知。

為提高效率,可對釋出/訂閱機制做如下設計:

  1. 當業務方在訂閱響應中進行取消訂閱時,此時不能刪除訂閱例項,而是要等響應完成後再刪除。
  2. 同類資料,至少有1個業務方繼續訂閱,就要定時請求並通知。無業務方時,不再請求
  3. 訂閱查詢超時間隔設定設計:
    4.1 當訂閱請求響應錯誤時,可縮短定時間隔進行重試,若失敗到設定次數,不再請求,等待設定時間後恢復請求。
    4.2 當訂閱請求響應成功時,更新下次定時間隔,使其達到上次響應與下次實際請求之間的時間等於設定的時間間隔。
    4.3 當訂閱請求尚未回覆時,如果超出設定的超時時間,認定為響應出錯,轉流程4.1處理。
  4. 考慮時間記錄值迴轉

一般通過比較當前時間與上次請求傳送時間是否超過時間間隔,來決定請求傳送。考慮這種情況,程式執行很長時間,當前時間記錄值已溢位了,小於上次請求傳送時間,此時可以這樣處理。

當發生溢位時,我們只需要知道 當前時間 與 上次傳送時間之間的間隔絕對值,只要間隔絕對值超過設定的值,就需要重發。

// nLastAskTime 為上次傳送時間. curTime 為當前時間  nSetAskInterval 為定時間隔
unsigned long nInterval = unsigned long(-1) - nLastAskTime + curTime;
if (nInterval > nSetAskInterval)
{
	bNeedReSend = true;
}


unsigned long(-1) - nLastAskTime是求出尚未溢位的值距離最大數值的距離,加上當前時間 curTime,得到實際的時間間隔,如果大於設定的時間間隔,則需要重發請求。

小結

本文總結了資料管理類設計的方案要點,一個良好的資料管理類,需要完成以下職責:

  1. 管理資料請求的全生命週期,包括髮送請求、取消請求以及響應請求
  2. 管理請求快取
  3. 支援釋出訂閱

經過上述分析和思考,資料管理模組未來可從以下幾點改進:

  1. 更精細化的訂閱請求管理。例如:支援同類請求不同的訂閱間隔等
  2. 更好的可擴充套件性。
    • 將通用操作提升到基類,提供更多輔助函式,降低開發新業務資料管理類時的心智負擔。
    • 實現通用資料管理類,資料型別由外部注入,降低重複程式碼。
  3. 增加更完善的日誌記錄,方便維護和查問題。