事件发布及处理

Event Publishing & Processing

应用程序生成的事件需要分发给更新查询数据库,搜索引擎或任何其他需要它们的资源的组件:事件处理程序。事件总线负责将事件消息发送给所有感兴趣的组件。在接收端,事件处理器(Event Processors )负责处理这些事件,包括调用相应的事件处理程序。

发布事件

在绝大多数情况下,Aggregates将通过applying 这些事件来发布事件。但是,偶尔我们需要将事件(可能来自另一个组件内)直接发布到事件总线。要发布事件,只需在EventMessage中包装描述事件的payload即可。 GenericEventMessage.asEventMessage(Object)方法允许您将任何对象包装到EventMessage中。如果传递的对象已经是一个EventMessage,它会直接返回。

事件总线

EventBus是一种将事件分发给订阅事件处理程序的机制。 Axon提供了两个Event Bus实现:SimpleEventBus和EmbeddedEventStore。虽然这两个实现都支持订阅和跟踪处理器(请参阅事件处理器),但EmbeddedEventStore会保留事件,以便在稍后阶段重演它们。 SimpleEventBus而不会存储他们,并且一旦将它们发布到订阅的组件,就会“忘记”事件。

使用配置API时,默认使用SimpleEventBus。要配置EmbeddedEventStore,您需要提供一个StorageEngine的实现,它可以实际存储Events。

Configurer configurer = DefaultConfigurer.defaultConfiguration();
configurer.configureEmbeddedEventStore(c -> new InMemoryEventStorageEngine());

Event Processors

Event Handlers主要是指收到事件时要执行的业务逻辑。Event Processors是指处理这些过程的技术方面组件。他们开启一个工作单元,或一个事务,还要确保相关数据可以正确地附加到在事件处理期间创建的所有消息。

Event Processors大致有两种形式:订阅和跟踪。订阅Event Processors订阅自己的事件来源,并由发布机制管理的线程调用。跟踪Event Processors,用它自己管理的线程从源头拉取消息。

Assigning handlers to processors

所有processor 都有一个名称,用于标识跨JVM实例的processor 实例。两个相同名称的processor ,可以看作是同一个processor 的两个实例。

所有 Event Handlers都附加到名称是“ Event Handler”类的包名称的Processor 。

例如下面的类:
● org.axonframework.example.eventhandling.MyHandler,
● org.axonframework.example.eventhandling.MyOtherHandler, and
● org.axonframework.example.eventhandling.module.MyHandler
将创建两个Processors:
● org.axonframework.example.eventhandling with 2 handlers, and
● org.axonframework.example.eventhandling.module with a single handler
Configuration API允许您配置其他策略,以将类分配给processors,甚至可以将特定的实例分配给特定的processors。

配置processors

Processors 关心的是处理事件的技术方面,而不管每个事件触发的业务逻辑如何。然而,“常规”(单例,无状态)事件处理程序的配置方式与Sagas略有不同,因为这些不同对于这两种类型的处理程序都很重要。

Event Handlers

默认情况下,Axon将使用订阅Event Processors。可以使用配置API的EventHandlingConfiguration类来更改处理程序的分配方式以及处理器的配置方式。

EventHandlingConfiguration 类定义了许多可用于定义处理器及如何配置的方法。
registerEventProcessorFactory允许你定义一个默认的工厂方法,创建没有明确定义工厂的事件处理器。
registerEventProcessor(String name, EventProcessorBuilder builder)定义了用于创建一个带有给定名称的处理器的工厂方法。注意,此种处理器只有当名称被选择作为任何可用的事件处理程序bean的处理器时才会创建。
registerTrackingProcessor(String name)定义具有给定名称的processor 应使用默认设置配置为Tracking Event Processor。它默认使用的是TransactionManager和TokenStore.
registerTrackingProcessor(String name,Function <Configuration,TrackingEventProcessorConfiguration> processorConfiguration,Function <Configuration,SequencingPolicy <?super EventMessage <?>>> sequencingPolicy)定义具有给定名称的处理器应配置为Tracking Processor,并使用给定的TrackingEventProcessorConfiguration阅读多线程的配置设置。 SequencingPolicy定义了处理器对事件顺序处理的期望。请参阅并行处理了解更多详情。
usingTrackingProcessors()设置默认Tracking Event Processor,而不是订阅Event Processor。

Sagas

Sagas的配置使用SagaConfiguration类。它提供静态方法来初始化Tracking Processing或Subscribing Processing 的实例。

要配置Saga 在订阅模式下运行,只需:
SagaConfiguration<MySaga> sagaConfig = SagaConfiguration.subscribingSagaManager(MySaga.class);

如果你不想使用默认的EventBus / Store作为这个saga的来源来获取它的消息,你也可以定义另一个消息源:
SagaConfiguration.subscribingSagaManager(MySaga.class, c -> /* define source here */);
subscribingSagaManager()方法的另一种变体允许您传递(构建器)EventProcessingStrategy。默认情况下,Sagas是同步调用的。也可以使用这种方法进行异步。但是,使用跟踪处理程序是异步调用的首选方式。

要配置saga使用跟踪处理器(Tracking Processor),只需:
SagaConfiguration.trackingSagaManager(MySaga.class);

这将是默认属性,这意味着使用单个线程来处理事件。你可以用这种方式来改变:
SagaConfiguration.trackingSagaManager(MySaga.class)
// configure 4 threads
.configureTrackingProcessor(c -> TrackingProcessingConfiguration.forParallelProcessing(4))

TrackingProcessingConfiguration有几个方法允许您指定将创建多少个segments 以及应该使用哪个ThreadFactory来创建处理器(Processor )线程。请参阅并行处理了解更多详情。

查看SagaConfiguration类的API文档(JavaDoc),了解如何配置saga事件处理的完整细节。

Token Store

跟订阅处理器不同,跟踪处理器需要一个令牌存储来存储他们的进度。跟踪处理器通过其事件流接收的每个消息都伴随有一个令牌。这个令牌允许处理器在稍后的时间点重新打开流,从最后一个事件中取出它离开的位置。

配置API使用令牌存储,以及处理器从全局配置实例中需要的大多数其他组件。如果没有显式定义TokenStore,则使用InMemoryTokenStore,这在生产环境中不推荐使用。

要配置不同的令牌存储,请使用Configurer.registerComponent(TokenStore.class,conf - > ... create token store ...)

请注意,您可以重写TokenStore以在各自的EventHandlingConfiguration或定义该处理器的SagaConfiguration中与跟踪处理器一起使用。在通常情况下,建议使用令牌存储,将令牌存储在与事件处理程序更新视图模型相同的数据库中。这样的话在更新视图模型和令牌的存储是原子性的。

Parallel Processing(并行处理)

从Axon Framework 3.1开始,跟踪处理器可以使用多个线程来处理事件流。他们是这样的做的,他们定义了一个叫’segment’的东西,并以数字作为唯一标识符。通常,单个线程将处理单个segment。

你可以定义要使用的Segments 的数量。当Processor 第一次启动时,它可以初始化多个segments。这个数字定义了可以同时处理事件的最大线程数。 TrackingProcessor的每个节点都将尝试启动配置的线程数量,以开始处理这些线程。

Event Handlers可能对事件的排序有特定的期望。如果是这种情况,处理器必须确保这些事件以特定的顺序发送给这些处理程序。 Axon为此使用SequencingPolicy。 SequencingPolicy本质上是一个函数,它为任何给定的消息返回一个值。如果SequencingPolicy函数的返回值对于两个不同的事件消息是相等的,则意味着这些消息必须按顺序处理。默认情况下,Axon组件将使用SequentialPerAggregatePolicy,这使得由相同的聚合实例发布的事件将被顺序处理。

一个Saga实例永远不会被多个线程同时调用。因此,saga的排序政策是无关紧要的。 Axon将确保每个Saga实例都按它们在Event Bus上发布的顺序接收它需要处理的事件。
注意:
请注意, Subscribing Processors 不管理自己的线程。因此,不可能配置他们应该如何接收他们的事件。实际上,它们将始终按顺序进行工作,因为这通常是Command Handling组件中的并发级别。

Multi-node processing

对于跟踪处理器来说,处理事件的线程是否都运行在同一个节点上,或者在托管相同(逻辑)TrackingProcessor的不同节点上运行并不重要。当具有相同名称的两个TrackingProcessor实例在不同的计算机上处​​于激活状态时,它们被视为同一个逻辑处理器的两个实例。他们将“竞争”事件流的各个部分。每个实例将“声明”一个segment,防止分配给该segment的事件在其他节点上处理。

TokenStore实例将使用JVM的名称(通常是主机名和进程ID的组合)作为缺省nodeId。你可以重写TokenStore的实现来支持多节点处理。

分发事件
在某些情况下,你须要将事件发布到外部系统,比如消息代理。
Spring AMQP
Axon提供了开箱即用的东西,以便将事件传送到AMQP消息代理(如Rabbit MQ)。

Forwarding events to an AMQP Exchange

SpringAMQPPublisher将事件转发给AMQP Exchange。他通常是用EventBus或EventStore的SubscribableMessageSource来进行初始化。

要配置SpringAMQPPublisher,只需将一个实例定义为一个Spring Bean即可。有许多setter方法帮助你完成您期望的效果,如事务支持,发布者确认(如果代理支持)以及exchange 名称。

默认exchange 名称是“Axon.EventBus”。
注意:请注意,exchanges 不会自动创建。您必须声明您希望使用的队列,exchanges 和Bindings 。你可以查看Spring文档以获取更多信息。

Reading Events from an AMQP Queue

Spring对从AMQP Queue读取消息提供了广泛的支持。然而,这需要被“桥接”到Axon上,以便这些消息可以从Axon处理,就好像它们是常规的事件消息一样。

SpringAMQPMessageSource允许事件处理器从队列中读取消息,而不是从事件存储或事件总线。它充当Spring AMQP和这些处理器所需的SubscribableMessageSource之间的适配器。

配置SpringAMQPMessageSource最简单的方法是定义一个重写默认的onMessage方法的bean,并用@RabbitListener对它进行注解,如下所示:
@Beanpublic SpringAMQPMessageSource myMessageSource(Serializer serializer) {
return new SpringAMQPMessageSource(serializer) {
@RabbitListener(queues = "myQueue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
super.onMessage(message, channel);
}
};
}

Spring的@RabbitListener注解告诉Spring需要为给定Queue上的每个消息(例子中的'myQueue')调用这个方法。该方法只是调用super.onMessage()方法,该方法将事件的实际发布执行给所有已订阅的处理器。

如果要将这个MessageSource注册为subscribe Processors,须要将正确的SpringAMQPMessageSource实例传递 Subscribing Processor的构造函数:
// in an @Configuration file:@Autowiredpublic void configure(EventHandlingConfiguration ehConfig, SpringAmqpMessageSource myMessageSource) {
ehConfig.registerSubscribingEventProcessor("myProcessor", c -> myMessageSource);
}

请注意,Tracking Processors与SpringAMQPMessageSource不兼容。

Asynchronous Event Processing

异步处理事件的推荐方法是使用跟踪事件处理器(Tracking Event Processor)。即使在系统发生故障的情况下(假设事件已经被持久化了),他也能保证处理完所有的事件。

但是,也可以在订阅处理器(SubscribingProcessor)中异步处理事件。为了达到这个目的,SubscribingProcessor必须配置一个EventProcessingStrategy。这个策略可以用来改变如何管理事件监听器的调用。

默认策略(DirectEventProcessingStrategy)在传递事件的线程中调用这些处理程序。这允许处理器使用现有的事务。

另一个Axon提供的策略是AsynchronousEventProcessingStrategy。它使用Executor来异步调用事件监听器(Event Listeners)。

即使AsynchronousEventProcessingStrategy是异步执行,仍然需要按顺序处理某些事件。 SequencingPolicy定义事件是否必须按顺序,并行或两者兼而有之。策略返回给定事件的序列标识符。如果策略为两个事件返回一个相同的标识符,则意味着它们必须由事件处理程序按顺序处理。空序列标识符表示该事件可以与任何其他事件并行处理。
Axon提供了许多可以使用的通用策略:
FullConcurrencyPolicy将告诉Axon这个事件处理程序可以同时处理所有的事件。这意味着事件之间没有任何关系需要按照特定的顺序进行处理。
SequentialPolicy告诉Axon必须按顺序处理所有的事件。事件的处理将在前一个事件的处理完成时开始。
SequentialPerAggregatePolicy将强制从同一个聚合中引发的域事件按顺序处理。但是,来自不同聚合的事件可以同时处理。对于更新数据库表中聚合的细节的事件监听器( event listeners)来说,这通常是一个合适的策略。

除了这些提供的政策,您可以自定义实现。所有策略都必须实现SequencingPolicy接口。该接口定义了一个方法getSequenceIdentifierFor,它返回给定事件的序列标识符。返回相同序列标识符的事件那么表示必须按顺序处理。产生不同序列标识符的事件可以被同时处理。出于性能原因,如果事件可能与任何其他事件并行处理,则策略实现应返回null。这是更快的,因为Axon不必检查事件处理的任何限制。

建议在使用AsynchronousEventProcessingStrategy时显示定义一个ErrorHandler。默认的ErrorHandler传播异常,但是在异步执行中,除Executor之外没有任何东西传播。这可能会导致事件没有被处理。相反,建议使用ErrorHandler报告错误并允许处理继续。 ErrorHandler在SubscribingEventProcessor的构造函数上进行配置,其中还提供了EventProcessingStrategy。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 由应用程序生成的事件需要被分发到更新查询数据库的组件,搜索引擎或其他需要它们的资源:事件处理程序(Event Ha...
    勇赴阅读 1,246评论 0 1
  • 52 /寻找100首歌系列 在“20世纪感动日本的100首歌”这个榜单里收录了两首石川さゆり(石川小百合)的歌,我...
    达耳闻阅读 1,782评论 0 3
  • 最近的生活有点乱糟糟的,不知道干什么,也不知道怎么做。可能有的人看到这句话马上就觉得我是一个对生活已经完全没有方向...
    讲不出来阅读 385评论 0 0