Axon(十)

十、调试

10.1事件快照

10.1.1快照

当聚合长期存在,状态不断变化时,会产生大量的事件。必须加载所有这些事件来重建聚合的状态可能会对性能产生很大影响。快照事件是一个具有特殊用途的域事件:它将任意数量的事件汇总为单个事件。通过定期创建和存储快照事件,事件存储不必返回长的事件列表。只有最新的快照事件和创建快照后发生的所有事件。

例如,库存商品往往经常变动。每卖出一件商品,一件事就会使库存减少一件。每一批新产品进货,库存量就会增加一些更大的数字。如果你每天卖出100件商品,你每天至少会生产100件活动。几天后,您的系统将花费太多时间读取所有这些事件,以确定是否应该引发“ItemOutOfStockEvent”。一个快照事件可以替换许多这样的事件,只需存储库存中的当前项目数。

10.1.1.1创建快照

快照创建可以由许多因素触发,例如:自上次快照以来创建的事件数、初始化聚合的时间超过某个阈值、基于时间等。目前,Axon提供了一种机制,允许您根据事件计数阈值触发快照。

SnapshotTriggerDefinition接口提供了何时创建快照的定义。

EventCountSnapshotTriggerDefinition提供了当加载聚合所需的事件数超过某个阈值时触发快照创建的机制。如果加载聚合所需的事件数超过某个可配置阈值,则触发器将通知快照程序为聚合创建快照。

快照触发器是在事件源存储库上配置的,它具有许多属性,允许您调整触发:

  1. Snapshotter设置实际Snapshotter实例,负责创建和存储实际快照事件;
  2. trigger触发器设置触发快照创建的阈值;

Snapshoter负责实际创建快照。通常,快照是一个尽可能少地干扰操作的过程。因此,建议在其他线程中运行Snapshoter。Snapshoter接口声明了一个方法:scheduleSnapshot(),它将聚合的类型和标识符作为参数。

Axon提供了AggregateSnapshoter,它创建和存储AggregateSnapshot实例。这是一种特殊类型的快照,因为它包含实际的聚合实例。Axon提供的存储库知道这种类型的快照,并将从中提取聚合,而不是实例化新的快照。快照事件之后加载的所有事件都流式传输到提取的聚合实例。

序列化快照事件

请确保使用的序列化程序实例(默认为XStreamSerializer)能够序列化聚合。XStreamSerializer要求您使用热点JVM,或者您的聚合必须具有可访问的默认构造函数或实现可序列化接口。

AbstractSnapshoter提供了一组基本属性,允许您调整快照的创建方式:

  1. EventStore设置事件存储,用于加载过去的事件并存储快照。此事件存储必须实现SnapshotEventStore接口。
  2. Executor设置执行器,例如ThreadPoolExecutor,它将提供线程来处理实际的快照创建。默认情况下,快照是在调用scheduleSnapshot()方法的线程中创建的,这通常不建议用于生产。

AggregateSnapshoter还提供了一个属性:

  1. AggregateFactories属性允许您设置将创建聚合实例的工厂。通过配置多个聚合工厂,您可以使用单个Snapshoter为各种聚合类型创建快照。
  2. EventSourcingRepository实现和AggregateConfiguration提供了对给定聚合所使用的AggregateFactory的访问。

这两种方法分别通过EventSourcingRepository和AggregateConfiguration aggregateFactory方法提供工厂。任何一个的结果都可以用于在Snapshoter中配置与聚合使用的聚合工厂相同的聚合工厂。

Snapshoter配置

如果使用在另一个线程中执行快照创建的执行器,请确保为底层事件存储配置了正确的事务管理(如有必要)。

对于非Spring用户和Spring用户,都提供了一个默认的snapshoter。前者使用配置API提供默认的aggregatesnapshoter,从注册的Aggregates/AggregateConfigurations检索聚合工厂。Spring使用springaggregatesnapshoter,当需要创建快照时,它将自动从应用程序上下文中查找正确的AggregateFactory实例。

Axon部分

AggregateConfigurer<GiftCard> giftCardConfigurer =

        AggregateConfigurer.defaultConfiguration(GiftCard.class)

                           .configureSnapshotTrigger(config -> new EventCountSnapshotTriggerDefinition(

                                   config.snapshotter(), 500

                           ));

Configurer configurer = DefaultConfigurer.defaultConfiguration()

                                         .configureAggregate(giftCardConfigurer);

Springboot部分

可以将聚合的自定义SnapshotTriggerDefinition定义为SpringBean。为了将SnapshotTriggerDefinition bean绑定到聚合,请在@aggregate注释上使用SnapshotTriggerDefinition属性。下面的清单展示了如何定义一个定制的EventCountSnapshotTriggerDefinition,它将每500个事件创建一个快照。

请注意,如果Snapshotter实例尚未明确定义为bean,则会自动为您配置。这意味着您可以简单地将Snapshotter作为参数传递给SnapshotTriggerDefinition。

@Bean

public SnapshotTriggerDefinition giftCardSnapshotTrigger(Snapshotter snapshotter) {

    return new EventCountSnapshotTriggerDefinition(snapshotter, 500);

}

...

@Aggregate(snapshotTriggerDefinition = "giftCardSnapshotTrigger")

public class GiftCard {...}

10.1.1.2存储快照事件

当快照存储在事件存储中时,它将自动使用该快照汇总所有以前的事件,并将其返回原位。所有事件存储实现都允许并发创建快照。这意味着它们允许在另一个进程为同一个聚合添加事件时存储快照。这允许快照过程作为单独的进程一起运行。

快照作为事件的替代?

通常,一旦所有事件成为快照事件的一部分,就可以对其进行存档。在常规操作场景中,快照事件永远不会被事件存储再次读入。但是,如果希望能够在创建快照之前重建聚合状态,则必须使事件保持最新状态

Axon提供了一种特殊类型的快照事件:AggregateSnapshot,它将整个聚合存储为快照。动机很简单:您的聚合应该只包含与做出业务决策相关的状态。这正是您希望在快照中捕获的信息。Axon提供的所有事件源存储库都会识别聚合快照,并将从中提取聚合。请注意,使用此快照事件需要事件序列化机制能够序列化聚合。

10.1.1.3筛选快照事件

启用快照时,事件存储中的每个聚合实例将存储多个快照。在某个阶段,应用程序不再使用这些快照事件中的某些,因为更新的版本取代了它们。尤其是如果这些快照事件通过使用AggregateSnapshot事件描绘了一种旧的聚合格式,那么不再加载这些快照是否明智。

您可以采取删除存储的所有快照的立场(对于给定的聚合类型),但这意味着将以100%的确定性重新创建快照。从事件存储读取聚合时,也可以过滤掉快照事件。为此,可以为每个聚合类型或整个EventStore定义SnapshotFilter。

SnapshotFilter是一个功能接口,提供两个主要操作:allow(DomainEventData<?)然后合并(SnapshotFilter)。前者提供反映快照事件的DomainEventData。后者允许将几个快照过滤器组合在一起。

以下代码片段演示如何配置SnapshotFilter:

Axon部分

SnapshotFilter giftCardSnapshotFilter = snapshotData -> /* allow or disallow this snapshotData */;

 

AggregateConfigurer<GiftCard> giftCardConfigurer =

        AggregateConfigurer.defaultConfiguration(GiftCard.class)

                           .configureSnapshotFilter(config -> giftCardSnapshotFilter);

Configurer configurer = DefaultConfigurer.defaultConfiguration()

                                         .configureAggregate(giftCardConfigurer);

Springboot部分

可以将聚合的自定义SnapshotFilter定义为SpringBean。为了将SnapshotFilter bean绑定到聚合,请在@aggregate注释上使用SnapshotFilter属性。

@Bean

public SnapshotFilter giftCardSnapshotFilter() {

    return snapshotData -> /* allow or disallow this snapshotData */;

}

...

@Aggregate(snapshotFilter = "giftCardSnapshotFilter")

public class GiftCard {...}

10.1.1.4基于快照事件初始化聚合

快照事件与其他事件一样是一个事件。这意味着快照事件的处理方式与其他域事件相同。当使用注释来定义事件处理程序(@EventHandler)时,可以对基于快照事件初始化完整聚合状态的方法进行注释。下面的代码示例显示如何像对待聚合中的任何其他域事件一样处理快照事件。

public class MyAggregate extends AbstractAnnotatedAggregateRoot {

    // ...

    @EventHandler

    protected void handleSomeStateChangeEvent(MyDomainEvent event) {

        // ...

    }

    @EventHandler

    protected void applySnapshot(MySnapshotEvent event) {

        // the snapshot event should contain all relevant state

        this.someState = event.someState;

        this.otherState = event.otherState;

    }

}

有一种快照事件的处理方式不同:AggregateSnapshot。此类型的快照事件包含实际聚合。聚合工厂识别这种类型的事件并从快照中提取聚合。然后,所有其他事件将重新应用于提取的快照。这意味着聚合永远不需要处理AggregateSnapshot实例本身。

10.1.2缓存

一个设计良好的命令处理模块在实现缓存时应该不会引起任何问题。尤其是在使用事件源时,从事件存储加载聚合是一项昂贵的操作。有了正确配置的缓存后,加载聚合可以转换为纯内存进程。

以下是一些帮助您最大限度地利用缓存解决方案的准则:

  1. 确保工作单元不必因为功能原因而执行回滚。回滚意味着聚合已达到无效状态。Axon将自动使所涉及的缓存项失效。下一个请求将强制从其事件中重建聚合。如果您可以在backbus上返回一个功能性的配置值,那么就可以返回一个功能性的配置。默认情况下,对于命令处理程序的运行时异常和事件处理程序的所有异常,都将回滚工作单元。
  2. 单个聚合的所有命令必须到达缓存中包含该聚合的计算机上。这意味着只要同一台机器“正常”,命令就应该一致地路由到同一台机器上。路由命令始终防止缓存过时。当事件存储在事件存储中时,对过时缓存的命中将导致命令执行并失败。默认情况下,Axon的分布式命令总线组件将使用一致的哈希来路由命令。
  3. 配置合理的生存时间/空闲时间。默认情况下,缓存的生存时间相对较短,只有几分钟。对于具有一致路由的命令处理组件,较长的空闲时间和生存时间通常更好。这就避免了仅仅因为聚合的缓存项过期而需要根据其事件重新初始化聚合。缓存的生存时间应与聚合的预期生存期匹配。
  4. 在内存中缓存数据。为了实现真正的优化,缓存应该将数据保存在内存中(最好是在堆上),以获得最佳性能。这就避免了在存储到磁盘甚至堆外时需要(重新)序列化聚合。

10.2事件处理

通常,应用程序组件包含一个或多个负责处理传入事件的事件处理器。跟踪事件处理器具有可在运行时更改的配置方面,以适应系统拓扑结构的更改

10.2.1增加和减少段数

在多个线程中处理事件的跟踪事件处理器使用段将流中的事件以可靠的方式跨这些线程分离。然而,特别是当这些线程分布在一个组件的多个实例中,并且实例的数量发生变化时,相应地调整段的数量可能是有用的

为此,Axon框架提供了一个拆分和合并API。这个API可以通过客户机配置直接使用,也可以通过Axon服务器直接使用,Axon服务器将所需的协调考虑在内

10.2.1.1通过axon框架进行分段调整

Axon框架中的跟踪事件处理器提供了增加或减少特定实例的段数的方法。使用此API时,必须提供要增加/减少的段的ID。此外,调用该方法的实例必须积极处理该段

首先,必须获取跟踪事件处理器的实例。这可以通过使用Axon的配置API来实现,如下所示:

// The `Configuration` was returned through the `Configurer` or is available as a //bean in the Spring Application Context

Public TrackingEventProcessor retrieveTrackingProcessor(org.axonframework.config.Configuration axonConfig,

                          String processorName) {

    return axonConfig.eventProcessingConfiguration()

    .eventProcessor(processorName) // This call returns an Optional

    .filter(eventProcessor -> eventProcessor instanceof TrackingEventProcessor)

    .map(eventProcessor -> (TrackingEventProcessor) eventProcessor)

    .orElseThrow(() -> new IllegalStateException(

     "No Tracking Event Processor found with name " + processorName

                     ));

}

使用上述代码段,可以按如下方式调用拆分或合并:

int segmentId;

TrackingEventProcessor trackingProcessor = retrieveTrackingProcessor(axonConfig, processorName);

// Split...

CompletableFuture<Boolean> futureResult = trackingProcessor.splitSegment(segmentId);

// Merge...

CompletableFuture<Boolean> futureResult = trackingProcessor.mergeSegment(segmentId);

多实例设置

如果您有一个给定Axon应用程序的多个实例,这通常意味着您已经复制了跟踪事件处理器。这样的设置是需要段调整的常规场景。

请注意,尤其是在这样的设置中,您需要将所述拆分或合并委托给正确的实例。在本例中,“正确的实例”是拥有要拆分和合并的段的实例。

10.2.2黑名单事件

在非均匀分布的应用程序环境中,事件处理组件可能接收到它们没有实际事件处理成员的事件。发生这种情况是完全可以的;单个应用程序处理所有现有事件的可能性非常小。然而,这一事实为黑名单事件的优化提供了可能性

为此,Axon必须选择自动将其无法处理的事件列入黑名单。跟踪事件处理器在实际的黑名单中处于领先地位,它通过在没有任何处理程序能够处理相关事件时向使用的事件流发送信号。Axon服务器连接提供的事件流反过来实现了通知Axon服务器节点某些事件不能由其处理的功能

默认情况下,对于连接到Axon服务器的Axon客户机,黑名单是打开的。要禁用黑名单,可以对disableEventBlacklisting属性进行如下调整:

Axon部分

AxonServerConfiguration axonServerConfig = new AxonServerConfiguration();

axonServerConfig.setDisableEventBlacklisting(true);

Configurer configurer =

    DefaultConfigurer.defaultConfiguration()

        .registerComponent(AxonServerConfiguration.class, c -> axonServerConfig);

Springboot部分

axon.axonserver.disableEventBlacklisting=true

重发黑名单事件

事件处理程序的拓扑结构可能在给定应用程序的生命周期中发生更改。因此,这意味着一旦列入黑名单的事件在以后的阶段可能确实存在事件处理程序成员。为了解决这个问题,Axon服务器将定期发送黑名单事件来刷新黑名单集。

10.3命令处理

本页提供有关在Axon应用程序中调整命令处理的详细信息

10.3.1重复的命令处理程序注册

如“消息传递概念”页中所述,命令始终路由到单个目标。这意味着在一个给定的JVM中注册一个命令处理程序时,应该以理想的方式处理相同的命令处理程序方法的第二次注册

Axon应用程序对这种重复注册的反应是由DuplicateCommandHandlerResolver定义的。这个解析器是一个函数接口,它接收一个命令名和一个注册的候选命令处理程序方法;一个命令处理程序方法是返回值。默认情况下,使用LoggingDuplicateCommandHandlerResolver,它将记录警告并返回候选处理程序

要配置使用的DuplicateCommandHandlerResolver,建议使用DuplicateCommandHandlerResolution,因为这个类为所有提供的实现提供了一个句柄。例如,要将重复解析器配置为抛出DuplicateCommandHandlerSubscriptionException作为警告,可以采取以下方法:

Axon部分

在配置类中:

DefaultConfigurer.defaultConfiguration().registerComponent(

    DuplicateCommandHandlerResolution.class,

    config -> DuplicateCommandHandlerResolution.rejectDuplicates()

);

Springboot部分

@Bean

public DuplicateCommandHandlerResolver duplicateCommandHandlerResolver() {

return DuplicateCommandHandlerResolution.rejectDuplicates();

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值