百变应用场景下,优酷基于图执行引擎的算法服务框架筑造之路( 二 )


数据源的封装通过动态参数配置方式实现通用性和可扩展性 。 数据查询只需要修改配置即可实现数据获取 , 不需要开发代码 。
3)基础算子
在DataSet数据集上封装的基本操作作为基础算子 , 比如Join、Union、Filter、Sort、Map、Collect等流式操作 。 在DataSet上重新封装Stream相关API , 便于对DataSet进行流式处理 。 对于大多数非Action类操作 , 通过流处理API延迟处理 。
4)业务算子
召回、预估、合并、打散、过滤等业务操作封装为业务算子 , 在业务算子中可以查询数据源 , 返回数据集后通过基础算子计算得到结果 。 最终业务图中执行单元为业务算子和数据源 , 业务图中通过对数据源和业务算子的依赖关系进行配置 , 表达业务逻辑 。
2. 图形化配置DAG
算子实现代码上通过标注的方式声明算子可配属性的相关描述 , 比如属性名称、属性类型、描述、取值范围、是否必填等 。 图形化配置页面读取算子元数据识别算子可配信息在页面展现 。 通过拖拽的方式将算子组成DAG执行图 , 平台内部实现图配置和AB配置的互相转换和兼容 , DAG图结构的保存同时会转换为AB键值配置并保存 。 同时支持配置实时刷新和算子元数据更新 。 通过分组的方式将大图拆分为多个子图 , 便于图展现和维护 。
百变应用场景下,优酷基于图执行引擎的算法服务框架筑造之路
本文插图
图:图形化配置
3. 配置动态解析和优化
1) 根据AB配置实时变更图执行结构
图引擎在运行时为了减少解析图结构的耗时 , 将图结构进行了缓存 , 在AB配置更新时需要实时反映到图引擎中 , 所以根据图配置的哈希值校验的方式检测图配置是否更新 , 图结构变更后会重新创建引擎实例 。
2)子图并行线程优化
在DAG执行时 , 所有算子都交给线程池异步运行 , 但是在大多数情况下子图可能是一个顺序执行图 , 不需要并行 , 不应该占用其他线程 , 所以在图执行时 , 动态根据依赖关系识别节点是否需要占用新线程运行 。
3)条件分支动态裁剪
如果图结构中存在条件节点 , 会根据条件节点的动态结果裁剪后续图节点的运行 。 如果一个图节点的执行条件为否 , 后续单独依赖它的节点都不会运行 , 条件节点具备传递性 。 如果后续节点不单独依赖不运行的节点 , 则当前节点可运行 。
4. DAG图执行引擎
1) 并发控制
通过图中依赖关系自动解析节点需要通过并行还是串行执行 , 最大程度复用线程 , 减少线程切换带来的开销 。 图执行过程中 , 并不是线程开的越多越好 , 在图中可以配置最大并发线程数来控制图的最大并发度 , 防止并行过多造成开销过大 。
2)超时控制
通过整个图上配置超时时间来控制图的超时 , 根据业务粒度 , 会将子业务配置为子图 , 从而通过控制子图的超时时间来控制子业务的超时时间 。
3)异步化执行
算子在图中的运行是全异步化的 , 算子之间通过Reactive模式进行依赖触发 。
4)通过协程优化异步执行
AliJDK支持协程 , 在JVM层面可以将线程优化为协程执行 。 让用户的代码能够轻量级地分配到多个核上充分利用机器资源 , 同时遇到阻塞逻辑 , 也能够通过运行时主动挂起任务 , 将线程让出给队列中的任务 。
DAG运行依赖线程池运行 , 算法实验平台提供了基础线程池 , 并同时将线程池在JVM内部优化为协程 , 通过压测比对 , 在IO阻塞逻辑比较多的情况下 , 协程池的性能要优于普通线程池 。
5. Debug调试
1) 线程调用树
在所有算子运行时都会通过性能分析工具在最小影响执行性能的情况下记录节点的耗时时间 , 通过组装为调用树的形式进行输出 , 在整个请求超时情况下 , 输出到对应日志 , 便于性能和问题分析 。


推荐阅读