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

返回本页常规视图.

CloudState概述

CloudState概述

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 - 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接口。

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 的这种设计太重,落地会是很大的问题。

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不持久,因此这意味着扩展到零,并且完全的群集崩溃将导致所有数据丢失。