第四节:海量数据导出到Excel、百万Excel导入到DB、秒级/分钟级排行版
一. 海量数据导出到Excel
背景:
但是当处理大数据量的Excel文件导出时,很可能因其内存占用较高而导致内存溢出问题。同时,数据处理过程可能非常耗时,导致用户等待时间过长或请求超时
解决方案:
情况1:小数据量大文件
A. 可以直接等待生成完毕,直接下载。
B. 服务器不存档,以文件流的形式给前端,前端直接生成文件
情况2:大数据量的文件
大文件导出都是异步,前端直接返回,而不是在线等待。
服务器异步做了三件事:
A. 利用NPOI生成Excel
B. 将该Excel保存到文件服务器上
C. 通知客户端进行下载,通常的方式:SignalR实时通知、发邮件、站内信等等。
二. 百万Excel导入到DB
背景:
项目中有一个数据迁移,原来的数据存储在旧的系统,现在系统做了重构,需要迁移到新的系统中,老系统的数据被加工到Excel中了,需要基于Excel实现文件的导入,可能遇到的问题:
1 内存溢出:百万级的excel文件非常大,读取后都加载到内存中可能导致内存溢出。
2 性能问题:百万数据从excel中读取并插入DB这个过程缓慢,需要考虑到性能。
3 错误处理:在文件读取和导入的过程中,会遇到各种各样的问题,需要妥善的处理这些问题。
解决方案:
1 解决内存溢出
首先百万数据存放在excel中的时候,可以分散到不同的sheet中;然后借助线程池,采用多线程读取不同的sheet,读取的过程中,基于NPOI库逐行读取excel中的内容,而非一次性加载到内存中。
2 解决性能问题
设定一个批次,比如3000条吧,当读取到3000条的时候,就执行一次批量插入操作。
批量插入借助开源库【EFCore.BulkExtensions】或【Zack.EFCore.Batch】进行插入
A. 逐行读取excel的时候要机型数据检查,格式问题提前检查好
B. 考虑数据重复问题,可以将excel中的几个字段设置成唯一性约束
C. 执行过程中报错,一般是先重试,还不行的话, 采用 “跳过+打印日志”的方式
最终结果:
一百万数据的excel导入到数据库,大约需要90s左右
三. 基于Redis中ZSet(Sorted Set)实现秒级排行榜
背景:
对于周榜、日榜、小时榜等,完全可以基于定时任务、离线任务等生成,直接统计数据然后做排序就行了,难度不大。 难度比较大的就是分钟级或者秒级的榜单的实现,因为数据库的话大量数据的order by会性能很差。
分析:
1 ZSet数据结构形如 key-member-score 结构,其中member不重复
2 分钟级别和秒级别的排行版最好是脱离DB,直接基于Redis。
3 排行版肯定是由大到小排行
解决方案:
1 采用ZSet结构存储,key为“myRank1”,member为用户的userId,score为用户的分数值
2 新用户添加,形如 RedisHelper.ZAdd("myRank1", (80, "10001")); 其中10001为用户的userId,80为该用户的默认分数。
3 用户分数增加 or 减少,可以:RedisHelper.ZIncrBy("myRank1", "10001", score); 其中score可以是正数 或 负数,在原分数上增加或减少。
4 可以获取排名前99的用户集合,可以: RedisHelper.ZRange("myRank1", 0, 98);
增加难度:
当用户的积分一样时,按照用户的创建时间来排序,也就是说,如果两个人分数相同,1.1日开通的用户必1.2日开通的用户排名靠前。
解决方案:
1 将分数score设置成一个浮点数,其中整数部分为正常的得分,小数部分为时间戳
2 score = 分数 + 1-时间戳/1e13
PS: 因为分数相同,先开通要在前面,所以需要 1-xxx, 后开通的时间戳大, 然后用 1-它,就小了,正好符合先开通的在前面。
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。