做过2B系统的同学都知道,2B系统最恶心的操作就是批量做所有事情。这不,我最近遇到了一个恶心的要求。 ——50个用户同时导入10000个文档,每个文档七八十个字段,请帮我优化一下。
Excel导入技术选型说起需要导入Excel,很多同学都做过,也很熟悉。这里使用的技术是POI系列。
然而,原生POI 很难使用。您需要自己调用POI的API来解析Excel。每次改模板,就得写一堆重复的、无意义的代码。
所以后来出现了EasyPOI,它在原生POI的基础上做了一层封装。使用注释可以帮助您自动将Excel 解析为Java 对象。
EasyPOI虽然使用方便,但是当数据量极大时,时不时就会出现内存溢出的情况,非常烦人。
因此,后来做了一些封装,创建了EasyExcel。可以配置不溢出内存,但解析速度会降低。
如果我们要关注技术细节,那就是DOM 解析和SAX 解析之间的区别。 DOM 解析将整个Excel 加载到内存中并一次性解析所有数据。对于大型Excel如果内存不够,会导致OOM,而SAX解析可以支持逐行解析。所以如果SAX解析正确执行,就不会出现内存溢出的情况。
因此,经过评估,我们系统的目标是每天500万个订单,其中进口的需求非常大。为了稳定性,我们最终选择使用EasyExcel作为Excel导入的技术选择。
导入设计我们之前做过一些系统,都是把导入的需求和正常的业务需求结合起来。这会造成一个非常严重的问题:一方输了,双方都会遭殃。当一个大的导入来临时,往往系统会特别卡。
与其他请求一样,导入请求只能在一台机器上处理。无论这个导入请求击中哪台机器都是不幸的。其他同样命中该机器的请求也会受到影响,因为导入会占用大量空间。资源,无论是CPU还是内存,通常都是内存。
还有一个非常操蛋的问题。一旦业务受到影响,往往只能通过增加内存来解决。 4G不能升级到8G,8G不能升级到16G。而且所有机器都要同时增加内存,但实际上,导入请求可能只有几个请求,造成大量的资源浪费和大量的机器成本。
另外,我们导入的每条数据都有七十、八十个字段,处理过程中需要写入数据库、ES、日志等多次操作,所以每条数据的处理速度都比较慢。我们按50ms计算(实际上比50ms长),一万条数据的处理时间就是10000 * 50/1000=500秒,接近10分钟。这个速度无论如何都是让人无法接受的。
那么,我一直在想,有没有什么办法可以降低成本,加快导入请求的处理速度,同时创造良好的用户体验呢?
经过深思熟虑,我其实想到了一个方案:单独一个导入服务,把它做成一个通用服务。
导入服务仅负责接收请求。收到请求后,直接告诉前端请求已收到,稍后通知结果。
然后,解析Excel,解析完一条数据后,直接扔到Kafka中,不做其他处理。下游服务将消耗它。消费完成后,向Kafka发送消息,告诉导入服务此数据的处理结果。导入服务会检测到它。已收到所有行的反馈,并通知前端导入已完成。 (前端轮询)
如上图所示,我们以导入XXX为例来描述整个过程:
前端发起导入XXX的请求;后端导入服务收到请求后立即返回,告诉前端请求已收到; import服务每解析一条数据就会向数据库写入一行数据,同时将数据发送到Kafka的XXX_IMPORT分区;处理服务XXX_IMPORT 的多个实例从不同分区拉取数据并进行处理。这里的处理可能涉及到数据合规性检查、调用其他服务补全数据、写入数据库、写入ES、写入日志等;等待一条数据处理完成后,Kafka的IMPORT_RESULT发送消息说这条数据已经处理完毕,要么成功,要么失败。失败需要失败原因;导入服务的多个实例从IMPORT_RESULT中拉取数据并更新数据库中每条数据的处理结果;前端轮询,当接口在某个请求过程中发现导入完成时,告诉用户导入成功;用户可以在页面查看导入失败记录并下载;初步测试经过上面的设计,我们只需要测试20秒导入10000条数据,比之前预估的10分钟快了半个多小时。
然而,我们发现了一个非常严重的问题。当我们导入数据时,查询界面卡住了,需要等待10秒才能弹出查询界面。从外观上看,导入影响了查询。
初步怀疑因为我们的查询只去了ES,所以我们最初怀疑ES没有足够的资源。
但是当我们检查ES的监控时,发现ES的CPU和内存仍然充足,没有出现任何问题。
然后,我们仔细检查了代码,没有发现明显的问题,服务本身的CPU、内存、带宽也没有发现明显的问题。
真是太神奇了,我根本不知道。
而且我们的日志也是用ES写的。原木量大于进口量。检查日志时,我们没有发现任何堵塞。
所以,我想,尝试直接通过Kibana查询数据。
去做就对了。导入的时候,我在Kibana上查询了数据。没有找到卡。结果显示,只需要几毫秒就可以找到数据。网络传输的时间较多,但整体时间只有1秒。左右数据都刷出来了。
所以可以排除是ES本身的问题,肯定是我们代码的问题。
这时候我做了一个简单的测试。我将查询和导入处理服务分开,发现没有任何延迟,秒级就返回了响应。
答案快要揭晓了。肯定是导入过程中ES连接池资源被占满,导致查询时拿不到连接,需要等待。
通过查看源码,终于发现RestClientBuilder类中硬编码了ES连接数,DEFAULT_MAX_CONN_PER_ROUTE=10,DEFAULT_MAX_CONN_TOTAL=30,每条路由最大为10,总连接数为最大30. 更何况,这两个配置是硬编码在代码中的,没有什么参数可以配置。只能通过修改代码来实现。
我们在这里也可以做一个简单的估算。我们的加工服务部署了4台机器。每台机器总共可以建立30个连接。 4台机器120个连接。如果我们导入10000个订单并平均分配,则每个连接都需要处理。 10000/120=83 条数据。每条数据处理100ms(上面用的50ms都是估计值),也就是8.3秒。因此查询时需要等待10秒左右,比较合理。
直接将这两个参数提高10倍到100和300,(关注公众号通哥阅读源码,一起学习),然后部署服务。测试发现导入过程中查询正常。
接下来我们测试了50个用户同时导入1万个订单,即同时导入了50万个订单。按照10000单20秒计算,总时间应该是50*20=1000秒/60=16分钟。然而测试发现,花了30多分钟。这次的瓶颈在哪里?
再次怀疑我们之前的压力测试是基于每个用户10,000个订单。当时的服务器配置是4台机器用于进口服务,4台机器用于加工服务。根据我们上面的架构图,按理说导入服务和处理服务是可以无限扩展的。只要增加机器,性能就可以提高。
所以,首先我们把处理服务的机器数量增加到25台(我们是基于k8s的,扩展很方便,所以就是改数量的问题)。我们跑了50万个订单,发现没有效果,还是花了30多分钟。
然后,我们将引入该服务的机器数量增加到25台,并运行了50万个订单。我们再次发现没有效果。这时,我们对人生都产生了一些怀疑。
通过检查各个组件的监控,我们发现此时导入服务的数据库有一个指标叫IOPS,已经达到了5000,并且持续在5000左右,什么是IOPS?
表示每秒读写IO次数,类似于TPS/QPS。表示每秒MySQL与磁盘的交互次数。一般来说,5000已经很高了。
目前来看,瓶颈可能就在这里。我再次检查了这个MySQL实例的配置,发现它使用了超高IO,实际上是一个普通的硬盘。我想如果换成SSD会不会更好。
直接联系运维购买一个SSD盘的新MySQL实例就可以了。
我切换配置,再次跑了50万单。这一次,时间确实缩短了。只用了16分钟,已经快一半了。
因此,SSD还是快得多。查看监控,当我们导入50万条订单时,SSD的MySQL IOPS可以达到12000左右,速度快了一倍多。
后来我们将处理服务的MySQL盘更换为SSD,时间再次下降到8分钟左右。
你以为到这里就结束了吗(关注公众号同哥阅读源码,一起学习)?
思考上面我们说了,按照之前的架构图,导入服务和处理服务是可以无限扩展的,我们分别增加了25台机器,但是性能还没有达到理想的情况。我们来计算一下。
假设瓶颈全部在MySQL。对于导入服务,对于一条数据,我们需要与MySQL 交互大约四次。整个Excel分为表头表和行表。第一条数据插入到头表中,后续数据更新到头表中并插入到行表中。处理完成后,头表和行表都会更新,所以按照12000 IOPS计算,MySQL会消耗我们500,000 * 4/12000/60=2.7分钟。同样,处理服务也几乎是一样的。处理服务也会写ES,但是处理服务没有头表,所以时间也计算为2.7分钟。不过这两个服务本质上是并行的,没有任何关系,所以总时间应该控制在4分钟以内,所以我们还有4分钟的优化空间。
再优化经过一系列的调查,我们发现Kafka有一个参数,叫做kafka.listener.concurrency。处理服务设置为20,这个Topic的分区为50。也就是说,我们实际上只使用了25台机器中的2.5台。处理Kafka 中消息的机器(猜测)。
找到问题之后,解决起来就很容易了。首先将此参数调整为2,保持分区数不变,再次测试。果然,时间缩短到了5分钟。经过一系列的调整测试,我们发现分区数为100时,并发数为4时效率最高,最快可以达到4分半钟。
至此,整个优化过程就结束了。
总结现在我们来总结一下优化的地方:
导入的Excel技术选的是EasyExcel,确实非常好。 OOM从未发生过;导入架构设计修改为异步处理,参考闪购架构; Elasticsearch连接数调整为每条路由100个,最大连接数为300; MySQL磁盘更换为SSD; Kafka优化分区数量和kafka.listener.concurrency参数;另外,还有很多其他的小问题,由于篇幅和内存的限制,无法一一讨论。
后期规划通过这次优化,我们还发现,当数据量足够大时,瓶颈仍然在存储区域。那么,是否可以通过优化存储区域来进一步提升性能呢?
答案是肯定的,例如有以下一些想法:
导入服务和处理服务都修改为单独的数据库和表格,不同的Excel落入不同的库,减少单个数据库的压力;写入MySQL修改为批量操作,减少IO次数;导入服务使用Redis代替MySQL进行记录;
但是,这次我们应该尝试所有这些吗?事实上,没有必要。通过这次压力测试,我们至少可以有个大概的了解。在数量达到该水平之前进行优化还为时不晚。
如果你还想了解更多这方面的信息,记得收藏关注本站。
本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/7230.html
用户评论
这标题听着真吓人,感觉像是在说对服务器特别残忍地榨取性能一样。
有16位网友表示赞同!
好奇“榨干服务器”到底是什么操作?听起来有点让人不安。
有13位网友表示赞同!
现在技术更新迭代这么快,是不是服务器压力很大啊?
有16位网友表示赞同!
希望这篇文章能解释清楚“榨干服务器”是什么意思,我真是很好奇了。
有6位网友表示赞同!
这个标题感觉很有冲击力!是不是要研究一些让服务器更轻松的解决方案?
有5位网友表示赞同!
如果真的要榨干服务器的话,会不会对它的寿命有影响?
有5位网友表示赞同!
看标题我感觉这篇文章应该是在说某个极端的性能优化策略吧?
有19位网友表示赞同!
想了解一下“榨干”这种方式到底有什么优缺点?
有13位网友表示赞同!
是不是现在的服务器硬件已经快跟不上发展的速度了?
有14位网友表示赞同!
会不会有其他更温和、更可持续的性能优化方法?
有12位网友表示赞同!
如果榨干服务器会导致安全风险,那岂不是妥协了一大堆利益吗?
有9位网友表示赞同!
感觉这个“榨干”的过程听起来很复杂。需要多了解一些技术细节。
有5位网友表示赞同!
想知道文章会从哪些方面来分析“榨干服务器”?
有17位网友表示赞同!
希望这篇文章能提供一些实际可行的建议,而不是只是描述问题。
有20位网友表示赞同!
感觉这个标题有点吓人。是不是要提高服务器的抗压能力?
有17位网友表示赞同!
对服务器进行过度优化会不会带来新的问题呢?
有5位网友表示赞同!
期待学习一下这篇文章提出的性能优化方法。也许能找到灵感!
有16位网友表示赞同!
想知道“榨干服务器”会对用户体验有什么影响?
有18位网友表示赞同!
如果真的要榨干服务器,那么成本是多少呢?
有18位网友表示赞同!