大數據量下查詢顯示優化方案小結
最近工作中,遇到了優化大批量數據查詢和顯示的問題,數據量在10W級別。經過反復設計和討論,最終得到優化到了較為滿意的效果,在此記錄小結下,在解決此類問題中的思考。
問題背景說明
通常情況下,用戶查詢數據量不超過1千條,但有幾個大戶,通過某種方式,生成了上萬級別的數據,前端未針對大數據量的查詢和顯示進行優化,導致該界面顯示卡頓、白屏、點擊無響應、顯示總量和實際總量不一致等等問題。
總體思路
大數據量查詢和顯示,可從以下三個方面來優化:
- 定時分批查詢
- 緩存中心
- UI優化
以上三層分別對應數據層、中間層和UI層,每層優化方案可單獨使用,也可以結合使用。建議結合使用。
定時分批查詢
數據后台提供的查詢指令是支持分頁查詢的,與其相關的字段有:
- 查詢方向 qryDir, 定位查詢方向,1為往下查,0為往上查
- 請求行數 qryCount,說明期望查詢的個數
- 定位串 qryPositionStr, 查詢定位串,用於定位查詢起點
基於上述三個字段,系統可以實現分頁查詢功能,但具體到UI層,因為無法獲知該用戶數據總量,也就無法推導總頁數信息,UI層無法以分頁形式展示。如果后台提供數據總量字段,那就可方便實現上一頁、下一頁、最后一頁等功能。現有后台沒提供總量信息,無法在UI層做分頁查詢,只在數據層做分頁查詢。
在數據層做分頁查詢時,對UI層是透明的。也就是說,當數據層尚未查詢完畢時,UI層是不會收到響應,還可以繼續操作界面。這里可以優化,同產品討論以下兩種處理方式:
- 方案一:本次查詢尚未返回,禁止UI層重復查詢
- 方案二:本次查詢尚未返回,支持UI層重復查詢
目前的設計采用方案二,因此需要數據層考慮重復請求的場景。除了重復請求之外,數據層還需要考慮后台限制和緩存管理,小結起來有以下三點:
- 后台限制
- 緩存管理
- 重復查詢
下面分別闡述數據層針對上述問題的設計方案。
- 后台限制
任何后台服務能夠提供的功能都是有限制要求的。數據后台對查詢指令有單次最大條數和查詢頻率的限制,具體限制如下:
- 單次查詢最大請求行數建議1000條
- 查詢頻率限制在5秒15筆查詢,超頻查詢會報錯
為了在不影響后台穩定性的情況下,盡可能快地查詢回所有信息。在查詢過程中,除了常規的分頁查詢之外,還額外增加定時查詢機制,具體設計為,每連續發起3筆分頁查詢,等待半秒后,繼續查詢,循環往復,直到所有查詢數據都返回,每次分頁查詢請求條數設置后台推薦的最大值。上述每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虛擬列表技術,此處不再敘述。
界面增量刷新,是指在UI層維護當前顯示內容緩存,當有數據更新時,通過比較更新數據和當前緩存,決定是否更新列表。基於上述緩存中心,可通過增量更新來優化判斷流程。
界面局部刷新,是指只針對當前界面顯示部分進行刷新,未顯示部分不進行刷新。界面局部刷新涉及到的函數有以下三個:
- int GetTopIndex() 獲得當前顯示區域最頂部Item的位置索引
- int GetCountPerPage() 獲得當前控件顯示區域最多能夠顯示Item的個數
- BOOL RedrawItems(int nFirst, int nLast) 重繪位置索引在nFirst和nLast區間中的Item
具體使用很簡單,在重繪時,只需要以下兩句話搞定:
int nFirst = GetTopIndex();
RedrawItems(nFirst, nFirst + GetCountPerPage())
以上方案實現了界面顯示局部刷新,配合虛表模式賦值和界面數據增量刷新,可極大提高在大數據量下的CListCtrl
刷新顯示效果,親測有效,值得使用。
全文總結
本文總結大數據量下的查詢和顯示優化方案,主要從數據層、中間層和UI層進行優化,方案設計思路如下:
- 數據層的定時分批查詢
- 根據后台限制優化定時查詢間隔
- 緩存管理
- 考慮重復查詢場景
- 中間層的緩存中心
- 支持定時增量查詢
- 推送數據既包括全量也包括增量
- UI層的交互優化和刷新優化
- 交互優化
- 虛表模式賦值
- 界面數據增量刷新
- 界面顯示局部刷新
以上是本人在大數據量下查詢和顯示優化方案的思考,文章可能會有紕漏和錯誤,歡迎大家在留言中指出來,大家一起討論學習,共同進步!