当前位置: 首页 > 科技观察

挤掉服务器:一次非人道的性能优化

时间:2023-03-17 22:33:35 科技观察

背景做过2B系统的同学都知道,2B系统最恶心的操作就是什么都喜欢batch。这不,最近遇到一个恶心的需求——50个用户同时导入10000个文档,每个文档有70、80个字段。请为我优化它。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。而且,所有的机器都要同时增加内存,但实际上importrequests可能只有几个请求,导致大量资源的浪费和大量的机器成本。另外我们导入的每条数据都有七八十个字段,在处理的过程中需要写数据库,写ES,写日志等操作,所以每条数据的处理速度比较慢,我们用50ms来计算(实际比50ms长),10000条数据的耗时处理需要10000*50/1000=500秒,接近10分钟。这个速度无论如何是不能接受的。所以,我一直在想,有没有什么办法可以降低成本,加快进口请求的处理速度,同时营造良好的用户体验呢?苦思冥想,果然想出了一个解决办法:独立一个导入服务,做成通用服务。导入服务只负责接收请求。收到请求后,会直接告诉前端已经收到请求,稍后再通知结果。然后,解析Excel,解析完一条数据,不做任何其他处理,直接丢给Kafka,由下游服务消费。消费结束后,给Kafka发送消息,告诉导入服务这个数据的处理结果,导入服务检测到所有行都收到反馈,然后通知前端导入完成。(前端轮询)如上图所示,我们以导入XXX为例来说明整个过程:前端发起导入XXX的请求;后端导入服务收到请求后立即返回,告知前端已收到请求;导入服务解析每条数据写入一行数据到数据库,同时将数据发送到Kafka的XXX_IMPORT分区;处理服务的多个实例从XXX_IMPORT的不同分区拉取数据并处理它们。这里的处理可能会涉及到数据合规检查,调用其他服务补全数据,写数据库,写ES,写日志等;一条数据处理完成后,向Kafka的IMPORT_RESULT发送消息,说这条数据处理完毕,或成功或失败,失败需要有失败原因;导入服务的多个实例从IMPORT_RESULT中拉取数据,并更新数据库中每条数据的处理结果;前端轮询接口在某个请求中发现导入已经完成,并告诉用户导入成功;用户可以在录制下载页面查看导入失败;初步测试经过以上设计,我们测试导入10000条数据只需要20秒,比之前预估的10分钟快了一星半。但是,我们发现了一个非常严重的问题。我们在导入数据的时候,查询界面卡顿,需要10秒左右刷新查询界面。从表面上看,导入影响了查询。初步怀疑是因为我们只查询了ES,所以初步怀疑是ES的资源不够用。但是我们查看ES的监控,发现ES的CPU和内存还是很充足的,没有出现问题。然后,我们仔细检查了代码,没有发现明显的问题,服务本身的CPU、内存、带宽也没有发现明显的问题。真是太神奇了,完全没有概念。而且我们的日志也是用ES写的,日志量比导入量还大。检查日志时,我们没有发现任何卡住。所以,我想,尝试直接通过Kibana查询数据。照你说的去做。导入的时候在Kibana上查询数据,没有找到卡。结果显示只需要几毫秒就可以找到数据。比较耗时的是在网络传输上,但是整个只需要1秒左右的数据就会刷出来。所以可以排除是ES本身的问题,肯定是我们的代码问题。至此,我做了一个简单的测试。我把query从导入的处理服务中分离出来,发现没有卡住,秒返回。答案即将浮出水面。肯定是导入过程中ES的连接池资源已经用完了,导致查询的时候无法获取到连接,所以需要等待。通过查看源码,终于发现ES连接数硬编码在RestClientBuilder类中,DEFAULT_MAX_CONN_PER_ROUTE=10,DEFAULT_MAX_CONN_TOTAL=30,每条路由最大10个,总连接数有最大值of30.更何况这两个配置是硬编码在代码里的,没有参数可以配置,只能通过修改代码来实现。这里也可以做一个简单的估算。我们的处理服务部署了4台机器,每台机器总共可以建立30个连接。4台机器是120个连接。如果导入的10000个订单是均匀分布的,每个连接都需要处理。10000/120=83条数据,每条数据的处理时间为100ms(上面用的50ms是估计的),也就是8.3秒,所以查询的时候等待10秒左右是合理的。直接将这两个参数增加10倍到100和300,(关注同格公众号一起阅读源码学习),然后部署服务。测试发现导入的同时查询正常。接下来我们测试50个用户同时导入10000个订单,即并发导入50万个订单。如果20秒计算10000个订单,总耗时应该是50*20=1000秒/60=16分钟。但是,测试发现用了30多分钟。这次的瓶颈在哪里?我怀疑我们之前的压力测试是基于10,000个单用户订单。当时的服务器配置是导入4台机器进行服务。服务4台机器,根据我们上面的架构图,按理说导入服务和处理服务是可以无限扩展的,只要增加机器就可以提升性能。所以,首先,我们把处理机器的数量增加到25台(我们是基于k8s的,扩展很方便,换个数量就行),跑了一个50万的订单,发现没有效果,还是用了30多分钟。然后,我们还将导入服务的机器数量增加到25台,并运行了500,000个订单。同样,我们发现没有效果。这时,我们有点怀疑人生了。通过查看各个组件的监控,发现导入到服务中的数据库有一个指标叫IOPS,已经达到5000,并持续在5000左右。什么是IOPS?表示一秒内读写IO的次数,和TPS/QPS相差无几,表示一秒内MySQL与磁盘的交互次数。一般来说,5000已经很高了。目前,瓶颈可能就在这里。再次查看这个MySQL实例的配置,发现它使用的是超高IO,但实际上是普通硬盘。不知道换成SSD会不会好点。照做吧,联系运维重新购买了一个SSD盘的MySQL实例。切换配置,再次跑50万单,这次时间确实减少了,只需要16分钟,差不多减少了一半。因此,SSD仍然要快得多。检查监控。当我们导入50万个订单时,SSD的MySQLIOPS可以达到12000左右,翻了不止一倍。后来我们也把处理业务的MySQL盘换成了SSD,时间又降到了8分钟左右。到这里你以为就结束了(关注同格公众号阅读源码,一起学习)?想想我们上面说的,按照前面的架构图,导入服务和处理服务是可以无限扩展的,我们分别添加了。到25台机器,但是性能还没有达到理想的情况,我们算一下。假设瓶颈都在MySQL,对于导入服务,一条数据我们需要和MySQL交互4次左右。整个Excel分为表头表和行表。第一条数据插入表头,后面的数据更新到表头,插入行表,当处理完成后,会更新表头表和行表,所以按照12000IOPS计算,MySQL会消耗我们500000*4/12000/60=2.7分钟。同样,处理服务类似,处理服务会走WriteES,但是处理服务没有头表,所以时间也计算为2.7分钟,但是这两个服务本质上是并行的,没有关系所以总的时间要控制在4分钟以内,所以我们还有4分钟的优化空间。重新优化经过一系列排查,我们发现Kafka有个参数叫kafka.listener.concurrency,processingservice设置为20,topicpartition为50,也就是说其实我们只用了2.5台机器25台机器在Kafka中处理消息(猜测)。如果你发现了问题,那就好办了。先把这个参数调成2,分区数不变,再测试。果然,时间下降了,5分钟就过去了。经过一系列的调整测试,发现分区数为100,并发为4时,效率最高,最快可以达到4分半钟。至此,整个优化过程告一段落。总结下面总结一下优化的地方:导入的Excel技术选用EasyExcel,确实很不错,从来没有出现过OOM;导入的架构设计修改为异步处理,参考秒杀架构;路由为100,最大连接数为300;MySQL磁盘被SSD替换;Kafka优化了分区数和kafka。通过后期规划中的这个优化,我们也发现,当数据量足够大的时候,瓶颈还是在存储方面。那么,是否可以通过优化存储区域来进一步提升性能呢?答案是肯定的,例如有以下思路:导入服务和处理服务都改成分库分表,不同的Excel落入不同的库,减轻单一数据库的压力;写MySQL批量操作,减少IO次数;导入服务使用Redis记录,不使用MySQL;但是,这次没有必要尝试所有这些。通过这次压力测试,我们至少可以知道我们所知道的。我们真的等到音量达到那个程度,再去优化也不迟。本文转载自微信公众号“童哥阅读源码”,可通过以下二维码关注。转载本文请联系童哥阅读源码公众号。