如何在开源名目Cadence中成功轮询

本指南适用于一切宿愿了解Cadence中轮询上班原理的开发人员和工程师。Cadence是相对较新(且齐全开源)的容错形态代码平台,最后由Uber开发(如今失掉了包括Instaclustr在内的更多公司的支持)。

少量用例遍布单个恳求-回复、复杂的形态追踪和异步事情照应,并与外部的无法靠依赖项启动通讯。构建此类运行程序的罕用方法是将有形态服务、数据库、定时义务和队列系统像大杂烩一样整合在一同。

但是,这会对开发人员发生负面影响,由于大部分代码都是用于管道的——这覆盖了少量底层细节面前的实践业务逻辑。Cadence是一个齐全开源的编排框架,可以协助开发人员编写高容错且能够长时期运转的运行程序,这通常也被称为上班流。

从实质上讲,它提供了一个与特定进程有关联的虚拟内存,并保管了完整的运行程序形态,包括函数堆栈以及兼容各种主机和软件缺点的部分变量。这使得开发人员在编写代码时能够充沛应用编程言语的性能个性,Cadence则担任运行程序的耐久性、可用性和可裁减性。由于忙碌期待通常会消耗少量非必要的CPU周期,因此应尽或许防止经常使用轮询,而是经常使用由事情触发的终止来启动成功,除非以下两者状况:

关于计算机而言,这相当于在短途游览中每5分钟征询一次性距离目标地还有多远。虽然如此,在很多状况下,这是惟一可用的选用。Cadence为耐久计时器、长时期运转的优惠和有限度重试提供弱小的支持,使得其十分适宜此类性能的成功。

成功轮询机制有很多种方法。本文关键讲成功对外部服务的轮询,并剖析这样做会从Cadence中取得怎样的收益。首先,咱们来繁难的解释一下Cadence的概念。Cadence的外围思念是一个无端障形态的上班流。这象征着上班流代码的形态,包括部分变量和它创立的任何线程,不受进程和Cadence服务缺点的影响。这是一个十分弱小的理念,由于它封装了形态、线程处置、耐久计时器和事情处置程序。为了满足确定性的执行要求,上班流不准许间接调用任何外部API。雷同,它们担任对优惠的执前启动调度。优惠是用来成功业务级性能的运行程序逻辑,例如调用服务或对媒体文件启动转码。当优惠出现缺点时,Cadence并不会复原其运转形态。因此,优惠函数可以蕴含任何代码,且不会遭就任何限度。

代码自身十分繁难——咱们将逐行解释代码的作用:

State polledState = externalServiceActivities.getState();while(!expectedState.equals(polledState)) {Workflow.sleep(Duration.ofSeconds(30));polledState = externalServiceActivities.getState();}

左右滑动检查完整代码

咱们首先调用一个优惠,在这种状况下,外部服务或许是REST API。而后咱们就须要启动条件判别。假设未到达所需的形态,会有10秒的期待。

这不是通常意义上的期待,而是一个耐久的计时器。在这种状况下,轮询会执行周期性的期待,但时期或许会更长;而且,假设执行失败,咱们必定不会宿愿糜费整个时期周期。Cadence经过将计时器以事情的方式启动耐久化,并在成功后通知相应的上班服务(即治理上班流和优惠实施的服务)来处置此疑问。

这些计时器可以对从几秒到几分钟、几小时、几天甚至几个月或几年的时时期隔启动治理。最后,经过再次调用外部服务来刷新形态。在继续启动操作之前,咱们先极速了解一下Cadence终究在后盾做了哪些上班来防止潜在的疑问。

Cadence历史记载和轮询留意事项

Cadence是如何成功无端障形态上班流的呢?关键在于Cadence是如何保持用上班流程执行成功的。上班流形态复原应用事情溯源,而事情溯源对代码的编写方式施加了一些限度。事情溯源将一系列不时变动的事情转为耐久化的形态。

每当上班流形态出现变动时,都会有一个新的事情追加到该上班流的事情历史记载中。而后,Cadence经过历史记载来启动操作重放,以从新树立上班流的形态。这就是为什么与外部环境的一切通讯都应该经过优惠启动,并且必定经常使用Cadence API来失掉时期、期待和创立新线程。

1、审慎经常使用轮询​

轮询须要依据判别条件不时地循环。由于每个优惠调用和计时器事情都是耐久的,因此即使是短的轮询距离也或许调演化成无法接受的时期消耗。如今咱们来钻研轮询片段的历史记载会以怎样的方式出现。

Cadence中轮询代码片段的事情历史记载

假设上班流在两边某个中央失败,且必定重放其历史操作记载,这或许会造成少量的事情清单被执行。有一些方法可以防止这些操作脱离掌控:防止经常使用较短的轮询周期,在上班流中设置正当的超时时期,限度轮询的次数。

记住一切操作都是耐久的,或许须要由人重放操作。

2、性能优惠重试次数​

假设外部服务由于某些要素失败了怎样办?咱们须要尝试,尝试,再尝试!Cadence存在一种机制,可以让Cadence记载优惠结果并能够完美地恢停上班流形态,同时还提供了对相似重试逻辑等额外性能的支持。上方是启用重试选项的优惠性能示例:

private final ExternalServiceActivities externalServiceActivities =Workflow.newActivityStub(ExternalServiceActivities.class,new ActivityOptions.Builder().setRetryOptions(new RetryOptions.Builder().setInitialInterval(Duration.ofSeconds(10)).setMaximumAttempts(3).build()).setScheduleToCloseTimeout(Duration.ofMinutes(5)).build());

​左右滑动检查完整代码

经过这样的操作,咱们通知Cadence,在ExternalServiceActivities中的操作最多可以重试3次,且每次重试的距离为10秒。这样,每个对外部服务优惠的调用都可以轻松的成功重试性能,且无需编写任何重试逻辑。

为了展现这种形式的实践成果,咱们将在示例名目中集成一个虚拟的轮询。

1、Instafood简介​

Instafood是一个基于在线运行的送餐服务。客户可以经过Instafood的移动运行从他们外地最青睐的餐厅中订购食物。订单可以是自取或外卖。

假设选用外卖,Instafood将通知其外卖司机从餐厅取餐并将其送到客户手中。Instafood为每个餐厅提供一个展现屏或平板电脑,用于Instafood和餐厅之间的通讯。客户下单后,Instafood就会通知餐厅,而后餐厅可以接受订单、提供估量成功时期或将其标志为已成功等。关于外送订单,Instafood将依据估量成功时期协调外卖司机取餐。

2、轮询"MegaBurgers"​

MegaBurgers是一家大型跨国快餐汉堡连锁店。他们有自己的移动运行程序和网站,并经常使用REST API作为后端为客户提供订单服务。Instafood和MegaBurgers已达成协定,Instafood客户可以经过Instafood的运行程序在MegaBurger下单,并可选用自取和外卖。与通用打算不同的是,MegaBurger并未选用在一切店面装置Instafood展现屏,而是赞同将Instafood的订餐系统以集成的方式与自身基于REST的订餐系统启动对接,以成功下单和接纳降级。

MegaBurger的REST API没有推送机制(WebSockets、WebHooks等),无法接纳订单形态降级。

雷同,其须要活期发送GET恳求来确定订单形态,这些轮询或许会造成订单上班流在Instafood端重复执行(例如布置外卖司机取餐)。

你须要性能一个Cadence集群来运转示例名目。在此示例中,咱们将经常使用Instaclustr平台来执行操作。

第1步:创立Instaclustr托管集群​

Cadence集群须要衔接Apache Cassandra集群作为耐久层。为了成功性能Cadence和Cassandra集群,咱们将遵照“创立Cadence集群”文档的操作指点。

第2步:性能Cadence域​

Cadence的后端由多租户服务提供支持,其中隔离单元被称为域。为了让Instafood运行程序运转,咱们首先须要为它注册一个域。

1、为了与Cadence集群启动交互,咱们须要装置其命令行界面客户端。

假设经常使用macOS,可以经过Homebrew装置Cadence CLI,如下所示:

brew install cadence-workflow# run command line clientcadence <command> <arguments>

其余操作系统​

可以经过Docker Hub镜像仓库ubercadence/cli来运转和经常使用CLI:

# run command line clientdocker run --network=host --rm ubercadence/cli:master <command><arguments>

左右滑动检查完整代码

在的步骤中,咱们将经常使用cadence来指代客户端。

2、为了衔接的稳固性,倡导经过负载平衡器地址来衔接和访问集群。可以在“衔接消息”选项卡的顶部找到负载平衡器地址,如下所示:

“ab-cd12ef23-45gh-4baf-ad99-df4xy-azba45bc0c8da111.elb.us-east 1.amazonaws.com”

左右滑动检查完整代码

3、如今可以经过列出域来测试衔接:

cadence --ad <cadence_host>:7933 admin domain list
cadence --ad <cadence_host>:7933 --do instafood domain register --global_domain=false

左右滑动检查完整代码

cadence --ad <cadence_host>:7933 --do instafood domain describe

第3步:运转Instafood示例名目​

1、从Instafood名目标Git代码仓库中克隆Gradle名目。

2、关上位于instafood/src/main/resources/instafood.properties门路的性能文件,将cadenceHost的值交流为自己的负载平衡器地址:

cadenceHost=<cadence_host>

​3、经过以下方式运转该运行程序:

cadence-cookbooks-instafood/instafood$ ./gradlew run

​4、检查终端输入以确认其能否曾经反常运转:

在了解Instafood如何与MegaBurger集成之前,让咱们先极速了解一下他们的API。

1、运转MegaBurger服务​

让咱们从运转服务开局。经过以下命令来启动服务:

cadence-cookbooks-instafood/megaburger$ ./gradlew run

或许在IDE中运转MegaburgerRestApplication。这是一个以内存作为耐久层的Spring Boot Rest API演示示例。当运行程序封锁时,一切数据都会失落。

2、MegaBurger的订单API​

MegaBurger颁布其Orders API以便跟踪和降级每个食品订单的形态。

POST /orders

创立一个订单并前往其ID。

curl -X POST localhost:8080/orders -H “Content-Type: application/json” --data ‘{“meal”: “Vegan Burger”, “quantity”: 1}’

左右滑动检查完整代码

{“id”: 1,“meal”: “Vegan Burger”,“quantity”: 1,“status”: “PENDING”,“eta_minutes”: null}

GET /orders​

前往一个蕴含一切订单消息的列表。

curl -X GET localhost:8080/orders
[{“id”: 0,“meal”: “Vegan Burger”,“quantity”: 1,“status”: “PENDING”,“eta_minutes”: null},{“id”: 1,“meal”: “Onion Rings”,“quantity”: 2,“status”: “PENDING”,“eta_minutes”: null}]

GET /orders / {orderId}

curl -X GET localhost:8080/orders/1
{“id”: 1,“meal”: “Onion Rings”,“quantity”: 2,“status”: “PENDING”,“eta_minutes”: null}

PATCH /orders/{orderId}

curl -X PATCH localhost:8080/orders/1 -H “Content-Type: application/ json” --data ‘{“status”:“ACCEPTED”}’

左右滑动检查完整代码

{“id”: 1,“meal”: “Onion Rings”,“quantity”: 2,“status”: “ACCEPTED”,“eta_minutes”: null}

如今曾经成功了一切性能的初始化,让咱们看看Instafood和MegaBurger之间集成的实践成果如何。

1、轮询上班流​

首先定义新的上班流MegaBurgerOrderWorkflow:

public interface MegaBurgerOrderWorkflow {@WorkflowMethodvoid orderFood(FoodOrder order);// ...}

此上班流有一个orderFood方法,该方法将经过与MegaBurger集成来发送和跟踪相应的FoodOrder。

如今来看看它的成功方式:

public class MegaBurgerOrderWorkflowImpl implements MegaBurgerOrderWork flow {// ...@Overridepublic void orderFood(FoodOrder order) {OrderWorkflow parentOrderWorkflow = getParentOrderWorkflow();Integer orderId = megaBurgerOrderActivities.createOrder(mapMegaBurgerFoodOrder(order)); updateOrderStatus(parentOrderWorkflow, OrderStatus.PENDING);// Poll until Order is accepted/rejectedupdateOrderStatus(parentOrderWorkflow,pollOrderStatusTransition(orderId, OrderStatus.PENDING));if (OrderStatus.REJECTED.equals(currentStatus)) { throw new RuntimeException(“Order with id “ + orderId + “was rejected”);}// Send ETA to parent workflowparentOrderWorkflow.updateEta(getOrderEta(orderId));// Poll until Order is cooking updateOrderStatus(parentOrderWorkflow,pollOrderStatusTransition(orderId, OrderStatus.ACCEPTED)); //Poll until Order is readyupdateOrderStatus(parentOrderWorkflow,pollOrderStatusTransition(orderId, OrderStatus.COOKING)); //Poll until Order is deliveredupdateOrderStatus(parentOrderWorkflow,pollOrderStatusTransition(orderId, OrderStatus.READY)); }// ...}

左右滑动检查完整代码

该上班流首先失掉其父上班流。MegaBurgerOrderWorkflow只处置与MegaBurger的集成,将订单交付给由独立上班流治理的客户端处置;这象征着咱们经常使用的是子上班流。而后,经过优惠来创立订单,并取得订单ID。

优惠只是API客户端的装璜器,该API客户端担任发送POST恳求到/orders。创立订单后,父上班流会收到一个订单如今处于PENDING形态的信号(这是一个发送给上班流的,来自外部的异步恳求)。

如今咱们肯活期待订单从PENDING转变为ACCEPTED或REJECTED。这就是轮询施展作用的中央。如今看看咱们的函数pollOrderStatusTransition做了什么:

private OrderStatus pollOrderStatusTransition(Integer orderId,OrderStatus orderStatus) { OrderStatus polledStatus =megaBurgerOrderActivities.getOrderById(orderId).getStatus();while (orderStatus.equals(polledStatus)) {Workflow.sleep(Duration.ofSeconds(30)); polledStatus = megaBurgerOrderActivities. getOrderById(orderId).getStatus();}return polledStatus;}

左右滑动检查完整代码

这与本文引见的其余轮询循环十分相似。惟一的区别是它用一个轮询的特定形态替代期待,直到订单形态出现变动。雷同的,用于经过ID失掉订单的实在API调用暗藏在优惠的前面,该优惠启用了重试性能。假设订单被拒绝,则会引发运转形态异常,使上班散失败。假设订单被接受,则将MegaBurger的估量成功时期前往给父上班流(父上班流经常使用估量成功时期来成功交付调度)。最后,图3中所示的形态将会被转换,直到订单被标志为已交付。

2、运转反常的场景​

最后,让成功一个完整的订单场景。

这个场景是示例名目中测试套件的一部分。惟一的要求是同时运转Instafood和MegaBurger主机,而后依照前文中的步骤操作。

测试用例形容了客户端经过Instafood下单MegaBurger的新素食汉堡,并且来店面取餐:

cadence-cookbooks-instafood/instafood$ ./gradlew test
class InstafoodApplicationTest {// ...@Testpublic voidgivenAnOrderItShouldBeSentToMegaBurgerAndBeDeliveredAccordingly() {FoodOrder order = new FoodOrder(Restaurant.MEGABURGER,“Vegan Burger”, 2, “+54 11 2343-2324”, “Díaz velez 433, Lalucila”, true);// Client orders foodWorkflowExecution workflowExecution= WorkflowClientstart(orderWorkflow::orderFood, order);// Wait until order is pending Megaburger’s acceptance await().until(() -> OrderStatus.PENDING.equals(orderWorkflow.getStatus()));// Megaburger accepts order and sends ETAmegaBurgerOrdersApiClient.updateStatusAndEta(getLastOrderId(),“ACCEPTED”, 15);await().until(() -> OrderStatus.ACCEPTED.equals(orderWorkflow.getStatus()));// Megaburger starts cooking ordermegaBurgerOrdersApiClient.updateStatus(getLastOrderId(),“COOKING”);await().until(() -> OrderStatus.COOKING.equals(orderWorkflow.getStatus()));// Megaburger signals order is readymegaBurgerOrdersApiClient.updateStatus(getLastOrderId(),“READY”);await().until(() -> OrderStatus.READY.equals(orderWorkflow.getStatus()));// Megaburger signals order has been picked-upmegaBurgerOrdersApiClient.updateStatus(getLastOrderId(),“RESTAURANT_DELIVERED”);await().until(() -> OrderStatus.RESTAURANT_DELIVERED.equals(orderWorkflow.getStatus()));await().until(() -> workflowHistoryHasEvent(workflowClient,workflowExecution, EventType.WorkflowExecutionCompleted)):} }

左右滑动检查完整代码

在这个场景中,有3个介入者:Instafood、MegaBurger和客户端。

2. 一旦订单抵达MegaBurger(订单形态为PENDING),MegaBurgers将其标志为ACCEPTED并前往估量成功时期。

3. 而后咱们看下整个形态降级序列:

4. 由于该订单是以取餐的方式交付,因此一旦客户端成功交付,整个上班流程就完结了。

在本文中,咱们学习了如何经常使用Cadence成功轮询。咱们展现了如何让Cadence集群在Instaclustr平台上运转,以及让运行程序衔接到它是如许容易。

译者引见

仇凯,社区编辑,目前就任于北京宅急送快运股份有限公司,职位为消息安保工程师。关键担任公司消息安保规划和树立(等保,ISO27001),日常关键上班内容为安保打算制订和落地、外部安保审计微危险评价以及治理。

您可能还会对下面的文章感兴趣: