需求背景
该篇内容基于之前写过的一篇<>,上一篇文章其实主要重点是结合logstash的实际应用。近期业务方提出了新的需求,增加了些业务逻辑,同时数据量也成倍增加,要求每日产出指标结果,这里再回顾下上篇的数据情况和技术方案同时对比下新调整后的数据量
端
调整前数据量
调整后数据量
生产端
每日20亿左右
每日500亿左右
输出端
减半,10亿左右
每日50亿+
考虑到投入产出比,该需求仍然采用来原来老的技术方案设计,只是做了些优化手段。具体使用到的技术:Java,Kafka,MLSQL,Logstash,Ruby,Hive,ES,SparkSQL,Datax「注意:这里均是实际的业务场景和实际的数据量,本文以分享为目的,如果读者有更好的方案,欢迎一起交流」
方案设计
「1.数据流向」 流程: 1.业务方将数据推送至MQ,并将消息进行序列化处理 2.通过流平台接入消费消息,并进行一部分逻辑处理,再次回转到MQ中 3.使用logstash消费消息,编写ruby进行逻辑处理,将数据写入hdfs 4.数仓对hdfs文件进行加载入表,进行建模处理 5.最后将指标结果写入到业务方,以es存储 阅读过上篇文章的读者相信对这部分的流程比较清晰,其实这就属于数仓开发流程和分层处理。基本流程梳理完成后,如果直接按照该种模式每日处理如此大的数据量,必然会占用大量的资源,对其他的调度任务产生影响。接下来就要结合实际的需求进行详细的设计。「2.技术方案」 流程: 1.业务侧通过protobuf序列化将数据推送至kafka 2.通过MLSQL开发udf,消费kafka数据反序列化,并进行去重处理(「这里设置的是每3秒一个批次,这里做了一次批内去重」 )并将数据再次写回kafka中 3.logstash端消费kafka数据,并编写filter逻辑,这里涉及到外部接口调用的逻辑,将调用结果保存至系统环境变量中(「考虑数据接口更新频率和数据量,这里每小时调用一次,另存入环境变量其实就是一个缓存,之所以没有存入文件,是因为logstash每次处理event都要对文件进行操作,效率较低」 ),然后进行获取,最后otut端将数据写入hdfs中 4.数据写入hdfs后,加载到hive表中,进行建模开发 5.最后使用datax将指标统计结果写入到es中供业务方使用「3.问题引入」 虽然上述的方案能够实现实时消息上百亿的数据量,但未考虑到数仓侧的计算压力,目前生产上每日跑批任务有6000+个任务,如果夜间去计算50亿+的数据量,将会把所有的资源全部占用,导致其他任务一直阻塞,最后会影响重要任务产出(「别问我怎么知道的,因为这是踩过的坑」 )。所以接下来需要对该部分任务进行优化,尽量做到占用最小的资源以最快的时间产出。
性能优化
「1.消费优化,其目标:即做到消费不延迟」 消费端的优化仍然沿用上篇的优化手段,唯一不同的地方在于logstash端涉及到的缓存问题,刚才上面也提及过需要调用外部接口来过滤一部分数据,但接口不可能是一直调用的,否则会对接口造成压力,结合接口更新和数据特性,这里每小时调用一次即可,那么问题来了,调用的结果存储到哪里呢?首先不能存储到外部系统,一方面增加了强依赖,另一方面对外部系统也会有压力负载,因此需要落入本地,之前采用过落入文件的方式,然后ruby中对文件进行操作,但是效果不佳(「这里涉及到频繁的文件打开关闭和同步问题」 ),后采用环境变量的方式进行存储,每次处理event的时候读取环境变量字段即可。具体使用方式如下:
if not ENV["app_list"].nil? event["arrays"] = ENV["app_list"].split(" ") event.cancel if event["arrays"].include? event["service_name"] # 这里的ENV["app_list"]就是读取的环境变量app_list字段 end
「2.批处理优化,其目标:占用最少的资源花最小的时间产出」 1.mapper和reduce数量调整 对于批处理的优化无非是对mapreduce的优化,由于logstash写入的hdfs文件是可切分的,所以产生的mapper数跟块的个数有关系,但由于mapred.min.split.size参数在服务端已经固定配置了(固定256M),如果大于该值则会直接报错。因此如果文件越大,那么切分的mapper数也就越多,申请的资源也就越多,所以对于占用小量资源的优化,从源头上就不可行。 2.存储优化 由于logstash输出的格式是textfile的,如果直接对该种格式进行处理,将会占用大量的网络资源,因此需要进行格式转换和压缩存储。这里优化的手段是采用orc存储,snappy格式压缩(「相对于lz4压缩,snappy是属于不可切分的,那么这也对mapper数量进行了控制」 )。
create table if not exists tableA( id string, field1 string, field2 string ) partitioned by (date_id string,hour string) stored as orc tblproperties ("orc.compress" = "SNAPPY");
3.改用执行引擎 基于第一点的优化思路,受限于源头的文件切分和可调参数导致无法控制资源,因此这里需要借用于分而治之的思想,即将每日的处理调整为每小时处理(「这里只是过滤对于该次需求无用的数据,不做每小时聚合的操作,即业务场景需要对全天的数据进行聚合处理」 ),为了尽可能使用较少时间执行,需要将小时处理的任务执行引擎由hive调整为sparksql执行,同时调整了以下几个参数
--开启动态分区 set spark.sql.auto.repartition=true; set spark.shuffle.service.enabled=true; set hive.exec.dynamic.partition.mode=nonstrict; set hive.exec.dynamic.partition=true; --开启sparksql自适应,即不采用固定的最小分区,避免产生小文件 set spark.sql.adaptive.enabled=true; set spark.sql.autoBroadcastJoinThreshold=209715200; set spark.sql.adaptive.shuffle.targetPostShuffleInputSize=1024000000;
效果产出
基于以上的几种优化手段,目前已经解决了前面提到的占用资源过多和产出时间过长的问题。
资源使用(队列最大资源使用量)
产出时间
优化前
1200Cores+4293189M+Fair Scheduler
14803秒
优化后
100Cores+92752M+Fair Scheduler
3470秒
yarn队列资源配置
调度任务执行情况
最终要聚合统计的数据量
待解决问题
虽然已经解决掉了占用资源多,产出时间长的问题,但是稳定性也是亟需要解决的问题。由于logstash消费端部署的机器配置各有差异,所以在写入hdfs的时候及其不稳定,也容易导致延迟产生。后续消费这块逻辑可能会迁移至flink来改造实现,目前这块仍在集成开发阶段中,待完善后会再次分享给读者们。另如果读者们如果有更好的解决方案,欢迎一起沟通讨论
往期推荐
Flink从入门到放弃之-入门篇(一)
教你如何使用正确姿势关闭SparkStreaming
数据开发必经之路-数据倾斜
元数据管理-技术元数据解决方案
2020年大厂面试题-数据仓库篇
一万字完整总结Flume
SparkStreaming完整学习教程
数据同步神器-Datax源码重构
zookeeper源码解读之-源码编译
zookeeper源码解读之-服务端启动流程
zookeeper源码解读之-DataTree模型构建+Leader选举
实战:如何实时采集上亿级别数据?
Spark数据倾斜之骚操作解决方案