大資料量下查詢顯示優化方案小結
阿新 • • 發佈:2020-03-19
# 大資料量下查詢顯示優化方案小結 #
最近工作中,遇到了優化大批量資料查詢和顯示的問題,資料量在10W級別。經過反覆設計和討論,最終得到優化到了較為滿意的效果,在此記錄小結下,在解決此類問題中的思考。
## 問題背景說明 ##
通常情況下,使用者查詢資料量不超過1千條,但有幾個大戶,通過某種方式,生成了上萬級別的資料,前端未針對大資料量的查詢和顯示進行優化,導致該介面顯示卡頓、白屏、點選無響應、顯示總量和實際總量不一致等等問題。
## 總體思路 ##
大資料量查詢和顯示,可從以下三個方面來優化:
* **定時分批查詢**
* **快取中心**
* **UI優化**
以上三層分別對應資料層、中間層和UI層,每層優化方案可單獨使用,也可以結合使用。建議結合使用。
### 定時分批查詢 ###
資料後臺提供的查詢指令是支援分頁查詢的,與其相關的欄位有:
* 查詢方向 **qryDir**, 定位查詢方向,1為往下查,0為往上查
* 請求行數 **qryCount**,說明期望查詢的個數
* 定位串 **qryPositionStr**, 查詢定位串,用於定位查詢起點
基於上述三個欄位,系統可以實現分頁查詢功能,但具體到UI層,因為無法獲知該使用者資料總量,也就無法推導總頁數資訊,UI層無法以分頁形式展示。如果後臺提供資料總量欄位,那就可方便實現上一頁、下一頁、最後一頁等功能。現有後臺沒提供總量資訊,無法在UI層做分頁查詢,只在資料層做分頁查詢。
在資料層做分頁查詢時,對UI層是透明的。也就是說,當資料層尚未查詢完畢時,UI層是不會收到響應,還可以繼續操作介面。這裡可以優化,同產品討論以下兩種處理方式:
* 方案一:本次查詢尚未返回,禁止UI層重複查詢
* 方案二:本次查詢尚未返回,支援UI層重複查詢
目前的設計採用方案二,因此需要資料層考慮重複請求的場景。除了重複請求之外,資料層還需要考慮後臺限制和快取管理,小結起來有以下三點:
1. 後臺限制
2. 快取管理
3. 重複查詢
下面分別闡述資料層針對上述問題的設計方案。
1. 後臺限制
任何後臺服務能夠提供的功能都是有限制要求的。資料後臺對查詢指令有單次最大條數和查詢頻率的限制,具體限制如下:
* 單次查詢最大請求行數建議1000條
* 查詢頻率限制在5秒15筆查詢,超頻查詢會報錯
為了在不影響後臺穩定性的情況下,儘可能快地查詢回所有資訊。在查詢過程中,除了常規的分頁查詢之外,還額外增加定時查詢機制,具體設計為,每連續發起3筆分頁查詢,等待半秒後,繼續查詢,迴圈往復,直到所有查詢資料都返回,每次分頁查詢請求條數設定後臺推薦的最大值。上述每3筆休息半秒需要根據後臺具體限制來設定,考慮到系統還有其他查詢指令在同時進行,設定寬鬆些比較合適。**要明確這一點,在分頁查詢過程中觸發流量超限錯誤,那麼之前已查詢的資料都會無效,要慎重設定定時間隔,寧可慢一點,不能出錯。**
2. 快取管理
此處的快取指的是資料層的快取,而不是快取中心的快取。此處的快取用於儲存分頁查詢返回的資料,等到所有查詢完畢,再彙總往上傳遞。這裡要注意的點,是要區分以下兩種情況:
* 由快取中心主動發起的分頁查詢
* 由資料層發起的分頁查詢
上述兩種情況,單靠分頁查詢標誌無法區分,需要增加額外的標識資訊來區分,在快取中心需要根據此標誌進行查詢資料的合併和過濾。
3. 重複查詢
當資料層查詢尚未完成,又有重複查詢的情況,按照場景可分為
* UI層發起新的全量查詢
* 快取中心定時發起增量查詢
針對UI層發起新的全量查詢,作為資料層有兩種應對策略:
* 方案一:本次查詢尚未完成,拒絕新的查詢
* 方案二:本次查詢尚未完成,允許新的查詢,同時清空上次已查詢得到的資訊
由於資料層是服務提供方,提供給UI層和快取中心層使用,由於UI層支援重複查詢,快取中心層支援定時查詢,所以資料層採用第二種方案。
針對快取中心定時發起增量查詢,當上一次全量或者增量查詢尚未返回時,快取中心要禁止新的增量查詢請求。
### 快取中心 ###
為了管理快取以及提供快取資料介面,設計快取中心,它介於UI層和資料層之間,UI層通過訂閱資料訊息,來獲得響應資料。查詢中心在查詢到資料後,通過釋出通知的方式,更新介面資料。
在快取中心中,對外提供兩個公共介面:
* ForceRefreshData: 主動重新獲取全量資料
* GetDataByAccount:按照賬號獲取全量資料
在快取中心內部,通過定時器來觸發分頁查詢。當查詢完畢時,通過訂閱釋出機制向有需要的介面推送資料,推送的資料既包括全量資料,也包括增量資料,便於UI層使用。
在快取中心處理過程中,增量資料的來源有兩處:
* 定時增量請求
* 兩次全量請求
無論是哪種來源,都需要和已有全量查詢結果進行對比,得出增量資料。當資料量很大時,從新的全量查詢和已有全量查詢中對比獲得增量資料,時間複雜度很高,比如全量有5W資料,新的全量有6W資料,那麼從6W資料中去掉已有的5W資料,得到增量1W資料,雙重迴圈的話,會操作6W*5W次,可通過`stl::set`結構來加速過濾篩選,其中的`key`設定為可唯一標識資料的屬性組合即可。
快取中心要考慮增量查詢和全量查詢衝突的問題,也就是說,要考慮以下表格中的四種場景:
| 當前的查詢場景 | 增量查詢 | 全量查詢 |
| -------- | -----: | :----: |
| 增量查詢 | 增量-增量 | 增量-全量 |
| 全量查詢 | 全量-增量 | 全量-增量 |
在具體實現時,建議快取中心中的增量查詢和全量查詢複用同個請求,儲存一份全量資料成員即可。
### UI優化 ###
UI優化可分為互動優化和重新整理優化兩個方面,以下分別來描述。
## 互動優化 ##
為減少可能的重複全量查詢,可在UI層提示使用者,該介面會主動接受資料推送(實際實現,可能是定時查詢、也可以是接收後臺主推),無需頻繁查詢,但不禁止使用者主動重新整理。
另外的話,在使用者主動重新整理後,介面增加查詢定時器,告知使用者已查詢耗時時間,已查詢到的資料量,該資料量可按照每次查詢1000條,根據底層邏輯來大概預估已查詢到的資料條數,等到資料層全部查詢完畢後,顯示最終準確數值。
## 重新整理優化 ##
本次優化是在`Windows`系統上,使用`CListCtrl`控制元件的`Report`模式來顯示資料。重新整理優化可以從以下三點出發:
* 虛表模式賦值
* 介面資料增量重新整理
* 介面顯示區域性重新整理
虛表模式賦值,是MFC中提供的加快列表控制元件顯示速度的方案,具體參見 [CListCtrl虛擬列表技術](https://blog.csdn.net/qq_23992597/article/details/83010310),此處不再敘述。
介面增量重新整理,是指在UI層維護當前顯示內容快取,當有資料更新時,通過比較更新資料和當前快取,決定是否更新列表。基於上述快取中心,可通過增量更新來優化判斷流程。
介面區域性重新整理,是指只針對當前介面顯示部分進行重新整理,未顯示部分不進行重新整理。介面區域性重新整理涉及到的函式有以下三個:
* [int GetTopIndex()](https://docs.microsoft.com/en-us/cpp/mfc/reference/clistctrl-class?view=vs-2017#gettopindex) 獲得當前顯示區域最頂部Item的位置索引
* [int GetCountPerPage()](https://docs.microsoft.com/en-us/cpp/mfc/reference/clistctrl-class?view=vs-2017#getcountperpage) 獲得當前控制元件顯示區域最多能夠顯示Item的個數
* [BOOL RedrawItems(int nFirst, int nLast)](https://docs.microsoft.com/en-us/cpp/mfc/reference/clistctrl-class?view=vs-2017#redrawitems) 重繪位置索引在nFirst和nLast區間中的Item
具體使用很簡單,在重繪時,只需要以下兩句話搞定:
```
int nFirst = GetTopIndex();
RedrawItems(nFirst, nFirst + GetCountPerPage())
```
以上方案實現了**介面顯示區域性重新整理**,配合**虛表模式賦值**和**介面資料增量重新整理**,可極大提高在大資料量下的`CListCtrl`重新整理顯示效果,親測有效,值得使用。
## 全文總結 ##
本文總結大資料量下的查詢和顯示優化方案,主要從資料層、中間層和UI層進行優化,方案設計思路如下:
* **資料層的定時分批查詢**
* 根據後臺限制優化定時查詢間隔
* 快取管理
* 考慮重複查詢場景
* **中間層的快取中心**
* 支援定時增量查詢
* 推送資料既包括全量也包括增量
* **UI層的互動優化和重新整理優化**
* 互動優化
* 虛表模式賦值
* 介面資料增量重新整理
* 介面顯示區域性重新整理
以上是本人在大資料量下查詢和顯示優化方案的思考,文章可能會有紕漏和錯誤,歡迎大家在留言中指出來,大家一起討論學習,共同