来自:阿里巴巴中间件

写在前面


写过 Blink SQL 的同学应该都有体会,明明写的时候就很顺滑,小手一抖,洋洋洒洒三百行代码,一气呵成。结果跑的时候,吞吐量就是上不去。导致数据延迟高,消息严重积压,被业务方疯狂吐槽。这时候,老鸟就会告诉你,同学,该优化优化你的代码了,再丢过来一个链接,然后留下一脸懵逼的你。笔者就是这么过来的,希望本文能帮助到跟我有过同样困惑,现在还一筹莫展的同学。

背景故事


先说一下相关背景吧,笔者作为一个刚入职阿里的小白,还处在水土不服的阶段,就被临危受命,改造数据大屏。为什么说临危受命呢,首先是此时距双十一仅剩一个月,再者,去年的双十一,这个大屏刚过零点就出现问题,数据一动不动,几个小时后开始恢复,但仍然延迟严重。此前,笔者仅有的实时计算开发经验是 storm ,用的是 stream API ,对于 Blink 这种 SQL 式的 API 完全没接触过。接到这个需求的时候,脑子里是懵的,灵魂三问来了,我是谁?我即将经历什么?我会死得有多惨?不是“此时此刻,非我莫属”的价值观唤醒了我,是老大的一句话,在阿里,不是先让老板给你资源,你再证明你自己,而是你先证明你自己,再用结果赢得资源,一席话如醍醐灌顶。然后就开始了一段有趣的故事~

压测血案


要找性能问题出在哪儿,最好的方法就是压测。这里默认大家都对节点反压有一定的了解,不了解的请先移步典型的节点反压案例及解法。

一开始是跟着大部队进行压测的,压测的结果是不通过!!!一起参加压测的有三十多个项目组,就我被点名。双十一演练的初夜,就这样伤心地流走了。西湖的水,全是我的泪啊。不过痛定思痛,我也是通过这次压测终于定位到了瓶颈在哪里。

瓶颈初现


数据倾斜

在做单量统计的时候,很多时候都是按商家维度,行业维度在做 aggregate ,按商家维度,不可避免会出现热点问题。

HBase写瓶颈

当时我在调大 source 分片数,并且也无脑调大了各个算子的资源之后,发现输出 RPS 还是上不去, sink 节点也出现了消息积压。当时就判断, HBase 有写瓶颈,这个我是无能为力了。后来的事实证明我错了, HBase 的确有写瓶颈,但原因是我们写的姿势不对。至于该换什么姿势,请继续看下去。

神挡杀神


先来分析一下我们的数据结构(核心字段):
biz_date, order_code, seller_id, seller_layer, order_status, industry_id 。

我们 Group by 的典型场景有:

CREATE VIEW order_day_view ASSELECTindustry_id,seller_layer,biz_date,count(distinct order_code) AS salesCountFROMorder_viewGROUP BY industry_id,seller_id,seller_layer,biz_date
;
总结下来就是,按卖家维度,行业维度什么的,都非常容易出现数据倾斜。

数据倾斜其实有很多解法,这里我不展开讨论,只讲我们这个案例的解法。

倾斜的原因,无非就是 Group by 的字段出现了热点,大量的消息都集中在了该字段少数几个取值上。通常的解法是,在消息中选择具备唯一性,或者预估会分布比较均匀的字段。如果这个字段是整型的,可以直接取模(模数一般是节点的并发数),如果是字符串,可以先进行哈希计算,再取模,得到一个分片地址(本文取名为bucket_id)。在接下来的所有 aggregate 算子中,都要把他作为 Group by 的 key 之一。

在我们这个案例中,我们选择了 order_code 这个具备唯一性的字段。首先在源头把分片地址算出来,加到消息里面,代码如下:

SELECT
o.biz_date, o.order_code, o.seller_id, o.seller_layer, o.order_status, o.industry_id, o.bucket_id
FROM (select *,MOD(hash_code(order_code), 32) AS bucket_id from order_stream) o

然后把这个 bucket_id 层层传递下去,在每一个需要 Group by 的地方都在后面带上 bucket_id ,例如:

CREATE VIEW order_day_view ASSELECTindustry_id,seller_layer,biz_date,count(distinct order_code) AS salesCount,bucket_idFROMorder_viewGROUP BY industry_id,seller_id,seller_layer,biz_date,bucket_id
;

事实上,我一开始想到的是用下面 Tips 里的方法,结果就杵进垃圾堆里了,性能问题是解了,但是计算出来的数据都翻倍了,明显是错的。至于我是怎么发现这个问题,并分析其原因,再换了解法,又是另一段故事了。可以提前预告一下,是踩了 Blink 撤回计算的坑,后面会再出一个专题来讲述这个故事哒

这里还想再延伸一下,讲讲我的学习方法。如果读者中有跟我一样的小白,可能会奇怪,同样是小白,为何你这么秀,一上来就搞压测,还能准确地分析出性能的瓶颈在哪里。其实有两方面的原因,一方面是我有过 storm 的开发经验,对实时计算中会遇到的坑还是有一定的认识;另一方面,是我没说出来的多少个日日夜夜苦逼学习充电的故事。我的学习习惯是喜欢追根溯源,就找了很多介绍 Flink 基本概念,发展历史,以及跟流式和批处理计算框架横向对比的各类博客。而且带着kpi去学习和什么包袱都没有去学习,心态和学习效率是不一样的。前者虽然效率更高,但是是以损害身心健康为代价的,因为学习过程中不可避免的会产生急躁情绪,然后就会不可避免的加班,熬夜,咖啡,再然后他们的好朋友,黑眼圈,豆豆,感冒就全来了。后者虽然轻松,但是什么包袱都没有,反而会产生懈怠,没有压力就没有动力,这是人的天性,拗不过的。这就是矛盾的点,所以在阿里,经常提到“既要也要还要”,其实宣扬的是一种学会平衡的价值观。至于怎么平衡,嘻嘻,天知地知我知。对,只能自己去领悟怎么平衡,别人教不会的。

概念有了一定的认知,下面就开始实践了。整个实践的过程,其实就是在不断的试错。我是一开始连反压的概念都不知道的,一直在无脑的调大 CU ,调大内存,调高并发数,调整每两个节点之间的并发数比例。寄希望于这样能解决问题,结果当然是无论我怎么调,吞吐量都是都风雨不动安如山。现在想想还是太年轻呀,如果这样简单的做法能解决问题,那那个前辈就绝对不会搞砸了,还轮的到我今天来解决。后来也是在无尽的绝望中想通了,不能再这么无脑了,我要找其他法子。想到的就是在代码层面动刀子,当然试错的基本路线没有动摇,前面也提到过,我一开始是想到的“加盐”,也是在试错。

学习方式决定了我做什么事,都不可能一次成功。甚至有很多情况,我明知道这样做是错的,但我就是想弄明白为什么行不通,而故意去踩这个坑。不过也正是因为试了很多错,踩了很多坑,才挖出了更多的有价值的知识点,扩大了知识的边界。

此时无声胜有声,送上几句名言,与诸君共勉:
1、塞翁失马,焉知非福。---淮南子·人间训
2、一切过往,皆为序章。---阿里巴巴·行癫
3、学习就像跑步一样,每一步都算数。---百阿·南秋

tips: 如果在消息本身中找不到分布均匀的字段,可以考虑给每一条消息加上一个时间戳,直接使用系统函数获取当前时间,然后再对时间戳进行哈希取模计算,得到分片地址。相当于强行在时间维度上对消息进行打散,这种做法也被形象的称为“加盐”。

佛挡杀佛


上一段看下来,似乎只解决了数据倾斜的问题。之前还提到有一个 HBase 写瓶颈问题,这个该如何解呢?

还是接着上面的思路继续走下去,当我们把 bucket_id 一路传递下去,到了 sink 任务的时候,假设我们要按商家维度来统计单量,但是别忘了,我们统计的结果还按订单号来分片了的,所以为了得到最终的统计值,还需要把所有分片下的值再 sum 一下才行,这大概也是大多数人能想到的常规做法。而且我们现有的 HBase rowKey 设计,也是每个维度的统计数据对应一个 rowKey 的,为了兼容现有的设计,必须在写 HBase 之前 sum 一下。

但是笔者当时突发奇想,偏偏要反其道而行之,我就不 sum ,对于 rowKey ,我也给它分个片,就是在原来 rowKey 的基础上,后面再追加一个 bucket_id 。就相当于原来写到一个 rowKey 上的数据,现在把他们分散写到 64 个分片上了。

具体实现代码如下:

INSERT INTO hbase_result_sinkSELECTCONCAT(businessRowkey, '|', bucket_id) AS businessRowkey,cast(uopAcceptCount as DECIMAL)from hashBucket_view

这样一来, API 也必须改造了,读的时候采用 scan 模式,把所有分片都读出来,然后求和,相当于把 sum 的工作转移到 API 端了。

这样做的好处在于,一方面可以转移一部分计算压力,另一方面,因为 rowKey 只有一个,而我们写 rowKey 的任务(即 sink 节点)并发数可能有多个, Java 开发者应该都深有体会,多线程并发对一个变量进行累加的时候,是需要加锁和释放锁的,会有性能损耗,可以猜测, HBase 的写瓶颈就在于此。后来的事实也证明,这种做法将输出 RPS 提升了不止一个两个档次。

赶考当天


人事已尽,接下来就是关二爷的事了( ̄∇ ̄)。双十一零点倒计时结束,大屏数字开始飙升起来,随之一起的,还有我的肾上腺素。再看看数据曲线,延迟正常,流量峰值达日常的 10 倍。其实结果完全是在预期之内的,因为从最后一次的压测表现来看, 100W 的输入峰值(日常的 333 倍), 5W 的输出峰值(日常的 400 倍),都能稳稳的扛下来。出于数(懒)据(癌)安(晚)全(期)的角度考虑,很多大屏和数据曲线的截图就不放出来了。

其实现在回过头再看,此时的内心是平静如水的。不是大获全胜后的傲娇,也不是退隐山林的怯懦。只是看待问题的心态变了。没有翻不过的山,没有迈不过的坎。遇事不急躁,走好当下的每一步就好,也不必思考是对是错,因为每一步都算数,最后总能到达终点。

浮生后记


笔者写文章习惯带一些有故事趣味性的章节在里面,因为我觉得纯讲技术,即使是技术人看起来也会相当乏味,再者纯讲技术的前提是作者具备真正透进骨髓去讲述的功底,笔者自认为还相差甚远,只能加点鱼目来混珠了。换个角度来看,纯技术性的文章,观赏性和权威性更强,每一句都是精华,这种咀嚼后的知识虽有营养饱满,但是不是那么容易消化,消化后能吸收多少,还有待确认。所以我力求展示我的咀嚼过程,更多是面向跟我一样的小白用户。

作者信息:

王科,花名伏难,先后经历国内三大电商平台,苏宁,京东,阿里,电商大战深度参与者。现任职阿里巴巴供应链事业部,从事业务平台化改造、实时计算相关工作。信奉技术自由平等,希望通过简单形象的语言,打破上层建筑构建的知识壁垒,让天下没有难做的技术。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

一个 Blink 小白的成长之路相关推荐

  1. excel函数手册_一个函数高手的成长之路

    作者 | Jodie 秋叶Excel训练营优秀学员 早上跑步听樊登读书会,讲到家长如何培养孩子的社交能力,有一个例子非常好. 你的孩子因为一个玩具,和其他的小朋友争吵起来了,你会怎么处理呢? 大部分家 ...

  2. 《小白H5成长之路19》if判断语句的一些用法

    "小白,你面临过选择么?" "面临过啊,今天上午就在犹豫是骑自行车还是坐公交,出门一看天太冷,所以就做公交来上班了." "如果不冷你就骑自行车来了,对 ...

  3. 《小白HTML5成长之路38》插入视频的功能好简单

    "朱哥,现在视频站好火啊!那些视频是怎么放到网页上进行播放的呢?一定很麻烦吧!" 老朱:"不麻烦,HTML5标准下,一个video标签就搞定了." 小白:&qu ...

  4. 《小白H5成长之路50》js与PHP配合完成图片上传功能

    "小白!还记得之前我们说过的jQuery中的ajax异步加载数据的方法么?" "记得,之前的代码我这里还有,你看看是不是这个?" 老朱:"恩,没错,通 ...

  5. 十年风雨,一个普通程序员的成长之路(五) 成长:得到与教训

    目录 十年风雨,一个普通程序员的成长之路(五) 一.前言:生活的演变 二.成长:得到与教训 十年风雨,一个普通程序员的成长之路(五) author 妖生 date 2019.06.09 一.前言:生活 ...

  6. 芯片工程师成长之路_一个FPGA工程师的成长之路

    最近逻辑组任务较多,人力不足,因此招了一些新员工.最近一段时间,也面试了很多人,各个行业和公司的都有,形形色色的人面试多了,也有一些感触,另外,年近而立,也需要总结一下.在此记录下来,与君共勉. 关于 ...

  7. 十年风雨,一个普通程序员的成长之路(一)怀念:西安的小黑屋

    受大飞的文字及李莹大哥的影响,也写了篇自己毕业以来,十年生涯的回顾.还在沉默王二的文章激励下一鼓作气把公众号开通了,发了这篇公众号的首篇文章. 终于开通了自己第一个公众号,发表了第一篇文章.看着空白的 ...

  8. 十年风雨,一个普通程序员的成长之路(八)不想做技术总监的项目经理,不是好程序员...

    目录 十年风雨,一个普通程序员的成长之路(八)不想做技术总监的项目经理,不是好程序员 01 技术总监写不写代码? 02 面试的坎坷与杯具 03 新的开始 & 旧的结束 十年风雨,一个普通程序员 ...

  9. 科研小白的成长之路——博一年度总结

    2020年9月,背负行囊,襟怀炽热之心,带着对未来的憧憬,我跨越大半个中国从广西老家来到哈工大,开始了科研求学之旅.时光荏苒,想想自己从博士入学时的懵懂,到现在逐渐适应"博士僧"生 ...

最新文章

  1. Android学习笔记(三):Andriod程序框架
  2. XML —— DTD介绍
  3. “约见”面试官系列之常见面试题之第七十篇之==和===(建议收藏)
  4. LeetCode 1087. 字母切换(回溯)
  5. 图深度学习前沿工作汇总与解析
  6. 技术干货 | 应用性能提升 70%,探究 mPaaS 全链路压测的实现原理和实施路径
  7. 华为第二批“十大军团”正式成立!
  8. 模拟电子技术不挂科学习笔记3(放大电路的分析方法)
  9. Zabbix 4.0.0 新功能介绍
  10. JS 中的== 与 ===
  11. 读书 -- 个人购书经验总结
  12. 【已解决】您的PHP似乎没有安装运行WordPress所必需的MySQL扩展
  13. 游侠随笔:关于业务型数据库审计 有图有真相
  14. 【错误解决】Spring JPA的错误及其解决方案
  15. 小米手机刷机失败补救方法详解
  16. 关于pingpp(招行一网通)-混淆
  17. 影响内存频率的几个因素
  18. python3模拟键盘输入_python 模拟键盘输入
  19. 13. Linux权限管理命令
  20. 20 人机猜拳互动游戏开发

热门文章

  1. poj2240(Bellman-ford)
  2. 关于使用JAVA中JDK安装和在命令行中编译和运行程序的一些总结
  3. 线段树 ---- 牛客多校2021多校第6场 H Hopping Rabbit 扫描线
  4. python全栈慕课网靠谱么_全栈和python的区别 ?
  5. c++ eos智能合约开发_十分钟教你开发EOS智能合约
  6. 【网络流24题】解题报告:C、最小路径覆盖问题(有向无环图最小路径覆盖)(最大流)
  7. mysql8.0_grant改变-You are not allowed to create a user with GRANT
  8. 计算机图形学直线扫描转论文,计算机图形学实验报告-实验1直线段扫描转换.doc...
  9. 中双目运算符_C++日志(四十)教你如何以非成员函数的形式重载运算符
  10. 你属于程序员中的哪种人?