这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

CloudState官方文档

CloudState官方文档学习

1 - CloudState官方文档概述

CloudState官方文档概述

CloudState官方文档

2 - CloudState概述

CloudState概述

2.1 - CloudState概述

CloudState概述

备注:内容摘录自 CloudState概述:https://cloudstate.io/docs/user/features/index.html

高级概念

状态管理倒置

在传统的n层体系结构中,一层(应用层)将调用另一层(数据库层)来检索和操纵其状态。这种架构方法面临一些挑战,这些挑战与 serverless 的理念息息相关:

  • 应用必须知道数据存储的位置,正在使用的技术,如何与之通讯等。
  • 该应用负责处理与管理状态相关的错误,包括基础设施级别的故障以及领域级别的错误,例如并发更新和事务。

Cloudstate会反转此模型。应用代码不会调用状态管理系统,状态管理系统调用应用代码。如何访问数据,使用何种技术等已 100% 成为状态管理系统的领域。数据访问错误也成为状态管理系统的领域-用户代码从此不必关注。事务问题,例如管理并发更新,缓存,分片,路由等,都成为状态管理系统而不是用户代码的关注。

在将状态管理问题从用户代码范围转移到基础设施范围时,我们可以简化用户需要编写的代码,从而实现更多自动化,并实现更多的深度监控。

换句话说,在反转状态管理时,应用代码不与数据库对话,更正确的说法是是数据库(或更确切地说,状态管理系统)与应用代码对话。应用代码不与数据库建立连接或任何调用,状态管理系统根据需要连接到应用代码。应用代码不会针对所需的状态发出查询,状态管理系统根据需要将状态传递给应用代码,作为需要处理的命令。

专业术语

下图说明了Cloudstate系统的不同组件如何组合在一起。

该图显示了不同的Cloudstate概念如何融合在一起

有状态服务

有状态服务是可部署的单元。它在Kubernetes中表示为 StatefulService 资源。它包含一个User函数,并且可以引用有状态存储。当Cloudstate Operator 处理它时,它将使用关联的 Kubernetes 服务将其转换为 Kubernetes 部署资源以进行访问。部署将注入Cloudstate Proxy

状态存储

状态存储是对数据存储(通常是数据库)的抽象。它在Kubernetes中表示为 StatefulStore 资源。多个有状态服务可以使用单个存储来存储其数据,但是有状态服务可能不一定配置存储:如果有状态服务不具有任何持久化的状态,则它不需要有状态存储。

User Function

User Function是用户编写,打包为Docker映像并部署为 有状态服务 的代码。User function 公开了gRPC接口,该接口使用 Cloudstate 协议。注入的 Cloudstate 代理 使用此协议与用户功能对话。尽管用户功能确实实现了该协议,但是最终用户开发人员自己通常不会提供此实现,而是使用特定于 User function 所使用的语言的Cloudstate 支持库来实现协议,并提供一种语言特定的惯用API,供开发人员进行编码。

协议

Cloudstate协议是协议的开放规范,用于Cloudstate状态管理代理与 Cloudstate user function 通讯。Cloudstate项目本身提供了此协议的参考实现。该协议是使用gRPC构建和指定的,并支持多种不同的Entity类型。Cloudstate提供了一个TCK,可用于验证可用的代理支持库的任何排列。

代理

Cloudstate代理作为 sidecar 注入到每个有状态服务的 deployment 中。它负责状态管理,并暴露出由 user function 实现的 Entity Service,给系统的其余部分提供 GRPC和REST服务,转换传入的调用为 命令 ,并发送到使用Cloudstate 协议 的 User function。代理通常将与同一有状态服务中的其他节点形成集群,从而允许高级状态管理功能,例如在单个有状态服务的多个节点之间的分片,复制和寻址通信。

参考实施

Cloudstate参考实现可实现Cloudstate 协议。它是使用 Akka 实现的,利用 Akka 的集群功能来提供Cloudstate有状态功能的可扩展和弹性实现。

支持库

尽管可以通过实现 Cloudstate 协议 中的gRPC接口来简单地实现 user function,但该协议有些底层,并且并不特别适合于表达业务逻辑,而这些业务逻辑通常驻留在user function中。相反,鼓励开发人员为他们选择的语言(如果有)使用Cloudstate支持库。

命令

命令是 user function 收到的消息。命令可能来自有状态服务之外,也可能来自其他有状态服务,其他非Cloudstate服务或外界,或者它们可能来自服务内部(作为调用或转发来自另一命令的命令处理的副作用)。

实体

一个user function实现一个或多个实体。实体在概念上等效于类或状态类型。一个实体将具有可以处理命令的多个实例。例如,user function 可以实现包含与聊天室关联逻辑的聊天室实体,并且特定的聊天室可以是该实体的实例,其中包含当前在该房间中的用户列表和发送给它的消息历史记录。每个实体都有一个特定的Entity类型,它定义了实体状态如何持久,共享以及其功能是什么。

实体实例

实体的实例。实体实例由 Entity Key 标识,该键对于给定实体是唯一的。实体在User function中保存状态,并且根据实体类型 保存此状态在gRPC流的上下文中。收到特定实体实例的命令后,代理服务器将对该实体实例向User函数进行新的流式gRPC调用。该实体实例收到的所有后续命令将通过该流调用发送。

实体服务

实体服务是一种gRPC服务,它允许与Entity进行交互。该代理使这项服务可用于其他Kubernetes服务和ingreses使用,而用户功能提供了它的实现。请注意,该服务并非像普通的gRPC服务那样由 user function 直接实现,而是通过Cloudstate 协议实现的,该协议通过状态管理功能(例如接收和更新状态的功能)丰富了传入和传出的gRPC消息。

实体类型

实体使用的状态管理的类型。可用的类型包括事件源和无冲突的复制数据类型。每种类型都有自己的子协议,作为Cloudstate 协议的一部分,用于状态管理,以传达状态和特定于该类型的更新。

实体键

用于标识Entity实例的键。所有命令都必须包含实体键,以便可以将命令路由到该命令所来自的实体的正确实例。实体服务的gRPC描述符注释了实体的传入消息类型,以指示哪些字段包含实体密钥。

Event Sourced

一种实体,使用事件日志存储其状态,并通过重播该日志来恢复其状态。在Event Sourcing中将详细讨论这些内容。

无冲突的复制数据类型

一种实体类型,它使用无冲突的复制数据类型(CRDT)存储其状态,该类型在服务的不同节点之间复制。

2.2 - gRPC描述符

gRPC描述符

备注:内容摘录自 CloudState概述:https://cloudstate.io/docs/user/features/grpc.html

Cloudstate实体使用gRPC描述符指定其接口。这是一个示例描述符:

syntax = "proto3";

import "google/protobuf/empty.proto";
import "cloudstate/entity_key.proto";

package example.shoppingcart;

service ShoppingCartService {
    rpc AddItem(AddLineItem) returns (google.protobuf.Empty);
    rpc RemoveItem(RemoveLineItem) returns (google.protobuf.Empty);
    rpc GetCart(GetShoppingCart) returns (Cart);
}

message AddLineItem {
    string user_id = 1 [(.cloudstate.entity_key) = true];
    string product_id = 2;
    string name = 3;
    int32 quantity = 4;
}

message RemoveLineItem {
    string user_id = 1 [(.cloudstate.entity_key) = true];
    string product_id = 2;
}

message GetShoppingCart {
    string user_id = 1 [(.cloudstate.entity_key) = true];
}

message LineItem {
    string product_id = 1;
    string name = 2;
    int32 quantity = 3;
}

message Cart {
    repeated LineItem items = 1;
}

该描述符提供购物车实体。它支持三种不同的命令AddItemRemoveItemGetCart

指定实体键

在上面的描述符中要注意的最重要的事情是实体键注释。用作rpc命令输入的每条消息都有一个-这是Cloudstate的要求,所有入站命令消息都必须包含一个实体键。

Cloudstate使用实体键来了解命令所针对的实体实例。在上面的示例中,使用的实体键为user_id。这意味着user_id系统中每个实体都有一个购物车实体。当收到给定实体键的命令时,如果实体的状态尚未建立,Cloudstate将使用该实体类型的协议建立对 user function 的gRPC流式调用,并且针对实体键接收的任何命令都将通过该调用发送。

Cloudstate实体键必须是字符串。当将非字符串类型指定为实体键时,它将以代理特定的方式转换为字符串。因此,为了获得最大的可移植性,建议仅将字符串用作实体键。如果将多个字段指定为实体关键字,则将这些字段以代理特定的方式串联在一起。

转码HTTP

Cloudstate代理使用此处描述的Google转码注释,支持将gRPC转码为HTTP/JSON 。使用此功能,您可以使用HTTP/ JSON使用实体的gRPC接口。

2.3 - Event sourcing

CloudEvent中的Event sourcing

备注:内容摘录自 https://cloudstate.io/docs/user/features/eventsourced.html

事件源是一种提供ACID语义的持久化方法,可跨实体进行水平扩展和故障隔离。

源于 Event sourcing 的实体不是保留实体的当前状态,而是保留导致实体达到其当前状态的所有事件。这些事件存储在日志中。当实体的当前状态加载到内存中时,将读取日志,并重播每个事件,以计算实体的当前状态。

在分布式系统中,Event sourcing 的最大优势之一是可以将实体的状态可靠地复制到其他服务和视图。与基于CRUD的实体不同,后者没有固有的方式来了解特定更新是否已在其他地方复制,事件源实体可以利用其事件持久化到日记这一事实,并在该日记中使用 offset 来跟踪系统的哪些部分复制了哪些事件。这是实现命令查询责任隔离( Command Query Responsibility Segregation / CQRS)模式所必需的基本构建块,因为它允许通过使用事件日志来使读取侧视图保持最新。

基于事件的实体还允许进行时间查询,其中可以在历史记录的任何点重新创建实体的状态。这对于审核目的很有用,因为事件日志可以用作审核日志,也可以用于调试目的。

一致性保证

基于Event sourcing的实体提供了强大的一致性保证。基于Event sourcing的实体在有状态服务部署中的每个节点上共享-在任何给定时间,每个实体都将恰好位于一个节点上。如果命令到达位于不同节点上的实体的特定节点,则该命令由代理转发到包含该特定实体的节点。该转发是透明完成的,用户功能不知道它的发生。

因为每个实体都恰好位于一个节点上,所以该节点可以按顺序处理每个实体的消息。因此,不存在与事件源实体相关的并发问题,每个实体一次只处理一个消息。

术语

Cloudstate在事件源方面使用以下术语:

  • 状态

    状态是当前设置的 event sourced 实体实例的值。它由 event sourced 实体保存在内存中。

  • 命令

    命令是一个消息,寻址到特定实体来执行特定操作。命令来自发送者,并且答复可以发送到发送者。命令可能与事件有所不同,因为命令是执行操作的请求-它指的是将来应该发生的事情。命令可能会失败。另一方面,事件指的是过去已经发生的事情。该事件是当前服务如何发现该事件的信息-我们无法更改该事件,因为该事件已经发生。但是,一个服务发出的事件可能被另一服务解释为命令。命令不持久,只有事件持久。

  • 命令处理程序

    一个命令处理程序是处理一个命令的代码。它可以使用当前状态来验证命令,并且可以发出事件作为其处理的一部分。命令处理程序不得直接更新实体的状态,而只能通过发出事件间接地更新实体的状态。如果命令处理程序确实更新了状态,则当钝化实体(从内存中删除)时,这些更新将丢失

  • 事件

    一个事件是一块得到持久数据的提供并且指示已经发生改变的实体。事件存储在日记中,并且每次由状态管理系统重新加载实体时都读取并重播事件。

  • 事件处理程序

    事件处理程序是被允许的唯一代码段更新该实体的状态。它接收事件,并根据事件更新状态。

  • 快照

    快照是实体的整个当前状态的记录,作为优化,它会定期保留(例如,每100个事件)。使用快照,当从日记中重新加载实体时,不需要重播整个日记,只需重播上次快照以来的更改即可。

  • 持续化ID

    持久化ID是持久时被预置到各实体ID的标识符。如果要在多个有状态服务之间共享数据库,则必须为每个实体选择唯一的持久性ID,这样它们的实体实例才不会发生冲突,这一点很重要。

个人看法:引入 eventing source 和 CQRS,复杂度大幅上升,cloudstate 的这种设计太重,落地会是很大的问题。

2.4 - 无冲突的复制数据类型

CloudEvent中的Event sourcing

备注:内容摘录自 https://cloudstate.io/docs/user/features/crdts.html

无冲突的复制数据类型(Conflict-free Replicated Data Types / CRDT)是可用于支持分布式系统中状态的高可用性和可伸缩性共享的数据结构。CRDT状态复制到系统中的每个节点。每个节点都可以读取和更新CRDT,而无需其他节点的任何协调。如果两个或更多节点同时修改了CRDT,则可以将修改合并在一起,并且CRDT保证最终,所有节点将就该CRDT的当前状态达成一致。

使CRDT成为CRDT的是其合并功能。对于给定的数据类型,您可以定义一个函数,该函数采用其保存的数据的多个版本,然后使用该函数将这些版本合并到一个版本中,这样就不会影响版本的合并顺序,您仍将始终获得相同的结果,然后可以将该数据类型用作CRDT。

要使用CRDT,应用程序必须找到一种使用可用的CRDT表示其数据的方法。重要的是要记住,CRDT并不是一种魔术技术,可以浇灌在任何任意数据模式上以使其高度可用和可扩展。必须仔细设计应用程序表示其数据的方式,以在可用CRDT的约束下工作。

何时使用CRDT

CRDT在不需要强一致性而只需要最终一致性的情况下很有用。CRDT不保证在一个节点上进行的更新将立即在所有节点上可见,而是保证此类更新最终将在所有节点上可见。

在需要非常低延迟的读写的情况下,它们很有用。读取CRDT不需要网络通信,因为只需从节点本地副本读取值即可。写CRDT也不需要网络通信,因为写CRDT仅需要更新节点本地副本。稍后将CRDT的更新复制到其他节点。

在需要高可用性的情况下,CRDT也很有用。如果一个或多个节点在群集中变得无响应,则丝毫不影响其他节点读取和更新其持有的CRDT的能力。对于网络分区,一旦解决了网络分区,对CRDT执行的所有更新都将复制到曾经无法访问的节点。

最后,在需要非常高吞吐量写入的某些情况下,CRDT很有用。确切地说,当此保留取决于所使用的特定CRDT时,例如计数器和投票可以支持非常高的吞吐量写入,因为它们不需要将每个更新都复制到每个节点,仅偶尔复制它们的更新就足够了。

Cloudstate中提供的CRDT

  • GCounter

    仅增长计数器(Grow-only Counter/GCounter)是只能递增的计数器。它的工作方式是为每个节点跟踪一个单独的计数器值,然后取所有节点的值之和以获得当前计数器值。由于每个节点仅更新其自己的计数器值,因此每个节点可以协调这些更新以确保它们是一致的。然后,如果合并函数看到同一个节点的两个不同值,则仅取最高值,因为该值必须是节点发布的最新值。

  • PN计数器

    正负计数器(PNCounter)是既可以递增也可以递减的计数器。它通过组合两个GCounter来工作,一个为正数,跟踪增量,一个为负数,跟踪增量。通过从正GCounter减去负GCounter来计算最终计数器值。

  • GSet

    仅增长集或GSet是只能添加项目的集合。GSet是非常简单的CRDT,其合并功能是通过合并两个合并的GSet来定义的。

  • 或设置

    观察删除集或ORSet是可以添加和删除项目的集合。它是通过为每个元素维护一组唯一的标记来实现的,这些标记在添加到集合中时会生成。删除元素时,该节点当前观察到的所有标签都将添加到删除集中,因此,只要没有任何新的添加,而该节点在删除元素时都没有看到该元素,则该元素将是删除。一个简单的实现会在删除元素时累积逻辑删除,但是Cloudstate参考实现提供了清理逻辑删除的实现。

  • Flag是一个以false开头的布尔值,可以将其设置为true。一旦设置为true,就不能再设置为false。标志是一个非常简单的CRDT,合并功能只是一个布尔值或两个正在合并的标志值。

  • LWW注册

    最后写入胜利寄存器或LWWRegister是一个CRDT,它可以保存任何值,以及时钟值和节点ID,以指示何时由哪个节点更新它。如果两个节点具有两个不同的值版本,则时钟值最高的一个将获胜。如果时钟值相等,则使用节点上的稳定函数确定时钟(例如,地址最低的节点)。请注意,LWWRegister不支持对其值的部分更新。如果寄存器包含一个人员对象,并且一个节点更新了age属性,而另一个节点同时更新了name属性,则这些更新中只有一个最终将获胜。默认情况下,LWWRegister容易受到节点之间时钟偏差的影响。如果有更值得信赖的更新顺序可用,Cloudstate支持选择提供自定义时钟值。

  • OR地图

    观察到的已移除地图或ORMap与ORSet相似,此外,该集合的值用作地图的键,而地图的值本身就是CRDT。当在两个不同的节点上同时修改ORMap中相同键的值时,两个节点中的值将合并在一起。

  • 投票

    投票是一种CRDT,它允许节点对条件进行投票。它类似于GCounter,每个节点都有自己的计数器,奇数被认为是条件的投票,而偶数被认为是条件的投票。投票的结果是通过获取当前是集群成员的所有节点的投票来决定的(当节点离开时,其投票将被丢弃)。可以使用多种决策策略来决定投票结果,例如至少一项,多数和全部。

Cloudstate中的CRDT方法

有状态服务可以管理多个CRDT,每个CRDT由Cloudstate实体密钥标识。

Cloudstate代理负责实施CRDT机制。这包括合并功能,复制状态更改并确保所有节点最终在每个CRDT状态上达成共识的机制。代理将告诉用户功能CRDT的状态,并具有用于推送更新并从用户功能接收更新的协议。代理服务器和用户功能通过依次轮流进行更新来保持其本地表示同步-当用户功能从代理服务器接收命令时,允许用户功能进行更新,并且允许代理服务器进行更新(用户功能上没有未执行的命令时)。这种方法使CRDT在用户功能中的实现非常简单,

流式CRDT呼叫

Cloudstate CRDT支持处理服务器流式调用,即CRDT的gRPC服务调用将返回类型标记为时streamed。当用户功能接收到流消息时,有两种情况允许更新CRDT:第一次收到呼叫时和客户端取消流时。如果希望在其他时间进行更新,则可以通过沿流发送的流消息发出效果来进行更新。

用户功能可以响应任何内容而向下游发送消息,但是Cloudstate提供的支持库仅允许响应CRDT更改发送消息。这样,可以实现需要监视CRDT状态的用例。

写一致性

默认情况下,更新在本地节点上执行,并异步复制到其他节点。更新也可以在其他写入一致性下执行,Cloudstate支持大多数。请注意,当使用非本地写入一致性时,CRDT的许多优点将不再可用,写入将需要网络通信,并且可能会受到其他节点故障的影响。因此,应谨慎使用非本地写入一致性,它们主要用于以下情况:在这种情况下,由于节点崩溃而导致的写入丢失(在将其复制到另一个节点之前,这种情况很少见)是不可接受的。

耐用性

尽管CRDT可以持久,但Cloudstate参考实施尚不支持持久CRDT。将来可能会提供此功能。

由于Cloudstate CRDT不持久,因此这意味着扩展到零,并且完全的群集崩溃将导致所有数据丢失。

3 - github文档

CloudState github上的文档

CloudState 的文档中内容还不完善,然后在github的首页,发现有部分文档说明,内容很不错。

https://github.com/cloudstateio/cloudstate#table-of-contents

3.1 - 为什么选CloudState?

为什么是CloudState?

备注:内容摘录自 https://github.com/cloudstateio/cloudstate#why-cloudstate

  • 从开发到生产,serverless开发人员的体现是革命性的,并将主导云计算的未来
    • FaaS,具有短暂,无状态和存活时间短的function,仅是serverless开发人员体验的第一步
    • FaaS非常适合处理密集的,可并行的工作负载,将数据从A移到B,从而提供丰富的功能并进行转换。但是有很大的限制,受限于这些它定位很好的用例,这使得进行传统的应用开发和实现分布式系统协议变得困难而效率低下。
  • 需要的是用于通用应用开发的下一代serverless平台(serverless 2.0)和编程模型(例如,微服务,流管道,AI/ML等)。
    • 可以让我们实现通用用例,例如:购物车,用户会话,事务,ML模型训练,低延迟预测服务,作业计划等等。
    • 缺少对长期存活的虚拟状态服务的支持,以可扩展和可用的方式管理分布式状态的方法,以及为工作选择正确的一致性模型的选项。
  • 我们正在通过在Knative / Kubernetes,gRPC和Akka(集群,持久性等)上构建下一代serverless的解决方案,以解决这些问题,并提供了丰富的客户端API(JavaScript,Go,Python,Java,Scala,PHP等) )

当前Serverless实现的局限性

Serverless 对不同的人意味着不同的事情。许多人认为它与 Function-as-a-Service(FaaS)相同。我们不仅仅看到它:一个新的PaaS类别,其重点是开发者体验,并支持应用的整个生命周期,而不仅仅是最新版本的编程API。

Adzic等人在论文“serverless计算:经济和架构影响”中的定义。描绘出更广阔的前景:

*“‘serverless’是指新一代 platform-as-a-service 产品,其中基础设施提供商负责接收客户端请求并对其进行响应,容量规划,任务调度和操作监控。开发人员只需担心这些问题。处理客户请求的逻辑。”

如今,无服务器是无状态服务的绝佳平台,它专注于从1-10000请求的伸缩,包括伸缩到零,并且以非常经济高效的方式(没有事件==没有成本)进行扩展,做得非常出色。它简化了规模交付并简化了运维。

Serverless的当前形式,即所谓的“Function-as-as Service”(FaaS),是一种经典的数据传输架构,我们将数据移至代码中,而不是相反。它非常适合处理密集的工作(所谓的尴尬并行),将数据从A移到B,从而提供丰富的功能并进行转换。

但是,我们认为Serverless不仅仅是FaaS(这只是整个过程的第一步)。这与特定的实现无关。而是与开发人员体验有关,这是一种构建和运行应用的新方式,现在是时候扩展其范围和支持的用例了。

  • FaaS的局限性在于其Function是短暂,无状态且寿命短。这使得构建通用的以数据为中心的云原生应用成为问题,因为在性能,延迟和吞吐量方面它过于昂贵,没有计算上下文(参考位置),并被迫一遍又一遍的加载和存储保存在后端存储中的状态。
  • FaaS的另一个局限性是,通常情况下,function不能直接寻址,这意味着它们无法使用点对点通信直接相互通信,而始终需要诉诸于发布-订阅,传递所有数据都存储在一些缓慢且昂贵的存储介质上。该模型可以很好地适用于事件驱动的用例,但产生的延迟过高,无法解决通用分布式计算问题。

有状态serverless计算的需求

如果Serverless从概念上讲是关于如何从方程式中移除人员并解决开发人员有关生产系统推理的最棘手问题,那么他们需要具有丰富且易于理解的语义的声明性API和高级抽象(除了诸如函数之类的低级原语之外)用于处理永无止境的数据流,管理复杂的分布式数据工作流以及以可靠,弹性,可扩展和高性能的方式管理分布式状态。

我们需要支持的是:

  • 有状态的、长期运行、虚拟、可寻址的组件。
    • 正如Hellerstein等人所讨论的:“如果平台为建立亲和性(例如,移动数据)付出了成本,那么它应该在多个请求中收回这一代价。这激发了程序员建立软件代理的能力,这些代理称为函数,actor,服务等-会随着时间的推移以已知身份持续存在于云中。”
  • 可广泛选择的协调和通信模式(除了通过代理进行的基于事件的发布/订阅之外),包括使用点对点,广播,聚合,合并,混洗等常见模式进行细粒度的状态共享
    • 正如乔纳斯(Jonas)等人的结论:*“这种局限性还表明,无服务器计算的新变体可能值得探索,例如,命名函数实例并允许直接寻址以访问其内部状态(例如,Actors as a Service)”
  • 可靠地管理大规模分布式状态的工具,以持久或短暂的方式,具有一致性选项,从最终因果一致性的,以及在逻辑上保持逻辑分离的同时物理放置代码和数据的方式。
    • 正如Hellerstein等人所讨论的:“过程式程序设计的顺序隐喻不会扩展到云上。开发人员需要能够鼓励代码以小粒度单位(包括数据和计算)正确运行的语言,并且可以随时间和空间轻松移动。”
  • 有状态函数的智能自适应放置-在逻辑上保持分离的同时,物理上共置代码和数据的方式。
  • 端到端的正确性和一致性-能够推理流管线和属性,并保证它具有整体性。
  • 可预测的性能,延迟和吞吐量-在启动时间,通信,协调和持久存储/访问数据方面。

端到端的正确性,一致性和安全性对于不同的服务意味着不同的含义。它完全取决于用例,不能完全外包给基础设施。下一代无服务器实现需要提供编程模型和全面的开发人员体验,并与维护这些属性的基础设施协同工作,而又不忽略最棘手,最重要的问题:如何可靠地大规模管理云中的数据。

进入Cloudstate

Cloudstate 是定义规范,协议和参考实现的标准工作,旨在将无服务器及其开发者体验的承诺扩展到通用应用开发。

Cloudstate通过添加对长时间运行的可寻址有状态服务的支持,以及通过gRPC访问映射格式正确的数据的方式,建立并扩展了传统的无状态FaaS模型,同时支持从强一致性到最终一致性的各种不同的一致性模型。根据数据的性质,决定应如何处理,管理和存储数据。

您可以定义数据模型,选择其一致性模式和解析方法,并通过格式良好的gRPC命令和读取通道的协议来访问数据,以数据为中心的操作,流管道和事件。

有状态功能与CRUD不兼容

我们需要重新考虑在 Serverless 中使用CRUD。一般而言,CRUD意味着不受限制的数据库访问,并且过于广泛和开放性,无法在无服务器环境(或与此相关的任何常规云开发)中有效使用。

无限制的数据库访问意味着用户功能本身需要管理有关数据访问和存储的细节,因此您将所有操作问题从无服务器框架转移到用户函数中。现在,框架很难知道每次访问的意图。例如:

  • 该操作是读还是写?
  • 可以缓存吗?
  • 是否可以放宽一致性,还是需要强一致性?
  • 在部分故障期间可以进行操作吗?

相反,如果我们了解这些属性,则可以自动做出更好的决策。例如:

  • 写操作快而读操作慢:向数据库添加更多内存
  • 读取不可变值:添加缓存
  • 写入必须可序列化:添加分片,每个实体添加单个写入器。

状态抽象

我们都知道约束可以解放,这对无服务器来说同样适用。实际上,Serverless成功的原因之一是它拥有如此受限的开发体验,这使您作为开发人员可以专注于本质:function的业务逻辑。例如,Serverless具有出色的模型,可以通过通信进行抽象,其中所有通信均转换为接收和发出事件。

我们问自己的问题是:我们可以用相同的方式抽象状态吗?为函数提供状态输入和输出状态的干净而统一的抽象。

这将使框架能够代表 function 来管理持久状态,在整个系统中进行全面监视和管理,并做出更明智的决策。

无约束 CRUD 在此模型中不起作用,因为我们无法将整个数据集传入函数和传出函数。我们需要的是具有受限输入/输出协议的数据存储模式。属于此类别的模式是Key-Value,Event Sourcing和CRDT。

在“ Event Sourcing”中,状态为事件日志,而状态为任何由于处理命令而新保留的事件。

在CRDT中,状态输入是增量和/或状态更新的流,状态输出是增量和/或状态更新的流。

在 Key-Value 中,state out为key,state in为value。

尽管大多数开发人员都使用过 Key-Value 存储,但 Event Sourcing 和 CRDT 可能有点陌生。有趣的是,它们在状态一致性的相对方面非常适合事件驱动模型,前者提供强(ACID)一致性(通过事件记录),而后者提供最终/因果一致性。综上所述,它们允许您为特定用例和数据集选择最佳模型,从而为我们以一致的方式管理分布式状态提供了真正广泛的选择。

高级设计

Cloudstate 的参考实现建立在Kubernetes,Knative,Graal VM,gRPC和Akka的基础之上,并且具有越来越多的针对不同语言的客户端API库。入站和出站通信始终使用受约束且定义明确的协议使用gRPC channel 通过sidecar,在该协议中,用户定义commands in, events in, command replies out, 和 events out。通过gRPC进行通信允许用户代码以不同的语言(JavaScript,Java,Go,Scala,Python等)实现。

每个有状态服务均由持久性Akka actor 的 Akka 集群支持(支持多种数据模型,存储技术和数据库)。但是,通过将用户代码桥接到后端状态和集群管理的一组 sidecar,可以使用户免受这些复杂性的影响。

管理分布式状态不只是以可靠的方式将数据从A推送到B。这涉及模型的选择,该模型反映数据在现实世界中的使用情况,以及它在可用一致性上的收敛性,而不是人为地强制执行的一致性。Kubernetes和Akka的结合能够使数据跨集群,数据中心,可用性区域和大陆,并保持有用的一致状态。此外,可以通过命令通道嵌入在有状态集群中更好地执行的重复工作,或者需要维持长时间运行状态的重复工作。

扩展无服务器的用例

FaaS适用的用例

如上所述,无服务器1.0(FaaS)非常适合可并行的以处理为中心的用例,在这种情况下,传入数据通过无状态函数的管道向下游推送,这些无状态函数在向下游推送之前先进行数据丰富和转换。

这个用例的示例有:

  • 令人尴尬(Embarrassingly)的并行任务 -通常按需和间断地调用。例如,调整图像大小,执行对象识别以及运行基于整数编程(integer-programming-based)的优化。
  • 编排函数,用于协调对专有自动伸缩服务的调用,其中后端服务本身可以真正完成繁重的工作。
  • 组成函数链的应用程序,例如,通过数据依赖关系连接的工作流程。这些用例通常显示出较高的端到端延迟。

如Adzic等。在他们的论文“无服务器计算:经济和架构影响”中写道:

“ …今天,无服务器平台可用于重要(但不是五项任务关键任务)的任务,在这些任务中,高吞吐量是关键,而不是非常低的延迟,并且单个请求可以在相对较短的时间范围内完成。在无服务器环境中托管此类任务使其成为显着降低托管成本并加快新功能交付时间的引人注目的方法。”

Cloudstate支持的新用例

但是,难以低延迟,高性能,可靠的方式使用无状态函数(FaaS)来实现传统的应用开发,微服务,有状态的数据管道和通用分布式系统问题。

Cloudstate旨在扩展模型并使其易于实现用例,例如:

  • 机器学习模型的培训和服务
    • 需要建立动态模型并提供低延迟服务的任何用例
  • 低延迟实时预测/推荐服务
  • 低延迟实时欺诈检测
  • 低延迟实时异常检测
  • 用户会话,购物车等
    • 在单个请求的整个生命周期中管理内存中(但也可能持久化)的会话状态。非常常见的用例有零售,在线游戏,实时投注等。
  • 事务和工作流管理
    • 事务性分布式工作流管理,例如Saga模式。管理工作流中的每个步骤,包括在发生故障时进行回滚/补偿操作,同时提供一致性保证方面的选项。
  • 共享的协作工作区
    • 例如,协作文档编辑和聊天室。
  • 分布式计数,投票等
  • Leader选举和其他用于协调的分布式系统协议
    • 使用Akka集群/分布式数据来实施,虽然总是在分布式存储上进行协调(例如在Lambda的情况下为DynamoDB),但这样做的成本太高,速度太慢,并且可能成为单点故障。

Cloudstate的目标是提供一种以可扩展且可用的方式实现这些用例的方法,与应用本身协同工作,同时始终提供端到端的正确性,一致性和安全性。

3.2 - 设计与架构

CloudState的设计与架构

备注:内容摘录自 https://github.com/cloudstateio/cloudstate#design-and-architecture

高层概述

Cloudstate服务如下所示:

  • 入口/Ingress -可以是Istio,Knative或Kubernetes中的常规ClusterIP服务通信。无论使用哪种服务方法,Cloudstate都希望流量在其Pod上随机且均匀地均衡负载。
  • Akka Sidecar-该Sidecar由Cloudstate Operator注入。所有请求都通过它。单个Cloudstate服务的 sidecar 组成一个集群,使用Akka远程处理彼此直接通信。该集群以及 sidecar 之间的通信链接允许状态的分片和复制,以及在Pod之间寻址的P2P消息传递。
  • 代码 -这是开发人员实现的功能。可以用任何支持gRPC的语言编写。Akka边车使用预定义的gRPC协议与用户功能进行通信。该协议既包含传入的请求,也包含传出的响应,以及传达系统当前状态的消息。通常,Cloudstate将为每种语言提供支持库,以使gRPC协议适应该语言的惯用API。
  • 分布式数据存储区 -当服务需要保留状态时(例如,实现事件源实体时),该状态将保留到分布式数据存储区中。需要注意的重要一点是,用户代码不会直接与数据存储区交互-它与Akka边车交互,而Akka边车与数据存储进行通信。这样,所有数据库通信都可以由Akka边车直接管理和监视。并且由于这是通过提供高级模式来完成的,因此可以做出一些假设,以使Sidecar可以在整个集群中安全地缓存,分片和复制数据。

通用中间表示

在Akka sidecar和用户代码之间使用的gRPC协议是一种通用中间表示,由Hellerstein等人 在无服务器计算:向前一步,退两步 中定义。这用于允许用户函数利用分布式系统技术(例如Akka)提供的功能,而无需使用与那些技术相同的语言来编写。该协议还允许 sidecar 使用任何技术实现,而不仅仅是Akka。Cloudstate基于Akka的实现用于作为参考实现。

  • IR分为两个部分。
  • 首先是发现/discovery:用户函数在这里声明希望公开的服务,以及需要丰富这些服务的有状态功能的地方。这是通过Sidecar使用IR协议调用用户函数来请求描述它的描述符来完成的。该描述符包含用户函数希望公开的服务的序列化protobuf定义。每个服务都声明为具有特定的实体类型,受支持的类型包括 Event Sourcing 和 CRDT。
  • IR的第二部分是**可插拔的实体类型协议:**每种实体类型都定义了自己的gRPC协议,用于在sidecar和用户函数之间进行通信。以下是 event sourcing 协议的摘要:
service EventSourced {
    rpc handle(stream EventSourcedStreamIn) returns (stream EventSourcedStreamOut) {}
}

message EventSourcedStreamIn {
    oneof message {
        EventSourcedEvent event = 1;
        Command command = 2;
    }
}

message EventSourcedStreamOut {
    oneof message {
        EventSourcedReply reply = 1;
        Failure failure = 2;
    }
}

message EventSourcedReply {
    oneof response {
        Reply reply = 1;
        Forward forward = 2;
    }
    repeated google.protobuf.Any events = 3;
}

当实体的命令到达时,将使用此协议发送以下消息:

  1. 如果handle该实体没有现有的流,handle则调用流调用。只要有更多命令到达该实体,此流将保持打开状态,一段时间不活动之后,该流将被关闭。
  2. Sidecar加载该实体的事件日志,并使用EventSourcedEvent消息将每个事件传递给用户函数。
  3. 实体事件日志重播后,该命令将发送到用户function。
  4. 用户function处理命令,并以响应EventSourcedReply。它包含两个响应之一,一个Reply发送到原始来源,或者一个Forward将处理转发到另一个实体。它还包含零个或多个要保留的事件。这些事件将在发送答复或转发之前保留。
  5. 当实体流仍处于活动状态时,可以接收后续命令,这些命令可以在不重播事件日志的情况下进行处理。

用户函数应在流函数调用的上下文中保留实体的当前状态。

除其他外,该Command消息包含正在调用的gRPC rpc调用的名称-该RPC调用是在发现阶段声明的。它还包含该gRPC调用的有效负载,以及提取的实体ID,用于标识该调用所针对的实体。实体ID是通过使用Protobuf字段扩展声明的,这是示例用户功能消息,其中声明了实体ID:

message AddLineItem {
    string user_id = 1 [(.cloudstate.entity_key) = true];
    string product_id = 2;
    string name = 3;
    int32 quantity = 4;
}