最近工作中, 遇到了优化大批量数据查询和显示的问题, 数据量在 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 层的交互优化和刷新优化
交互优化
虚表模式赋值
界面数据增量刷新
界面显示局部刷新
以上是本人在大数据量下查询和显示优化方案的思考, 文章可能会有纰漏和错误, 欢迎大家在留言中指出来, 大家一起讨论学习, 共同进步!
来源: https://www.cnblogs.com/cherishui/p/12525563.html