azure多功能成像好用吗_了解Azure持久功能

azure多功能成像好用吗

Stateful Workflows on top of Stateless Serverless Cloud Functions—this is the essence of the Azure Durable Functions library. That's a lot of fancy words in one sentence, and they might be hard for the majority of readers to understand.

无状态无服务器云功能之上的有状态工作流-这是Azure耐用功能库的本质。 一句话中有很多花哨的单词,大多数读者可能很难理解。

Please join me on the journey where I'll try to explain how those buzzwords fit together. I will do this in 3 steps:

请加入我的旅程,我将尝试解释这些流行词如何组合在一起。 我将分3个步骤进行操作:

  • Describe the context of modern cloud applications relying on serverless architecture;

    描述依赖于无服务器架构的现代云应用程序的上下文;
  • Identify the limitations of basic approaches to composing applications out of the simple building blocks;

    从简单的构建块中确定组成应用程序的基本方法的局限性;
  • Explain the solutions that Durable Functions offer for those problems.

    解释耐用功能为这些问题提供的解决方案。

微服务 (Microservices)

Traditionally, server-side applications were built in a style which is now referred to as Monolith. If multiple people and teams were developing parts of the same application, they mostly contributed to the same code base. If the code base were structured well, it would have some distinct modules or components, and a single team would typically own each module:

传统上,服务器端应用程序是以现在称为Monolith的样式构建的。 如果多个人员和团队正在开发同一应用程序的各个部分,则他们大多会贡献相同的代码库。 如果代码库结构合理,它将具有一些不同的模块或组件,并且一个团队通常将拥有每个模块:

Monolith
Multiple components of a monolithic application
整体应用程序的多个组件

Usually, the modules would be packaged together at build time and then deployed as a single unit, so a lot of communication between modules would stay inside the OS process.

通常,模块将在构建时打包在一起,然后作为单个单元部署,因此模块之间的大量通信将保留在OS进程内部。

Although the modules could stay loosely coupled over time, the coupling almost always occurred on the level of the data store because all teams would use a single centralized database.

尽管随着时间的推移,模​​块可能会保持松散耦合,但是耦合几乎总是在数据存储级别上发生,因为所有团队都将使用单个集中式数据库。

This model works great for small- to medium-size applications, but it turns out that teams start getting in each other's way as the application grows since synchronization of contributions takes more and more effort.

该模型非常适合中小型应用程序,但是事实证明,随着应用程序的增长,团队之间开始互相影响,因为贡献的同步需要越来越多的精力。

As a complex but viable alternative, the industry came up with a revised service-oriented approach commonly called Microservices. The teams split the big application into "vertical slices" structured around the distinct business capabilities:

作为一个复杂的,但可行的替代方案,业界传出了通常被称为微服务修订面向服务的方法。 团队将大型应用程序划分为围绕不同业务功能构建的“垂直切片”:

Microservices
Multiple components of a microservice-based application
基于微服务的应用程序的多个组件

Each team then owns a whole vertical—from public communication contracts, or even UIs, down to the data storage. Explicitly shared databases are strongly discouraged. Services talk to each other via documented and versioned public contracts.

然后,每个团队都拥有一个完整的部门-从公共通信合同甚至UI到数据存储。 强烈建议不要显式共享数据库。 服务通过文档化和版本化的公共合同相互交谈。

If the borders for the split were selected well—and that's the most tricky part—the contracts stay stable over time, and thin enough to avoid too much chattiness. This gives each team enough autonomy to innovate at their best pace and to make independent technical decisions.

如果选择好分割的边界(这是最棘手的部分),则合同会随着时间的推移保持稳定,并且足够薄,可以避免过多的聊天。 这赋予了每个团队足够的自主权,可以按自己的最佳速度进行创新并做出独立的技术决策。

One of the drawbacks of microservices is the change in deployment model. The services are now deployed to separate servers connected via a network:

微服务的缺点之一是部署模型的改变。 现在,这些服务已部署到通过网络连接的单独服务器上:

Distributed Systems
Challenges of communication between distributed components
分布式组件之间通信的挑战

Networks are fundamentally unreliable: they work just fine most of the time, but when they fail, they fail in all kinds of unpredictable and least desirable manners. There are books written on the topic of distributed systems architecture. TL;DR: it's hard.

网络从根本上讲是不可靠的:它们在大多数情况下都可以正常工作,但是当它们出现故障时,它们会以各种不可预测的方式和最不希望的方式失败。 有关于分布式系统体系结构主题的书籍。 TL; DR:很难。

A lot of the new adopters of microservices tend to ignore such complications. REST over HTTP(S) is the dominant style of connecting microservices. Like any other synchronous communication protocol, it makes the system brittle.

微服务的许多新采用者倾向于忽略这种复杂性。 HTTP(S)上的REST是连接微服务的主要样式。 像任何其他同步通信协议一样,它使系统易碎。

Consider what happens when one service becomes temporary unhealthy: maybe its database goes offline, or it's struggling to keep up with the request load, or a new version of the service is being deployed. All the requests to the problematic service start failing—or worse—become very slow. The dependent service waits for the response, and thus blocks all incoming requests of its own. The error propagates upstream very quickly causing cascading failures all over the place:

考虑当一项服务暂时不健康时会发生什么:它的数据库可能离线,或者它正努力跟上请求负载,或者正在部署新版本的服务。 对有问题的服务的所有请求开始失败(或更糟的是)变得非常缓慢。 从属服务等待响应,从而阻止其自身的所有传入请求。 该错误非常Swift地传播到上游,从而在整个地方引起级联故障:

Cascading Failures
Error in one component causes cascading failures
一个组件中的错误导致级联故障

The application is down. Everybody screams and starts the blame war.

该应用程序已关闭。 每个人都在尖叫并开始责备战争。

事件驱动的应用 (Event-Driven Applications)

While cascading failures of HTTP communication can be mitigated with patterns like a circuit breaker and graceful degradation, a better solution is to switch to the asynchronous style of communication as the default. Some kind of persistent queueing service is used as an intermediary.

虽然可以通过断路器和正常降级等模式来缓解HTTP通信的级联失败,但更好的解决方案是将通信切换为默认的异步方式。 某种持久性排队服务用作中介。

The style of application architecture which is based on sending events between services is known as Event-Driven. When a service does something useful, it publishes an event—a record about the fact which happened to its business domain. Another service listens to the published events and executes its own duty in response to those facts:

基于服务之间发送事件的应用程序体系结构的样式称为事件驱动 。 当服务执行有用的操作时,它会发布一个事件-有关其业务领域发生的事实的记录。 另一个服务侦听已发布的事件,并根据这些事实执行自己的职责:

Event-Driven Application
Communication in event-driven applications
事件驱动应用程序中的通信

The service that produces events might not know about the consumers. New event subscribers can be introduced over time. This works better in theory than in practice, but the services tend to get coupled less.

产生事件的服务可能不了解消费者。 随着时间的推移,可以引入新的事件订阅者。 从理论上讲,这比在实践中效果更好,但是服务之间的耦合往往较少。

More importantly, if one service is down, other services don't catch fire immediately. The upstream services keep publishing the events, which build up in the queue but can be stored safely for hours or days. The downstream services might not be doing anything useful for this particular flow, but it can stay healthy otherwise.

更重要的是,如果一项服务宕机,其他服务不会立即着火。 上游服务会继续发布事件,这些事件会在队列中累积,但可以安全存储数小时或数天。 下游服务可能对此特定流没有做任何有用的事情,但否则可以保持健康。

However, another potential issue comes hand-in-hand with loose coupling: low cohesion. As Martin Fowler notices in his essay What do you mean by "Event-Driven":

但是,另一个潜在的问题是与松散的耦合并存:低内聚性。 正如马丁·福勒(Martin Fowler)在其论文中所注意到的, “事件驱动”是什么意思

It's very easy to make nicely decoupled systems with event notification, without realizing that you're losing sight of the larger-scale flow.

使用事件通知来使分离的系统很好地耦合非常容易,而无需意识到您已经看不到更大的流程了。

Given many components that publish and subscribe to a large number of event types, it's easy to stop seeing the forest for the trees. Combinations of events usually constitute gradual workflows executed in time. A workflow is more than the sum of its parts, and understanding of the high-level flow is paramount to controlling the system behavior.

鉴于有许多发布和订阅大量事件类型的组件,因此很容易停止为这些树看到森林。 事件的组合通常构成及时执行的渐进工作流。 工作流不仅仅是其各个部分的总和,而且对高级流程的理解对于控制系统行为至关重要。

Hold this thought for a minute; we'll get back to it later. Now it's time to talk cloud.

保持这种想法一分钟; 我们待会儿再讲。 现在该谈论云了

(Cloud)

The birth of public cloud changed the way we architect applications. It made many things much more straightforward: provisioning of new resources in minutes instead of months, scaling elastically based on demand, and resiliency and disaster recovery at the global scale.

公共云的诞生改变了我们构建应用程序的方式。 它使许多事情变得更加简单:在数分钟内而不是数月内配置新资源,根据需求进行弹性扩展,并在全球范围内恢复和灾难恢复。

It made other things more complicated. Here is the picture of the global Azure network:

这使其他事情变得更加复杂。 这是全球Azure网络的图片:

Azure Network
Azure locations with network connections
具有网络连接的Azure位置

There are good reasons to deploy applications to more than one geographical location: among others, to reduce network latency by staying close to the customer, and to achieve resilience through geographical redundancy. Public Cloud is the ultimate distributed system. As you remember, distributed systems are hard.

有充分的理由将应用程序部署到一个以上的地理位置:除其他外,它通过与客户保持亲近关系来减少网络延迟,并通过地理冗余来实现弹性。 公共云是最终的分布式系统。 您还记得,分布式系统很难。

There's more to that. Each cloud provider has dozens and dozens of managed services, which is the curse and the blessing. Specialized services are great to provide off-the-shelf solutions to common complex problems. On the flip side, each service has distinct properties regarding consistency, resiliency and fault tolerance.

还有更多。 每个云提供商都拥有数十种托管服务,这是诅咒和祝福。 专业服务非常适合为常见的复杂问题提供现成的解决方案。 另一方面,每种服务在一致性,弹性和容错性方面都具有独特的属性。

In my opinion, at this point developers have to embrace the public cloud and apply the distributed system design on top of it. If you agree, there is an excellent way to approach it.

我认为,在这一点上,开发人员必须采用公共云,并在其之上应用分布式系统设计。 如果您同意,有一种极好的方法来解决它。

无服务器 (Serverless)

The slightly provocative term serverless is used to describe cloud services that do not require provisioning of VMs, instances, workers, or any other fixed capacity to run custom applications on top of them. Resources are allocated dynamically and transparently, and the cost is based on their actual consumption, rather than on pre-purchased capacity.

略带挑衅性的术语“ 无服务器”用于描述不需要提供VM,实例,工作器或任何其他固定容量来在其之上运行自定义应用程序的云服务。 资源是动态,透明地分配的,成本是根据其实际消耗量而不是预先购买的容量确定的。

Serverless is more about operational and economical properties of the system than about the technology per se. Servers do exist, but they are someone else's concern. You don't manage the uptime of serverless applications: the cloud provider does.

无服务器更多的是关于系统的操作和经济特性,而不是技术本身。 服务器确实存在,但是它们是其他人关心的问题。 您无需管理无服务器应用程序的正常运行时间:云提供商可以管理。

On top of that, you pay for what you use, similar to the consumption of other commodity resources like electricity. Instead of buying a generator to power up your house, you just purchase energy from the power company. You lose some control (e.g., no way to select the voltage), but this is fine in most cases. The great benefit is no need to buy and maintain the hardware.

最重要的是,您需要为所用的东西付费,这与电力等其他商品资源的消耗类似。 无需购买发电机来为您的房屋供电,您只需从电力公司购买能源即可。 您失去了一些控制权(例如,无法选择电压),但这在大多数情况下都可以。 最大的好处是无需购买和维护硬件。

Serverless compute does the same: it supplies standard services on a pay-per-use basis.

无服务器计算的功能相同:它按使用付费提供标准服务。

If we talk more specifically about Function-as-a-Service offerings like Azure Functions, they provide a standard model to run small pieces of code in the cloud. You zip up the code or binaries and send it to Azure; Microsoft takes care of all the hardware and software required to run it. The infrastructure automatically scales up or down based on demand, and you pay per request, CPU time and memory that the application consumed. No usage—no bill.

如果我们更具体地谈论诸如Azure Functions之类的功能即服务产品,则它们提供了一种标准模型,可在云中运行少量代码。 您压缩代码或二进制文件并将其发送到Azure; Microsoft负责运行它所需的所有硬件和软件。 基础架构会根据需求自动扩展或缩减,您可以按请求,应用程序占用的CPU时间和内存付费。 没有用法-没有账单。

However, there's always a "but". FaaS services come with an opinionated development model that applications have to follow:

但是,总会有一个“ but”。 FaaS服务附带一个必须遵循的开发模型,应用程序必须遵循:

  • Event-Driven: for each serverless function you have to define a specific trigger—the event type which causes it to run, be it an HTTP endpoint or a queue message;

    Event-Driven :对于每个无服务器功能,您都必须定义一个特定的触发器-导致其运行的事件类型,无论是HTTP端点还是队列消息;

  • Short-Lived: functions can only run up to several minutes, and preferably for a few seconds or less;

    寿命短 :功能最多只能运行几分钟,最好持续几秒钟或更短的时间;

  • Stateless: as you don't control where and when function instances are provisioned or deprovisioned, there is no way to store data within the process between requests reliably; external storage has to be utilized.

    无状态的 :由于您无法控制在何处以及何时提供或取消提供功能实例,因此无法可靠地在请求之间的流程中存储数据; 必须使用外部存储。

Frankly speaking, the majority of existing applications don't really fit into this model. If you are lucky to work on a new application (or a new module of it), you are in better shape.

坦白地说,大多数现有应用程序并不真正适合该模型。 如果您幸运地使用新应用程序(或其新模块),那么您的状况会更好。

A lot of the serverless applications may be designed to look somewhat similar to this example from the Serverless360 blog:

许多无服务器应用程序的外观都可能类似于Serverless360博客中的示例:

Serviceful Serverless Application
Sample application utilizing "serviceful" serverless architecture
利用“服务性”无服务器架构的示例应用程序

There are 9 managed Azure services working together in this app. Most of them have a unique purpose, but the services are all glued together with Azure Functions. An image is uploaded to Blob Storage, an Azure Function calls Vision API to recognize the license plate and send the result to Event Grid, another Azure Function puts that event to Cosmos DB, and so on.

此应用程序中有9个托管Azure服务一起工作。 它们中的大多数具有独特的目的,但是所有服务都与Azure Functions粘合在一起。 图像上载到Blob存储,Azure函数调用Vision API识别车牌并将结果发送到事件网格,另一个Azure函数将该事件放入Cosmos DB,依此类推。

This style of cloud applications is sometimes referred to as Serviceful to emphasize the heavy usage of managed services "glued" together by serverless functions.

云应用这种风格有时也被称为Serviceful强调的电信管理服务领域大量使用“粘”在一起的无服务器功能。

Creating a comparable application without any managed services would be a much harder task, even more so, if the application has to run at scale. Moreover, there's no way to keep the pay-as-you-go pricing model in the self-service world.

如果应用程序必须大规模运行,那么创建没有任何托管服务的可比较应用程序将是一项艰巨的任务,甚至更加艰巨。 而且,没有办法在自助服务世界中保留“按需付费”定价模型。

The application pictured above is still pretty straightforward. The processes in enterprise applications are often much more sophisticated.

上图所示的应用程序仍然非常简单。 企业应用程序中的流程通常要复杂得多。

Remember the quote from Martin Fowler about losing sight of the large-scale flow. That was true for microservices, but it's even more true for the "nanoservices" of cloud functions.

请记住,马丁·福勒(Martin Fowler)所说的关于忽略大规模水流的说法。 对于微服务而言确实如此,但对于云功能的“纳米服务”而言更是如此。

I want to dive deeper and give you several examples of related problems.

我想更深入地介绍一些相关问题的例子。

无服务器组合的挑战 (Challenges of Serverless Composition)

For the rest of the article, I'll define an imaginary business application for booking trips to software conferences. In order to go to a conference, I need to buy tickets to the conference itself, purchase the flights, and book a room at a hotel.

在本文的其余部分,我将定义一个假想的业务应用程序来预订软件会议的旅行。 为了参加会议,我需要购买会议本身的门票,购买机票并在酒店预订房间。

In this scenario, it makes sense to create three Azure Functions, each one responsible for one step of the booking process. As we prefer message passing, each Function emits an event which the next function can listen for:

在这种情况下,创建三个Azure函数是有意义的,每个Azure函数负责预订过程的一个步骤。 由于我们更喜欢消息传递,因此每个Function都会发出一个事件,下一个Function可以监听:

Conference Booking Application
Conference booking application
会议预订申请

This approach works, however, problems do exist.

这种方法有效,但是确实存在问题。

灵活的排序 (Flexible Sequencing)

As we need to execute the whole booking process in sequence, the Azure Functions are wired one after another by configuring the output of one function to match with the event source of the downstream function.

由于我们需要按顺序执行整个预订过程,因此,通过将一个功能的输出配置为与下游功能的事件源匹配,可以将Azure功能彼此连接。

In the picture above, the functions' sequence is hard-defined. If we were to swap the order of booking the flights and reserving the hotel, that would require a code change—at least of the input/output wiring definitions, but probably also the functions' parameter types.

在上图中,函数的顺序是硬定义的。 如果我们要交换预订机票和预订酒店的顺序,那将需要更改代码-至少要更改输入/输出接线定义,而且可能还要更改函数的参数类型。

In this case, are the functions really decoupled?

在这种情况下,功能真的分离了吗?

错误处理 (Error Handling)

What happens if the Book Flight function becomes unhealthy, perhaps due to the outage of the third-party flight-booking service? Well, that's why we use asynchronous messaging:after the function execution fails, the message returns to the queue and is picked up again by another execution.

如果预订航班功能不正常(可能是由于第三方航班预订服务中断)会怎样? 嗯,这就是我们使用异步消息传递的原因:函数执行失败后,消息返回队列,并由另一个执行再次接收。

However, such retries happen almost immediately for most event sources. This might not be what we want: an exponential back-off policy could be a smarter idea. At this point, the retry logic becomes stateful: the next attempt should "know" the history of previous attempts to make a decision about retry timing.

但是,对于大多数事件源,此类重试几乎立即发生。 这可能不是我们想要的:指数补偿政策可能是一个更明智的主意。 此时,重试逻辑变为有状态 :下一次尝试应“了解”先前尝试的历史,以便做出有关重试时间的决定。

There are more advanced error-handling patterns too. If executions failures are not intermittent, we may decide to cancel the whole process and run compensating actions against the already completed steps.

还有更高级的错误处理模式。 如果执行失败不是间歇性的,我们可能决定取消整个过程,并针对已经完成的步骤执行补偿措施。

An example of this is a fallback action: if the flight is not possible (e.g., no routes for this origin-destination combination), the flow could choose to book a train instead:

一个后备动作就是一个后备动作:如果无法进行飞行(例如,此始发地-目的地组合没有路线),则流程可以选择预订火车:

Fallback On Error
Fallback after 3 consecutive failures
连续3次失败后的回退

This scenario is not trivial to implement with stateless functions. We could wait until a message goes to the dead-letter queue and then route it from there, but this is brittle and not expressive enough.

使用无状态功能来实现这种情况并非易事。 我们可以等到消息到达死信队列,然后再从那里路由它,但这是脆弱的,不够表现力。

平行动作 (Parallel Actions)

Sometimes the business process doesn't have to be sequential. In our reservation scenario, there might be no difference whether we book a flight before a hotel or vice versa. It could be desirable to run those actions in parallel.

有时,业务流程不必是顺序的。 在我们的预订方案中,我们预订酒店之前还是相反都可能没有区别。 可能希望并行执行这些操作。

Parallel execution of actions is easy with the pub-sub capabilities of an event bus: both functions should subscribe to the same event and act on it independently.

借助事件总线的pub-sub功能,可以并行执行动作:两个功能都应订阅相同的事件并对其独立执行操作。

The problem comes when we need to reconcile the outcomes of parallel actions, e.g., calculate the final price for expense reporting purposes:

问题出在我们需要协调并行操作的结果时,例如,出于费用报告目的计算最终价格:

Fan-out / Fan-in
Fan-out / fan-in pattern
扇出/扇入模式

There is no way to implement the Report Expenses block as a single Azure Function: functions can't be triggered by two events, let alone correlate two related events.

无法将Report Expenses块实现为单个Azure函数:函数不能由两个事件触发,更不用说关联两个相关事件了。

The solution would probably include two functions, one per event, and the shared storage between them to pass information about the first completed booking to the one who completes last. All this wiring has to be implemented in custom code. The complexity grows if more than two functions need to run in parallel.

该解决方案可能包括两个功能,每个事件一个,以及它们之间的共享存储,以将有关第一个完成的预订的信息传递给最后一个完成的预订。 所有这些接线必须以自定义代码实现。 如果需要并行运行两个以上的功能,则复杂度会增加。

Also, don't forget the edge cases. What if one of the function fails? How do you make sure there is no race condition when writing and reading to/from the shared storage?

另外,不要忘记边缘情况。 如果功能之一失败怎么办? 在共享存储中进行读写操作时,如何确保没有竞争条件?

失踪的编曲家 (Missing Orchestrator)

All these examples give us a hint that we need an additional tool to organize low-level single-purpose independent functions into high-level workflows.

所有这些示例都向我们暗示了我们需要一个额外的工具来将低级别的单用途独立功能组织到高级工作流程中。

Such a tool can be called an Orchestrator because its sole mission is to delegate work to stateless actions while maintaining the big picture and history of the flow.

可以将这种工具称为Orchestrator,因为它的唯一任务是将工作委托给无状态的动作,同时又要保持流程的总体情况和历史记录。

Azure Durable Functions aims to provide such a tool.

Azure耐用功能旨在提供这种工具。

引入Azure持久功能 (Introducing Azure Durable Functions)

Azure功能 (Azure Functions)

Azure Functions is the serverless compute service from Microsoft. Functions are event-driven: each function defines a trigger—the exact definition of the event source, for instance, the name of a storage queue.

Azure Functions是Microsoft的无服务器计算服务。 函数是事件驱动的:每个函数都定义一个触发器 ,即事件源的确切定义,例如,存储队列的名称。

Azure Functions can be programmed in several languages. A basic Function with a Storage Queue trigger implemented in C# would look like this:

可以使用多种语言对 Azure Functions进行编程。 使用C#实现的带有存储队列触发器的基本函数如下所示:

[FunctionName("MyFirstFunction")]
public static void QueueTrigger(
    [QueueTrigger("myqueue-items")] string myQueueItem, 
    ILogger log)
{
    log.LogInformation($"C# function processed: {myQueueItem}");
}

The FunctionName attribute exposes the C# static method as an Azure Function named MyFirstFunction. The QueueTrigger attribute defines the name of the storage queue to listen to. The function body logs the information about the incoming message.

FunctionName属性将C#静态方法公开为名为MyFirstFunction的Azure函数。 QueueTrigger属性定义要监听的存储队列的名称。 功能主体记录有关传入消息的信息。

耐用的功能 (Durable Functions)

Durable Functions is a library that brings workflow orchestration abstractions to Azure Functions. It introduces a number of idioms and tools to define stateful, potentially long-running operations, and manages a lot of mechanics of reliable communication and state management behind the scenes.

耐用函数是一个将工作流程流程抽象带到Azure函数的库。 它引入了许多惯用语和工具来定义有状态的,可能长期运行的操作,并在后台管理了许多可靠的通信和状态管理机制。

The library records the history of all actions in Azure Storage services, enabling durability and resilience to failures.

该库记录了Azure存储服务中所有操作的历史记录,从而实现了持久性和对故障的恢复能力。

Durable Functions is open source, Microsoft accepts external contributions, and the community is quite active.

耐用功能是开源的 ,Microsoft接受外部捐助,并且社区非常活跃。

Currently, you can write Durable Functions in 3 programming languages: C#, F#, and Javascript (Node.js). All my examples are going to be in C#. For Javascript, check this quickstart and these samples. For F# see the samples, the F#-specific library and my article A Fairy Tale of F# and Durable Functions.

当前,您可以使用3种编程语言编写持久函数:C#,F#和Javascript(Node.js)。 我所有的示例都将使用C#。 对于Javascript,请查看此快速入门这些示例 。 对于F#,请参阅示例F#特定的库以及我的文章F#和持久函数的童话

Workflow building functionality is achieved by the introduction of two additional types of triggers: Activity Functions and Orchestrator Functions.

通过引入两种其他类型的触发器来实现工作流构建功能:活动功能和协调器功能。

活动功能 (Activity Functions)

Activity Functions are simple stateless single-purpose building blocks that do just one task and have no awareness of the bigger workflow. A new trigger type, ActivityTrigger, was introduced to expose functions as workflow steps, as I explain below.

活动功能是简单的无状态单用途构建基块,仅执行一项任务,并且不了解更大的工作流程。 引入了新的触发器类型ActivityTrigger ,以将函数作为工作流步骤公开,如下所述。

Here is a simple Activity Function implemented in C#:

这是用C#实现的简单活动功能:

[FunctionName("BookConference")]
public static ConfTicket BookConference([ActivityTrigger] string conference)
{
    var ticket = BookingService.Book(conference);
    return new ConfTicket { Code = ticket };
}

It has a common FunctionName attribute to expose the C# static method as an Azure Function named BookConference. The name is important because it is used to invoke the activity from orchestrators.

它具有通用的FunctionName属性,以将C#静态方法公开为名为BookConference的Azure函数。 该名称很重要,因为它用于从协调器调用活动。

The ActivityTrigger attribute defines the trigger type and points to the input parameter conference which the activity expects to get for each invocation.

ActivityTrigger属性定义触发器类型,并指向活动期望每次调用获得的输入参数conference

The function can return a result of any serializable type; my sample function returns a simple property bag called ConfTicket.

该函数可以返回任何可序列化类型的结果; 我的样本函数返回一个简单的属性袋ConfTicket

Activity Functions can do pretty much anything: call other services, load and save data from/to databases, and use any .NET libraries.

活动功能几乎可以做任何事情:调用其他服务,从数据库中加载和保存数据,以及使用任何.NET库。

协调器功能 (Orchestrator Functions)

The Orchestrator Function is a unique concept introduced by Durable Functions. Its sole purpose is to manage the flow of execution and data among several activity functions.

协调器功能是耐用功能引入的独特概念。 其唯一目的是管理多个活动功能之间的执行和数据流。

Its most basic form chains multiple independent activities into a single sequential workflow.

它的最基本形式将多个独立的活动链接到一个顺序的工作流程中。

Let's start with an example which books a conference ticket, a flight itinerary, and a hotel room one-by-one:

让我们以一个示例为例,该示例一张一张预订会议票,一个航班行程和一个酒店房间:

Sequential Workflow
3 steps of a workflow executed in sequence
依次执行工作流程的3个步骤

The implementation of this workflow is defined by another C# Azure Function, this time with OrchestrationTrigger:

此工作流的实现由另一个C#Azure函数定义,这次使用OrchestrationTrigger

[FunctionName("SequentialWorkflow")]
public static async Task Sequential([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var conference = await context.CallActivityAsync<ConfTicket>("BookConference", "ServerlessDays");
    var flight = await context.CallActivityAsync<FlightTickets>("BookFlight", conference.Dates);
    await context.CallActivityAsync("BookHotel", flight.Dates);
}

Again, attributes are used to describe the function for the Azure runtime.

同样,属性用于描述Azure运行时的功能。

The only input parameter has type DurableOrchestrationContext. This context is the tool that enables the orchestration operations.

唯一的输入参数具有DurableOrchestrationContext类型。 此上下文是启用编排操作的工具。

In particular, the CallActivityAsync method is used three times to invoke three activities one after the other. The method body looks very typical for any C# code working with a Task-based API. However, the behavior is entirely different. Let's have a look at the implementation details.

特别是, CallActivityAsync方法被使用了三次来一个接一个地调用三个活动。 对于使用基于Task的API的任何C#代码,方法主体看起来都是非常典型的。 但是,行为是完全不同的。 让我们看一下实现细节。

幕后花絮 (Behind the Scenes)

Let's walk through the lifecycle of one execution of the sequential workflow above.

让我们逐步介绍一下上述顺序工作流程的一次执行的生命周期。

When the orchestrator starts running, the first CallActivityAsync invocation is made to book the conference ticket. What actually happens here is that a queue message is sent from the orchestrator to the activity function.

当协调器开始运行时,将进行第一个CallActivityAsync调用以预订会议票。 实际发生的是,队列消息从协调器发送到活动功能。

The corresponding activity function gets triggered by the queue message. It does its job (books the ticket) and returns the result. The activity function serializes the result and sends it as a queue message back to the orchestrator:

队列消息触发相应的活动功能。 它完成其工作(预订票证)并返回结果。 活动功能将结果序列化,并将其作为队列消息发送回协调器:

Durable Functions: Message Passing
Messaging between the orchestrator and the activity
协调器和活动之间的消息传递

When the message arrives, the orchestrator gets triggered again and can proceed to the secondactivity. The cycle repeats—a message gets sent to Book Flight activity, it gets triggered, does its job, and sends a message back to the orchestrator. The same message flow happens for the third call.

当消息到达时,协调器再次被触发,可以继续进行第二活动。 循环重复进行-一条消息被发送到Book Flight活动,它被触发,完成其工作,然后将消息发送回编排器。 第三次呼叫发生相同的消息流。

停止恢复行为 (Stop-resume behavior)

As discussed earlier, message passing is intended to decouple the sender and receiver in time. For every message in the scenario above, no immediate response is expected.

如前所述,消息传递旨在及时分离发送方和接收方。 对于上述情况下的每条消息,都不会期望立即响应。

On the C# level, when the await operator is executed, the code doesn't block the execution of the whole orchestrator. Instead, it just quits: the orchestrator stops being active and its current step completes.

在C#级别上,当执行await运算符时,该代码不会阻止整个协调器的执行。 相反,它只是退出:协调器停止活动,并且其当前步骤完成。

Whenever a return message arrives from an activity, the orchestrator code restarts. It always starts with the first line. Yes, this means that the same line is executed multiple times: up to the number of messages to the orchestrator.

每当来自活动的返回消息到达时,协调器代码都会重新启动。 它总是从第一行开始。 是的,这意味着同一行将被执行多次:最多发送给协调器的消息数。

However, the orchestrator stores the history of its past executions in Azure Storage, so the effect of the second pass of the first line is different: instead of sending a message to the activity it already knows the result of that activity, so await returns this result back and assigns it to the conference variable.

但是,协调器将其过去执行的历史记录存储在Azure存储中,因此第一行第二遍的效果是不同的:与其向活动发送消息,它已经知道该活动的结果,所以await返回返回结果并将其分配给conference变量。

Because of these "replays", the orchestrator's implementation has to be deterministic: don't use DateTime.Now, random numbers or multi-thread operations; more details here.

由于存在这些“重播”,因此协调器的实现必须是确定性的:不要使用DateTime.Now ,随机数或多线程操作; 更多细节在这里

活动采购 (Event Sourcing)

Azure Functions are stateless, while workflows require a state to keep track of their progress. Every time a new action towards the workflow's execution happens, the framework automatically records an event in table storage.

Azure功能是无状态的,而工作流需要状态来跟踪其进度。 每当执行针对工作流执行的新操作时,框架都会自动在表存储中记录一个事件。

Whenever an orchestrator restarts the execution because a new message arrives from its activity, it loads the complete history of this particular execution from storage. Durable Context uses this history to make decisions whether to call the activity or return the previously stored result.

每当协调程序由于其活动收到新消息而重新启动执行时,它都会从存储中加载此特定执行的完整历史记录。 持久上下文使用此历史记录来决定是调用活动还是返回先前存储的结果。

The pattern of storing the complete history of state changes as an append-only event store is known as Event Sourcing. Event store provides several benefits:

将状态更改的完整历史记录存储为仅追加事件存储区的模式称为事件源。 事件存储提供了以下好处:

  • Durability—if a host running an orchestration fails, the history is retained in persistent storage and is loaded by the new host where the orchestration restarts;

    持久性 —如果运行业务流程的主机发生故障,则历史记录将保留在持久性存储中,并由业务流程重新启动的新主机加载;

  • Scalability—append-only writes are fast and easy to spread over multiple storage servers;

    可扩展性 —仅追加写入可快速,轻松地分布在多个存储服务器上;

  • Observability—no history is ever lost, so it's straightforward to inspect and analyze even after the workflow is complete.

    可观察性 -不会丢失任何历史记录,因此即使在工作流程完成后也可以直接进行检查和分析。

Here is an illustration of the notable events that get recorded during our sequential workflow:

这是在顺序工作流程中记录的重要事件的说明:

Durable Functions: Event Sourcing
Log of events in the course of orchestrator progression
协调器进程中的事件日志

开票 (Billing)

Azure Functions on the serverless consumption-based plan are billed per execution + per duration of execution.

基于无服务器消耗的计划上的Azure功能按执行+执行时间计费。

The stop-replay behavior of durable orchestrators causes the single workflow "instance" to execute the same orchestrator function multiple times. This also means paying for several short executions.

持久协调器的停止重放行为导致单个工作流“实例”多次执行相同的协调器功能。 这也意味着要为几次短期执行支付费用。

However, the total bill usually ends up being much lower compared to the potential cost of blocking synchronous calls to activities. The price of 5 executions of 100 ms each is significantly lower than the cost of 1 execution of 30 seconds.

但是,与阻止同步调用活动的潜在成本相比,总费用通常要低得多。 每次执行100毫秒的5次执行的价格大大低于执行30秒的1次执行的成本。

By the way, the first million executions per month are at no charge, so many scenarios incur no cost at all from Azure Functions service.

顺便说一句,每月前一百万次执行是免费的 ,因此许多方案完全不需要使用Azure Functions服务。

Another cost component to keep in mind is Azure Storage. Queues and Tables that are used behind the scenes are charged to the end customer. In my experience, this charge remains close to zero for low- to medium-load applications.

要记住的另一个成本要素是Azure存储。 幕后使用的队列和表将向最终客户收费。 以我的经验,对于中低负载应用,此费用仍接近于零。

Beware of unintentional eternal loops or indefinite recursive fan-outs in your orchestrators. Those can get expensive if you leave them out of control.

当心编配器中无意识的永恒循环或不确定的递归扇出。 如果您将它们置于无法控制的位置,它们可能会变得昂贵。

错误处理和重试 (Error-handling and retries)

What happens when an error occurs somewhere in the middle of the workflow? For instance, a third-party flight booking service might not be able to process the request:

当工作流中间出现错误时会发生什么? 例如,第三方航班预订服务可能无法处理该请求:

Error Handling
One activity is unhealthy
一种活动不健康

This situation is expected by Durable Functions. Instead of silently failing, the activity function sends a message containing the information about the error back to the orchestrator.

耐用功能会预料到这种情况。 活动功能不是默默地失败,而是向协调器发送一条消息,其中包含有关错误的信息。

The orchestrator deserializes the error details and, at the time of replay, throws a .NET exception from the corresponding call. The developer is free to put a try .. catch block around the call and handle the exception:

协调器反序列化错误详细信息,并在重播时从相应的调用中引发.NET异常。 开发人员可以随意在调用周围放置try .. catch块并处理异常:

[FunctionName("SequentialWorkflow")]
public static async Task Sequential([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var conf = await context.CallActivityAsync<ConfTicket>("BookConference", "ServerlessDays");
    try
    {
        var itinerary = MakeItinerary(/* ... */);
        await context.CallActivityAsync("BookFlight", itinerary);
    }
    catch (FunctionFailedException)
    {
        var alternativeItinerary = MakeAnotherItinerary(/* ... */);
        await context.CallActivityAsync("BookFlight", alternativeItinerary);
    }
    await context.CallActivityAsync("BookHotel", flight.Dates);
}

The code above falls back to a "backup plan" of booking another itinerary. Another typical pattern would be to run a compensating activity to cancel the effects of any previous actions (un-book the conference in our case) and leave the system in a clean state.

上面的代码将退回到预订另一个行程的“备份计划”。 另一个典型的模式是运行补偿活动以取消任何先前操作的影响(在我们的情况下取消预定会议)并使系统保持干净状态。

Quite often, the error might be transient, so it might make sense to retry the failed operation after a pause. It's a such a common scenario that Durable Functions provides a dedicated API:

通常,错误可能是暂时的,因此在暂停后重试失败的操作可能很有意义。 耐久功能提供专用API的情况很常见:

var options = new RetryOptions(
    firstRetryInterval: TimeSpan.FromMinutes(1),                    
    maxNumberOfAttempts: 5);
options.BackoffCoefficient = 2.0;

await context.CallActivityWithRetryAsync("BookFlight", options, itinerary);

The above code instructs the library to

上面的代码指示库

  • Retry up to 5 times

    重试最多5次
  • Wait for 1 minute before the first retry

    第一次重试前请等待1分钟
  • Increase delays before every subsequent retry by the factor of 2 (1 min, 2 min, 4 min, etc.)

    将每次后续重试之前的延迟增加2倍(1分钟,2分钟,4分钟等)。

The significant point is that, once again, the orchestrator does not block while awaiting retries. After a failed call, a message is scheduled for the moment in the future to re-run the orchestrator and retry the call.

重要的一点是,协调器在等待重试时不会再次阻塞。 失败的呼叫后,计划在将来的某个时刻重新运行协调器并重试该呼叫的消息。

子编排 (Sub-orchestrators)

Business processes may consist of numerous steps. To keep the code of orchestrators manageable, Durable Functions allows nested orchestrators. A "parent" orchestrator can call out to child orchestrators via the context.CallSubOrchestratorAsync method:

业务流程可能包含许多步骤。 为了使编排器的代码易于管理,持久功能允许嵌套的编排器。 “父”协调器可以通过context.CallSubOrchestratorAsync方法调出子协调器:

[FunctionName("CombinedOrchestrator")]
public static async Task CombinedOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context)
{
    await context.CallSubOrchestratorAsync("BookTrip", serverlessDaysAmsterdam);
    await context.CallSubOrchestratorAsync("BookTrip", serverlessDaysHamburg);
}

The code above books two conferences, one after the other.

上面的代码预定了两个会议,一个接一个。

扇出/扇入 (Fan-out / Fan-in)

What if we want to run multiple activities in parallel?

如果我们要并行运行多个活动怎么办?

For instance, in the example above, we could wish to book two conferences, but the booking order might not matter. Still, when both bookings are completed, we want to combine the results to produce an expense report for the finance department:

例如,在上面的示例中,我们可能希望预订两个会议,但是预订顺序可能并不重要。 尽管如此,当两个预订都完成后,我们希望将结果合并以生成财务部门的费用报告:

Parallel Calls
Parallel calls followed by a final step
并行调用,最后一步

In this scenario, the BookTrip orchestrator accepts an input parameter with the name of the conference and returns the expense information. ReportExpenses needs to receive both expenses combined.

在这种情况下, BookTrip协调器接受带有会议名称的输入参数,并返回费用信息。 ReportExpenses需要同时接收这两个费用。

This goal can be easily achieved by scheduling two tasks (i.e., sending two messages) without awaiting them separately. We use the familiar Task.WhenAll method to await both and combine the results:

通过计划两个任务(即发送两个消息)而不必分别等待它们,可以轻松实现此目标。 我们使用熟悉的Task.WhenAll方法等待两者并合并结果:

[FunctionName("ParallelWorkflow")]
public static async Task Parallel([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var amsterdam = context.CallSubOrchestratorAsync("BookTrip", serverlessDaysAmsterdam);
    var hamburg   = context.CallSubOrchestratorAsync("BookTrip", serverlessDaysHamburg);

    var expenses = await Task.WhenAll(amsterdam, hamburg);

    await context.CallActivityAsync("ReportExpenses", expenses);
}

Remember that awaiting the WhenAll method doesn't synchronously block the orchestrator. It quits the first time and then restarts two times on reply messages received from activities. The first restart quits again, and only the second restart makes it past the await.

请记住,等待WhenAll方法不会同步阻止协调器。 它第一次退出,然后在收到来自活动的回复消息时重新启动两次。 第一次重新启动会再次退出,只有第二次重新启动才能使它超过await

Task.WhenAll returns an array of results (one result per each input task), which is then passed to the reporting activity.

Task.WhenAll返回结果数组(每个输入任务一个结果),然后将其传递到报告活动。

Another example of parallelization could be a workflow sending e-mails to hundreds of recipients. Such fan-out wouldn't be hard with normal queue-triggered functions: simply send hundreds of messages. However, combining the results, if required for the next step of the workflow, is quite challenging.

并行化的另一个示例可以是向数百个收件人发送电子邮件的工作流。 对于普通的队列触发功能而言,这样的扇出并不困难:只需发送数百条消息即可。 但是,如果工作流程的下一步需要,将结果组合起来是非常困难的。

It's straightforward with a durable orchestrator:

使用持久的编排器很简单:

var emailSendingTasks =
    recepients
    .Select(to => context.CallActivityAsync<bool>("SendEmail", to))
    .ToArray();

var results = await Task.WhenAll(emailSendingTasks);

if (results.All(r => r)) { /* ... */ }

Making hundreds of roundtrips to activities and back could cause numerous replays of the orchestrator. As an optimization, if multiple activity functions complete around the same time, the orchestrator may internally process several messages as a batch and restart the orchestrator function only once per batch.

进行数百次往返活动的往返旅行可能会导致协调器重播。 作为优化,如果多个活动功能大约在同一时间完成,那么协调器可以在内部将多个消息作为批处理,并且每个批处理仅重新启动一次协调器功能。

其他概念 (Other Concepts)

There are many more patterns enabled by Durable Functions. Here is a quick list to give you some perspective:

持久功能支持更多模式。 这是一个快速列表,可以给您一些看法:

  • Waiting for the first completed task in a collection (rather than all of them) using the Task.WhenAny

    使用Task.WhenAny等待集合中的第一个完成的任务(而不是所有任务)

    method. Useful for scenarios like timeouts or competing actions.

    方法。 对于超时或竞争行为等场景很有用。

  • Pausing the workflow for a given period or until a deadline.

    在给定时间段或截止日期之前暂停工作流程。
  • Waiting for external events, e.g., bringing human interaction into the workflow.

    等待外部事件,例如将人机交互带入工作流。
  • Running recurring workflows, when the flow repeats until a certain condition is met.

    当流程重复执行直到满足特定条件时,运行循环工作流程。

Further explanation and code samples are in the docs.

进一步的解释和代码示例在docs中

结论 (Conclusion)

I firmly believe that serverless applications utilizing a broad range of managed cloud services are highly beneficial to many companies, due to both rapid development process and the properly aligned billing model.

我坚信,由于快速的开发过程和正确的计费模式,利用广泛的托管云服务的无服务器应用程序对许多公司都非常有利。

Serverless tech is still young; more high-level architectural patterns need to emerge to enable expressive and composable implementations of large business systems.

无服务器技术还很年轻。 需要出现更多高级架构模式,以实现大型业务系统的表达性和可组合实现。

Azure Durable Functions suggests some of the possible answers. It combines the clarity and readability of sequential RPC-style code with the power and resilience of event-driven architecture.

Azure耐用功能提出了一些可能的答案。 它结合了顺序RPC样式代码的清晰度和可读性以及事件驱动体系结构的强大功能和灵活性。

The documentation for Durable Functions is excellent, with plenty of examples and how-to guides. Learn it, try it for your real-life scenarios, and let me know your opinion—I'm excited about the serverless future!

耐用功能文档非常出色,其中包含大量示例和操作指南。 学习它,在现实生活中尝试一下,让我知道您的意见-我对无服务器的未来感到兴奋!

致谢 (Acknowledgments)

Many thanks to Katy Shimizu, Chris Gillum, Eric Fleming, KJ Jones, William Liebenberg, Andrea Tosato for reviewing the draft of this article and their valuable contributions and suggestions.

非常感谢Katy ShimizuChris GillumEric FlemingKJ JonesWilliam LiebenbergAndrea Tosato审阅了本文的草稿及其宝贵的意见和建议。

翻译自: https://www.freecodecamp.org/news/making-sense-of-azure-durable-functions/

azure多功能成像好用吗

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值