Netflix Conductor: 微服务编排器

hwwang2 8年前
   <p>Netflix Content Platform Engineering团队运行着很多商务流程,这些流程由在微服务上执行的异步编排驱动。其中一些是会运行好几天的长流程。这些流程在准备好视频流以供全球观众观看的过程中起着至关重要的作用。</p>    <p>这些流程包括:</p>    <ul>     <li>为了内容吸收的Studio合作伙伴的集成</li>     <li>来自合作伙伴的基于 IMF 的内容吸收</li>     <li>在Netflix里搭建新字幕的流程</li>     <li>内容吸收,编码,以及部署到CDN上</li>    </ul>    <p>传统上,这些流程中的一些是使用pub/sub(发布/订阅)模式,直接调用REST以及使用数据库管理状态这些方法的组合来实现,以ad-hoc的方式完成整体编排。但是,随着微服务数量的增加,以及流程复杂度的提高,如果没有中央式的编排,理解这些分布式工作流会变得非常困难。</p>    <p>我们将Conductor构建为“编排引擎”,来解决如下需求,代替应用中对样板文件的需要,同时提供交互式流程:</p>    <ul>     <li>基于Blueprint。基于JSON DSL的blueprint定义执行流。</li>     <li>跟踪并且管理工作流</li>     <li>能够暂停,恢复以及重启流程</li>     <li>视图化流程流的用户接口</li>     <li>能够在需要时同步处理所有任务</li>     <li>能够扩展为百万级并发运行流程流。</li>     <li>对由客户抽象出的队列服务支撑</li>     <li>能够做基于HTTP或其他传输协议,比如gRPC的操作。</li>    </ul>    <p>构建Conductor是为了满足上述需求,至今已经在Netflix使用了大概一年时间。到目前为止,它已经帮助编排了超过260万流程,这些流程包括简单的线性工作流,也包括非常复杂的运行数天的动态工作流。</p>    <p>现在,我们将 Conductor 开源,放到了社区里,希望能够从有类似需求的其他公司学习,并且加强它的功能。</p>    <h3>为什么不使用点对点编排?</h3>    <p>我们发现,使用点对点任务编排很难随着增长的业务需求和复杂度而完成扩展。Pub/sub模型适用于最简单的流程,但是很快你就会发现该方案的一些问题,包括:</p>    <ul>     <li>流程流被“嵌入到”多个应用程序的代码里</li>     <li>通常,围绕输入/输出,SLA等存在很强的耦合以及假定,这使得更加难以适应变化的需求</li>     <li>几乎没有办法系统性地回答“某个电影的搭建还剩下什么任务有待完成的”?<br> ###为什么选择微服务?<br> 在微服务的世界里,很多业务流程自动化是由跨服务的编排驱动的。Conductor在启用跨服务的编排的同时,能够提供对微服务之间交互的控制和洞察。拥有跨微服务的编排能力还帮助我们利用已有服务构建新的流,或者更新已有流让其非常快速地就可以使用Conductor,高效地提供了引入Conductor的快捷方式。</li>    </ul>    <h3>架构概览</h3>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/cfc729b5326256e9a3a826d6c68097ff.png"></p>    <p>该引擎的核心是状态机服务,也称为Decider服务。随着工作流事件的发生(比如,任务完成,失败等),Decider将工作流blueprint和该工作流的当前状态组合起来,确定下一个状态,并且调度合适的任务,并且/或者更新该工作流的状态。</p>    <p>Decider和一个分布式队列协同工作来管理调度的任务。我们在 Dynomite 之上使用 dyno-queues 来管理分布式延迟队列。</p>    <h3>任务Worker的实现</h3>    <p>任务,通过worker应用程序实现,通过API层通信。Worker有两种实现方式,要么通过可以被编排引擎调用的REST端点来实现,要么通过池循环来周期性检查待定任务实现。Worker想要设计成幂等的无状态功能。池模型允许我们处理worker上的反压力,并且可以提供基于队列深度的自动扩展能力。Conductor提供API监督每个worker的工作负载大小,可以用来自动扩展worker实例。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f59bf80ec200b998435e0ba660bb599e.png"></p>    <p>Worker和引擎的通信</p>    <h3>API层</h3>    <p>API通过HTTP暴露——使用HTTP使得可以轻松地和不同的客户端集成。同时,添加另一种传输协议(比如,gRPC)应该是可能的并且相对直接。</p>    <h3>存储</h3>    <p>我们使用 Dynomite “作为存储引擎”,以及Elasticsearch索引执行流。存储API是可插拔的,并且能够适应多种不同的存储系统,包括传统的RDBMS或者Apache Cassandra这样的no-sql存储。</p>    <h3>核心观点</h3>    <p>工作流定义</p>    <p>工作流定义使用基于DSL的JSON来定义。工作流blueprint定义需要执行的一系列任务。每个任务要么是一个控制任务(比如,fork(分支),join(合并),decision(决策),sub workflow(子工作流)等等),要么是一个worker任务。对工作流的定义作版本化控制,提供管理升级以及迁移的灵活性。</p>    <p>一个工作流定义示例:</p>    <pre>  {    "name": "workflow_name",    "description": "Description of workflow",    "version": 1,    "tasks": [    {      "name": "name_of_task",      "taskReferenceName": "ref_name_unique_within_blueprint",      "inputParameters": {        "movieId": "${workflow.input.movieId}",        "url": "${workflow.input.fileLocation}"      },      "type": "SIMPLE",      ... (any other task specific parameters)    },    {}    ...    ],    "outputParameters": {    "encoded_url": "${encode.output.location}"    }    }  </pre>    <p>任务定义</p>    <p>每个任务的行为都受其模板的控制,该模板称为任务定义。任务定义为每个任务提供控制参数,比如超时,重试策略等。一个任务可以是一个由应用程序实现的worker任务,也可以是由编排服务器执行的系统任务。Conductor提供了开箱即用的系统任务,比如Decision,Fork,Join,Sub Workflow,以及一个SPI,允许集成自定义的系统任务。我们也增加了对HTTP任务的支持,可以辅助调用REST服务。</p>    <p>任务定义的JSON片段:</p>    <pre>  {    "name": "encode_task",    "retryCount": 3,    "timeoutSeconds": 1200,    "inputKeys": [    "sourceRequestId",    "qcElementType"    ],    "outputKeys": [    "state",    "skipped",    "result"    ],    "timeoutPolicy": "TIME_OUT_WF",    "retryLogic": "FIXED",    "retryDelaySeconds": 600,    "responseTimeoutSeconds": 3600    }  </pre>    <p>输入/输出</p>    <p>任务的输入是一个map,可能是工作流初始化的一部分,或者其他任务的输出。这样的配置允许在工作流里路由输入/输出,或者允许其他任务作为输入,这样该任务可以在之上执行操作。比如,编码任务的输出可以提供给发布任务作为部署到CDN的输入。</p>    <p>定义任务输入的JSON片段:</p>    <pre>  {      "name": "name_of_task",      "taskReferenceName": "ref_name_unique_within_blueprint",      "inputParameters": {        "movieId": "${workflow.input.movieId}",        "url": "${workflow.input.fileLocation}"      },      "type": "SIMPLE"    }  </pre>    <p>一个例子</p>    <p>让我们一起看看这个非常简单的编码以及部署的工作流:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/7e1464a2c3f4d964f2bb404f4ba32d0f.png"></p>    <p>这里总共涉及3个worker任务和一个控制任务(Errors):</p>    <ol>     <li>内容检查:检查输入路径的文件是否正确/完整</li>     <li>编码:生成一个视频的编码</li>     <li>发布:发布到CDN上</li>    </ol>    <p>这3个任务是由不同的worker实现的,使用任务API然后放到待定任务池里。这些是理想情况下幂等的任务,对任务的输入做操作,执行工作,并且更新状态。</p>    <p>UI</p>    <p>UI是监控以及故障排除工作流执行情况的基本机制。UI提供对流程内部的洞察能力,允许基于不同的参数,包括输入/输出的搜索,并且提供了blueprint的视觉展现,以及它已经执行的路径,来帮助大家更好地理解流程的执行情况。对于每个工作流实例来说,UI提供了每个任务执行的细节信息,包括如下细节:</p>    <ul>     <li>任务被调度,被worker执行以及最终完成的时间戳。</li>     <li>如果任务失败了,失败的原因</li>     <li>重试次数</li>     <li>执行任务的主机</li>     <li>提供给任务的输入,以及任务完成后的输出。<br> 这里是生成性能数字的kitchen sink工作流的UI片段:<br> <img src="https://simg.open-open.com/show/b27f4401517c36cdf38b027dbb5f4fdd.png"></li>    </ul>    <h3>考虑过的其他方案</h3>    <p>Amazon SWF</p>    <p>我们使用AWS的简单工作流做了早期版本。但是,最终选择构建Conductor,因为SWF有一些限制:</p>    <ul>     <li>需要基于blueprint的编排,而SWF要求编程式的决策器</li>     <li>视图化工作流的UI</li>     <li>在需要时,能够提供更多API自带的同步特性(而不仅仅是基于消息的)</li>     <li>需要索引工作流和任务的输入和输出,以及能够基于此搜索工作流</li>     <li>需要维护单独的数据存储来保存工作流事件,从而能够从故障中恢复,搜索等等。</li>    </ul>    <p>Amazon Step Function</p>    <p>最近发布的Amazon Step Function在编排引擎里添加了一些我们需要的特性。Conductor可能可以引入 states语言 来定义工作流。</p>    <h3>一些统计信息</h3>    <p>这里是我们已经运行了大概一年的生产实例的一些统计信息。大多数这些工作流都是被内容平台使用的,用来支持内容获取,吸收和编码的各种工作流。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/85ab8ca27e88779e89938382f8b469b7.jpg"></p>    <h3>未来的考虑</h3>    <ul>     <li>支持AWS Lambda(或者类似)功能作为任务,用来做无服务器的简单任务。</li>     <li>和容器编排框架紧密集成,这样可以允许worker实例自动扩展</li>     <li>为每个任务记录执行数据的日志。我们认为这是有助于故障诊断的很有用的功能。</li>     <li>从UI上能够创建并且管理工作流blueprint</li>     <li>支持 states语言 。</li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://dockone.io/article/1930</p>    <p> </p>