如需转帖,请征得原作者同意
StreamCQL应用程序执行流程
前戏: CQL代码结构
之前我们并没有梳理CQL部分的代码结构, 在分析了差不多的代码之后, 来看看每个部分都一一对应:
还没有涉及的包括: PhysicalPlan,物理计划/逻辑计划优化器,executors执行器.
正文: submitApplication
历经千辛万苦, 终于回到SubmitTask的submitApplication, 创建物理计划Executor,并执行Application.
1 2 3 | private void submitApplication() { new PhysicalPlanExecutor().execute(context.getApp()); } |
api.Application -> application.Application
api的Application是流处理执行计划应用程序, 封装的是CQL语句构建而成的应用程序:
1 2 3 4 5 6 7 8 9 10 | public class Application { private String applicationId = null; //应用id private String applicationName = null; //应用名称 private TreeMap<String, String> confs; //整个应用程序中用到的配置属性,也包含用户自定义的配置属性 private String[] userFiles; //用户自定义添加的一些文件 private List<UserFunction> userFunctions; //用户自定义的函数,udf和udaf都在这个里面 private List<Schema> schemas = new ArrayList<Schema>(); //执行计划中的所有的schema private List<Operator> operators = null; //执行计划中所有的操作,包含输入、输出和计算操作 private List<OperatorTransition> opTransition = null; //整个执行计划中所有的连接线,定义了operator之间的连接关系 } |
还记得上一篇中在buildApplication时,由SplitContext拆分算子,组合算子会把operators和transitions都设置到Application里吗?
application.Application针对Schema和Operator采用Manager管理类(实际上底层的存储结构都是Map)来操作:
1 2 3 4 5 6 | public abstract class Application { private String appName; //应用程序名称 private EventTypeMng streamSchema; //所有Schema集合 private OperatorMng operatorManager; //算子集合 private StreamingConfig conf; //系统级别的配置属性 } |
Application在API
和物理计划
是不同的对象, 同样API阶段的Operator
在物理计划中对应的是IRichOperator
.
IRichOperator
OperatorMng管理的算子包括输入算子(addInputStream),输出算子(addOutputStream),功能算子(addFunctionStream).
虽然两个Operator处于不同的阶段, 但是总的来说都可以分为输入,输出和Function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ① application ⬇️ ② api ⬇️ IRichOperator (com.huawei.streaming.operator) Operator (com.huawei.streaming.api.opereators) |-- AbsOperator |-- FunctionStreamOperator |-- FunctionOperator |-- InnerOutputSourceOperator |-- JoinFunctionOp |-- SplitterOperator |-- DataSourceFunctionOp |-- OutputStreamOperator |-- AggFunctionOp |-- InputStreamOperator |-- SplitOp |-- InnerInputSourceOperator |-- UnionFunctionOp |-- InnerFunctionOperator |-- SelfJoinFunctionOp |-- FunctorOperator |-- FunctorOp |-- UnionOperator |-- FilterFunctionOp |-- FilterOperator |-- OutputOperator |-- BasicAggFunctionOperator |-- FunctionStreamOperator |-- JoinFunctionOperator |-- InputOperator |-- AggregateOperator |-- BaseDataSourceOperator |
IRichOperator流处理算子
基本接口: 所有的流处理相关的算子实现,都来源于这个算子, 所有的外部Storm实现均依赖于这个接口
正常的CQL insert语句只有一个输出,所以在前面的SplitContext中会有一个outputStreamName和一系列的operators和transitions.
但是这里对于IRichOperator流算子, 它是构成Storm的Topology组件,就必须考虑算子之间数据的流动,一个Bolt可能有多个输入和输出.
对于一个算子而言,输入数据可以有多个.但是输出是只有一个! 这就好比最终的select只会有一个输出schema: select输出的数据作为算子的输出.
所以IRichOperator的输入getInputStream和getInputSchema都是集合, 而输出的getOutputStream和getOutputSchema都是单一的.
Update: 输入输出这里其实看流,反正是一个流一个名称。都是允许多输入多输出的。一个算子就是一个流,一个算子的多个实例都属于一个流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public interface IRichOperator extends IOperator, Configurable{ //① 流处理算子基本接口 String getOperatorId(); //获取算子id int getParallelNumber(); //获取算子并发度 List<String> getInputStream(); //获取输入流名称, 多个输入流 String getOutputStream(); //获取输出流名称 Map<String, IEventType> getInputSchema(); //获取输入schema ⬅️ <key是输入流名称,IEventType是输入流的Schema> IEventType getOutputSchema(); //获取输出schema ⬅️ Map<String, GroupInfo> getGroupInfo(); //获取分组信息 } public class Operator { // ② 每个计算单元成为一个Operator,定义了各类操作.分为Source和InnerFunction private String id; //算子ID private String name; //算子名称 private int parallelNumber; //并行度 private TreeMap<String, String> args; //每个operator的参数 } public class OperatorTransition { // ③ 各种Operator的连接关系,定义了从一个operator到另外一个operator的连接 private String id; //当前连接的id private String streamName; //流名称 private String fromOperatorId; //发起连接的Operator id private String toOperatorId; //接收连接的Operator id private DistributeType distributedType; //数据获取类型,仅仅在非sourceOperator中存在 private String distributedFields; //数据分发字段, 仅在distributedType为field的时候生效 private String schemaName; //流上进行数据传输的时候的schema名称 } |
还记得上一章在创建Transition时会指定分组字段和分组策略吗? 和这里的GroupBy应该是会有点血缘关系的. 当然算子还少不了输入输出Schema.
PhysicalPlanExecutor -> ExecutorPlanGenerator
PhysicalPlanExecutor.execute传入的是API的Application, 而submit(app)的app是application.Application.
怎么转换: 通过ExecutorPlanGenerator物理计划生成器生成可执行的计划. 可执行指的是可以运行在Storm引擎.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void execute(Application apiApplication) { LOG.info("start to execute application {}", apiApplication.getApplicationId()); parseUserDefineds(apiApplication, isStartFromDriver); //① 准备工作 com.huawei.streaming.application.Application app = generatorPlan(apiApplication); // ⬅️ API.App->application.App submit(app); //④ 提交Application } //由物理执行计划api.Application生成可执行计划application.Application(最终生成的,可以提交的应用程序) private com.huawei.streaming.application.Application generatorPlan(Application apiApplication) { preExecute(apiApplication); //执行器执行之前的钩子 new PhysicPlanChecker().check(apiApplication); //② 用户自定义的处理: 执行计划的组装, 构建application, 表达式的解析被延迟到这里来实现 com.huawei.streaming.application.Application app = generator.generate(apiApplication); preSubmit(app); //提交执行计划之前的钩子 executorChecker.check(app); //③ 执行计划检查 return app; } |
①准备工作parseUserDefineds
: 注册jar包,注册函数,打包等(类似storm中需要上传jar包).
1 2 3 4 5 6 7 8 9 | 2015-11-25 02:32:24 | INFO | [main] | |