网易云音乐Flink实时计算平台运维系统架构演进

2020-07-03 00:00:00 数据 用户 平台 实时 维系

本文由作者授权网易云发布

作者:李涵淼,网易云音乐数据平台组工程师

相比Spark来说,更多的国内公司愿意去使用Flink作为实时计算引擎解决当前公司的实时业务需求。网易云音乐从实时计算平台打造之初就定下Flink作为计算引擎,不只是考虑到Flink的优点(exactly-onece,state,丰富语义表达,执行性能等),同时也从其社区活跃度,版本更新速度,上下游扩展性,任务可运维性等多个维度进行了评估。本文主要介绍网易云音乐实时计算平台运维系统的架构演进,并对运维系统构建的过程中遇到的难题进行了描述和解决办法,希望可以对相关同学有帮助。

运维系统简介

网易云音乐构建实时计算平台之初,把重点都放在了平台开发上,包括平台的功能性,易用性和稳定性。随着平台越来越稳定,跑的任务数量越来越大,用户对单个任务和组任务的运维要求越来越高。一言以蔽之,用户想要有关自己任务的所有metrics,同时还需要在此基础上,对某些组任务的metrics进行聚合和展示。熟悉Flink的同学都知道,flink是有专门的metrics收集模块的,用户可以进入flink job页面进行metrics的查看。但是,就以目前Flink 1.7新版本来说,这种方法太不友好(页面反应慢;寻找对应metrics困难等)。也许开发人员可以去job页面去看,但是有很多的用户不是开发也并不熟悉flink,让这些用户去job页面看metrics并定位问题显然不现实。所以运维系统应运而生。

运维系统初始架构

在准备做运维系统之初,结合Flink自身含有的reporter,我对现有的开源框架进行了调研,后定了Graphite进行metrics收集,Graphana进行展示。期初选择使用graphite的原因有以下几点:

  • Graphite跟Flink结合的很好,Flink有现成的jar包和配置可用,无需进行开发
  • Graphite是Python写的,如果我们后期需要对graphite进行源码改写和定制化操作,无需进行重新打包编译的过程,直接重启服务即可。
  • Graphite可以进行metrics的预聚合和重命名。对于Flink上报上来的metrics,我们是肯定需要进行聚合操作的,这样才能直观的看出任务的总体情况。

总体的架构如下图:

初版运维系统架构

Flink任务定时发送metrics到graphite,graphite进行过滤(自己开发),聚合和重命名并存储到whisper中。用户在graphana上建立相应的监控图进行任务级别的metrics监控。

这一切看起来都简单而美好,直到运维系统上线后两周的一天,用户反映:

metrics监控图展示的数值和实际数值差距太大,不准确。

通过定位后,我发现graphite进程竟然占用CPU一直保持在99%高居不下。那问题来了,为什么刚上线的时候没有这样的情况呢?

通过调用集群机器监控历史图表发现,刚上线的时候,这台机器的CPU使用率的确不是太高,大概45%左右。而后又去查看实时计算平台大盘发现:平台任务个数两周内增加了35个,分析任务DAG后,看到有大部分任务的并行度特别大。

那原因就一目了然了:

导致CPU飙升到99%就是metrics数量激增引起的。由于graphite有单CPU瓶颈,如果数据量过大并且又进行正则匹配操作,CPU处理不过来肯定是会丢数据的,后导致graphana数据展示不准确。

那问题该怎么解决?这个是graphite本身受到单核限制,并没有太好的解决办法,以下是几个替代做法:

  • 实时计算平台底层启动flink任务的时候,设置多个client,每个client对应一个graphite,将数据分流。这样做的缺点是:增加了平台提交任务的判断逻辑;只能解决一时的问题,随着任务的增加,总有一天达到上限。
  • 数据到达graphite后,直接用carbon-cache进行分发,不经过carbon-agg做聚合。等到用户在graphana做查询的时候,再做聚合。缺点:由于flink的metrics命名规则很长,如果不做匹配重命名操作,很难在graphana写出查询规则。
  • 用prometheus替代graphite;缺点:由于flink 1.5版本里不支持prometheus gateway,如果使用prometheus,那必须知道实时计算平台所有机器的hostname。(prometheus是主动去拉取metrics的,并不是被动的接收)如果机器扩容或者换机器,那我们的拉取规则就需要跟着改变,耦合性太大。

以上几种方法都有各种各样的问题,那在1.5版本的限定下有没有更好的解决办法呢?

进化版运维系统

经过大量的调研工作,还真的让我找到了解决的办法。我决定舍弃graphite,取用promethues。上面,我说到了用promethues的短板所在,但是在新版的运维系统中,我并没有直接将metrics输出到promethues,而是用了如下图的架构:

改良版运维系统

从图中可以看出,这版运维系统,我引入了statsd和statsd_export。先解释下这两个组件:

  • statsd是很久就在github上开源了,是一个用于监听收集数据的应用。由于特别的轻量,使用和部署方便而被广泛使用。Flink本身也带有statsd reporter
  • statsd_export是为prometheus量身打造的应用,已经在社区开源,是statsd和prometheus中间的桥梁,被用于处理statsd收集的metrics同时本身作为服务对接prometheus

期初,这套系统搭建完毕以后,并没有遇到什么问题,metrics被statsd_proxy进行一致性哈希后分发到各个statsd实例中,从statsd实例的cpu和内存使用占比来看是足以应对大量的数据的。(statsd_proxy是可以开多线程的)

几天后,我决定将statsd_proxy和statsd进行分离,把statsd_proxy进程放在一台机器上,其他6个statsd实例分别两个一组放在其他三台机器上,目的是减少单台机器的负载。但是所有进程都启动后,问题出现了:

发现statsd_proxy的每个线程内存使用量持续增长,后吃完机器资源,进程down掉。严重的一次竟然把服务器跑死机了。

而后是漫长的问题定位过程,终于找到原因:

更改后和更改前的区别无非是更改后增加了statsd_proxy到各个statsd的网络开销,由于metrics量级很大,statsd_proxy收集metrics的速度大于网络发送的速度,后导致没有处理的metrics保存在内存中,内存持续增长。那为什么网络发送的速度比较慢呢?statsd_proxy我们可是开了多线程的啊!原因是因为即使我们开了多线程,但是往外发送的时候,我们是公用一个udp port的,多个udp socket没有绑定到同一个port上。那么疑问又来了:明明记得linux 3.9以后的版本操作系统提供了SO_REUSEPORT参数,允许多个sockets绑定到同一个port上,那为什么这里不行呢?又经过各种定位找出原因:==由于statsd是用nodejs实现的,而nodejs disable了这个选项。== 呵呵,多么坑爹的操作。

那怎么办,statsd是不是用不了了?我是不甘心的,所以又是各种找方法,终于上天不负苦心人,我在github上找到了statsd_proxy的C语言实现版本,并且是支持SO_REUSEPORT特性的。经过编译后启动,问题解决。

就这样平稳的过了一个多星期,突然又接到用户反馈:

发现graphana图表中旧的metrics并不会消失,而是会以一条直线的常量一直显示下去。这里解释一下这种现象:如果用户配置的是一张图显示多个flink任务的metrics,当其中一个任务被下掉后,按道理来说,这个任务的所有metrics都应该从图中消失。但事实情况并不是这样,而是会以这个任务下掉那一刻的值一直显示下去。

接下来又是漫长的问题定位过程,后依然被我找到了原因:

statsd_export是不支持metrics TTL的,只要是曾经发送到statsd_export中的数据,都会被保存下来,如果想让过期的metrics消失,只能重启进程。社区的哥们也通过邮件帮我确认了这种判定并且很给力的给出了解决办法:他给了我一个即将但是还没有release的statsd_export版本,这个版本是支持TTL特性的。我通过测试后发现是可行的,后部署上线。

从后一版运维系统上线至今,没有出现过任何的用户反馈问题和其他系统问题,稳定性还是可以的,这一路的踩坑过程真的是酸甜苦辣什么都有,但是只要是结果满意,那就没有什么值得抱怨的。

后,我提一下:Flink 1.7版本是支持metrics通过 gateway直接连接prometheus的,不需要像云音乐运维系统这样,中间需要依赖那么多的组件。大家可以去试验一下性能,一帆风顺还是一路坎坷,试过才知道!

相关文章