流式查询
问题概述
OOM问题的案例,背景是【在物流领 域,针对各个下级⽹点⽽⾔,每⽉1⽇〜9⽇是⼀个进⾏财务⽉结的重要时间节点,在这个关键节点 上,各个⽹点会需要使⽤导出功能输出寄派件、费⽤客⼾信息等多种信息进⾏汇总结算】。也就是 说,在⽉初的时候,每个⽹点都要统计⼀个⽉的各种流⽔(寄件,收件等),最后以excel的表格 的形式下载给客⼾。在这个业务中为什么容易发⽣OOM呢,平均1个⽹点的1个⽉的流⽔⼤约在 30w⾏左右,根据计算得出⼤约500⾏数据会占⽤1M内存,1个站点把30万⾏⼀股脑读到内存中就 会占⽤600M内存,试想⼀下如果全国的⽹点都在⽉初集中下载报表的话,jvm是很容产⽣内存溢 出的问题的。
解决⽅案概述
这样⼀个棘⼿的问题⽤⼀个单⼀的解决⽅案是不够的,主要采取了以下的解 决⽅案
2.1 ⽤硬盘换内存
接到⼀个统计数据请求的时候,⼀次性把30w条数据从数据库中读到⼀个List的做法,显然是不 现实的,这样⼀个list将会占⽤600M内存。我们可以分⻚查询,每次查询1000条数据,然后往 硬盘上写,多读取⼏次,⼀点⼀点的把所有的数据都读出来,⼜⼀点⼀点的往硬盘上写,这样 在这个过程中,占⽤的内存就会少很多,主要变成了对硬盘的占⽤。这⾥写excel的技术,选择 的是阿⾥巴巴的easyexcel.
2.2 使⽤mybatis的流式查询
使⽤【流式查询】查询成功后返回的是⼀个迭代器⽽不是⼀个集合,应⽤每次都从迭代器中获 取⼀条查询结果,能够降低内存的使⽤。试想⼀下如果我们不使⽤流式查询,⼀次性从数据库 中取30万条数据,内存根本不够⽤,这时我们只能选择分⻚查询了,⽽分⻚查询的性能取决于 表设计以及索引的设计,⼤量数据分⻚查询的性能是很低的,耀哥对⽐使⽤流式查询和分⻚查 询两种⽅案,得到的结论是取30万条数据,流式查询的速度⼤约是分⻚查询的4,5倍左右
2.3 使⽤redission 的信号量限流
⽣成⼀个⽉的流⽔报表是⼀个⾮常耗时的操作,⽤⼾也不可能⻢上就要结果,所以学⽣的公司 对同时⽣成报表的请求做了限制,同时只处理10个报表的⽣成,这个期间再有⽣成报表的请 求,我们将会让这些请求排队,等到前⾯的报表⽣成完毕后,再处理后⾯的请求。报表⽣成成
功后,再通知客⼾主动去下载,这⾥使⽤redisson分布式锁的信号量来限制同时创建报表的线 程数量。
2.4 mq解耦,微服务拆分
本次业务中,读数据库,写excel⽂件,上传到⽂件服务器这三个操作都⾮常耗时,学⽣的公司使 ⽤mq解耦,把这次请求拆分成3个微服务,这样读,写,上传就不会相互影响。
流式查询解决方案
数据库插入十万条数据
public class easyTest {
int a=0;
@org.junit.Test
public void add(){
while (a<=100000){
TbGoodMapper mapper = MySqlSessionFactory.getSession().getMapper(TbGoodMapper.class);
TbGood