Flink 动态实时流计算

2020-07-02 00:00:00 代码 逻辑 计算 加载 引擎

(先给个预告,下一期关于Flink的文章会讲如何将机器学习融入Flink中)

摘要

本文提供了一种在流计算中不停机动态加载代码来做到敏捷而快速的开发的思路。

代码提供在 Lofka 的 lofka-night-watcher 模块中。

TsingJyujing/lofkagithub.com

目前利用JavaScript(仅支持ECMA5的语法)编写的动态脚本可以支持:

  • HTTP读写
  • RedisCluster操作
  • 所有有JDBC Driver的SQL的操作
  • MongoDB的操作
  • 导入第三方的库

基本上可以完成大部分的不定需求的快速开发。

前言:什么是配置

之前拜读了一下这篇文章:

一篇好TM长的关于配置中心的文章jm.taobao.org

,在 Dubbo Meetup 时有幸听过作者本人的演讲。

其中有一个基本的概念:一个大型的,分布式的系统,停下来不是那么容易的。所以不太可能停下来修改配置再发布,我们需要在运行中控制软件行为,这个时候我们就需要配置中心。

正如文章所说:

映射到软件领域上,我们总是需要对系统的某些功能特性预留出一些控制的线头,以便我们在未来需要的时候,可以人为的拨弄这些线头从而控制系统的行为特征,我把它叫做 “系统运行时(runtime)飞行姿态的动态调整“。

近正好着手公司的整个流计算框架的重构,其实流计算和大型分布式系统(虽然我们做的流计算本身已经是分布式的了)有着异曲同工之妙——很TM不好停。

一个流计算程序,动辄运行几周,乃至几个月,停下来以后,只能从上一次的Checkpoint的状态恢复,好在我们的确保后续操作都做到了幂等,可以用At Least Once,如要做Exactly Once的消费,又会徒增很多工作量。

不能停机的时候,动态的调整软件的行为就显得很重要。

举几个例子:

  1. 围栏报警,在车辆出入围栏的时候进行报警,围栏有可能是用户随时画出来的,如何即时生效?
  2. 报警有的需要短信,有的需要邮件,如何做?
  3. 某天增加了一个接口用于APP内推送,如何推送?如何做到不停止流计算的情况下增加这个功能?
  4. 数据库地址变更,能否在不停止的情况下切换?
  5. 报警X太多了,导致短信太多,决定下线这个报警类型,如何做?

以上问题都不难,但是如果想要减少停机的影响就变得比较麻烦。 除了这些问题,一些监控指标参数的变更,几个参数的联动逻辑,往往不是能一次性确定好的。

更加可气的是,有些产品经理脑子瓦特了,刚提的需求过半个小时就改,真想打到他提肛。 除了红烧产品经理以外,我们也必需有个方法应对不明确、且必须要做的需求。

我们必须搞出一个解决方案,应对脑子坏掉的自己、领导和产品。

我们现在的问题

对于上面五个问题,我们可能会考虑:

  1. 轮询数据库(可能加一个Cache防止读写过于频繁),检查每个用户的围栏,计算后将结果吐出去来解决问题1。
  2. 仍然是读取数据库的配置,判断如何推送解决2。
  3. 改代码重新发布解决3。
  4. 用一个线程监听配置中心上的配置,数据库地址变化的时候锁住不让读写,然后把Connection(或者Druid)完成功能4。
  5. 改代码重新发布解决5。

当然,上面的方案并不完美,在流计算的开发与维护中,只有20%是涉及结构的变化,大部分的变化都是细节的逻辑变化,或者在原来的基础上做一些增减,原来放到Redis的数据现在要放到MongoDB,而后除了要写MongoDB还需要做一些其他操作:例如推送部分特殊的消息到X接口。

往往是随着业务的不断扩展,需求也变得越来越复杂。 这不是一件坏事(完全没业务才是坏事),但是慢慢的干净的流计算代码就开始腐化。出现且不限于以下情况:

  1. 基础库的冲突,你用Protobuf 2.5,而我是3.0起步这样的事情
  2. 业务堆砌,代码成坨,很多业务刚上线需求就变化了,于是又新建了一个类实现了某个接口……
  3. 出于稳定性,流计算不太好停机,所以需求通常是攒了一堆再发布,发布严重滞后,这对很多以快打慢的公司是个致命问题

后忍无可忍,通过复制和剪切代码将一部分业务单独剥离了出去,集群中开始跑起了第二个流计算服务,Kafka的消费率也从变成了200%。

这只是噩梦的开始。

一些基础的部分(如反序列化)可能用的是同一套代码,一套修改以后,另一套也要修改,有远见的人可能已经将公共部分抽取出来放到了私有的Maven上,当然这不能避免事情变得更加糟糕——终于有一天流计算的框架或者是数据流的拓扑也需要修改了,那带来的代价是巨大的。

我们不仅要更新业务,还需要更新逻辑。

一个实用且不完美的解决方案

我这里会给出一个解决方案,说来也很简单,就是用 Nashorn 解释器动态加载执行 JavaScript 脚本来剥离业务逻辑,并且构建代码的自动重新加载机制,使得流计算在不停机的情况下迅速切换逻辑。

对于数据源较为单一的数据处理,我们可以将流计算抽象为:

Source[T]-->FlatMap(List[Function[T->Unit]])

相关文章