十八、集成框架拓展
18.1 Spring AMQP
Spring amqp是除了默认的Axon服务器之外分发事件的另一种方法。
Axon提供了开箱即用的支持,用于在AMQP消息代理(如RabbitMQ)之间传输事件。
要使用来自Axon的springamqp组件,请确保类路径上有Axon AMQP模块。
- 将事件转发到AMQP交换
Spring amqp Publisher将事件转发到AMQP交换。它是用SubscribableMessageSource初始化的,通常是EventBus或EventStore。理论上,这可能是发布者可以订阅的任何事件源。
要将应用程序中生成的事件转发到AMQP通道,一行属性配置足够:
axon.amqp.exchange=ExchangeName
axon.amqp.exchange=交换名称
这将自动将所有已发布的事件发送到具有给定名称的AMQP交换。
默认情况下,发送时不使用AMQP事务。可以使用axon.amqp.transaction-mode属性,并将其设置为事务性或发布者确认。
注意
请注意,交换不是自动创建的。您仍然必须声明要使用的队列、交换和绑定。查看Spring文档了解更多信息。
- 从AMQP队列读取事件
Spring广泛支持从AMQP队列读取消息。但是,这需要被“桥接”到Axon,这样就可以从Axon处理这些消息,就好像它们是常规事件消息一样。
SpringAMQPMessageSource允许事件处理器从队列而不是事件存储或事件总线读取消息。它充当springamqp和这些处理器所需的subscribblemessagesource之间的适配器。
要从队列接收事件并在Axon应用程序中处理它们,需要配置SpringAMQPMessageSource:
@Bean
public SpringAMQPMessageSource myQueueMessageSource(AMQPMessageConverter messageConverter) {
return new SpringAMQPMessageSource(messageConverter) {
@RabbitListener(queues = "myQueue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
super.onMessage(message, channel);
}
};
}
然后配置一个处理器,将此bean用作其消息的源:
axon.eventhandling.processors.name.source=myQueueMessageSource
请注意,跟踪处理器与SpringAMQPMessageSource不兼容。
18.2 JGroup
除了默认的Axon服务器之外,JGroups是分发命令总线(commands)的另一种方法。
JGroupsConnector使用JGroups作为底层的发现和调度机制(因为它的名字已经给出了)。对于本参考指南来说,描述JGroups的功能集有点过分,因此请参考JGroups用户指南以了解更多详细信息。
要使用Axon中的spring jgroups组件,请确保类路径上有Axon JGroups模块。
由于JGroups同时处理节点的发现和节点之间的通信,因此JGroupsConnector同时充当commandbussconnector和CommandRouter。
注意
您可以在axon distributed commandbus JGroups模块中找到分布式commandbus的JGroups特定组件。
JGroupsConnector有四个必需的配置元素:
- 通道-定义JGroups协议栈。通常,JChannel是使用JGroups配置文件的引用来构造的。JGroups提供了许多默认配置,这些配置可以作为您自己配置的基础。请记住,IP多播通常不适用于云服务,如Amazon。在这种环境中,TCP Gossip通常是一个好的开始。
- clusterName-定义每个段应该注册到的集群的名称。具有相同集群名称的段最终将彼此检测并在彼此之间分派命令。
- localSegment—命令总线实现,它分派发往本地JVM的命令。这些命令可能是由其他jvm上的实例或本地jvm上的实例调度的。
- serializer-用于在命令消息通过网络发送之前对其进行序列化。
注意
使用缓存时,应在ConsistentHash更改时清除它,以避免潜在的数据损坏(例如,当命令未指定@TargetAggregateVersion,新成员快速加入和离开JGroup,在聚合仍被缓存到其他位置时修改聚合时)
最终,JGroupsConnector需要实际连接,以便将消息发送到其他段。为此,请调用connect()方法。
JChannel channel = new JChannel("path/to/channel/config.xml");
CommandBus localSegment = SimpleCommandBus.builder().build();
Serializer serializer = XStreamSerializer.builder().build();
JGroupsConnector connector = JGroupsConnector.builder()
.channel(channel)
.clusterName("myCommandBus")
.localSegment(localSegment)
.serializer(serializer)
.build();
DistributedCommandBus commandBus = DistributedCommandBus.builder()
.connector(connector)
.commandRouter(connector)
.build();
// on one node:
commandBus.subscribe(CommandType.class.getName(), handler);
connector.connect();
// on another node, with more CPU:
commandBus.subscribe(CommandType.class.getName(), handler);
commandBus.subscribe(AnotherCommandType.class.getName(), handler2);
commandBus.updateLoadFactor(150); // defaults to 100
connector.connect();
// from now on, just deal with commandBus as if it is local...
注意
请注意,并不要求所有段都有相同类型命令的命令处理程序。对于不同的命令类型,可以使用不同的段。分布式命令总线将始终选择一个节点来发送命令,该节点支持该特定类型的命令。
Springboot配置
如果使用Spring,可能需要考虑使用JGroupsConnectorFactoryBean。它在ApplicationContext启动时自动连接连接器,并在ApplicationContext关闭时正确断开连接。此外,它对测试环境使用了合理的默认值(但不应视为生产就绪)和配置的自动连接。
JGroups连接器的设置都以前缀axon.distributed.jgroups开始.
# the address to bind this instance to. By default, it attempts to find the
# Global IP address
axon.distributed.jgroups.bind-addr=GLOBAL
# the port to bind the local instance to
axon.distributed.jgroups.bind-port=7800
# the name of the JGroups Cluster to connect to
axon.distributed.jgroups.cluster-name=Axon
# the JGroups Configuration file to configure JGroups with
axon.distributed.jgroups.configuration-file=default_tcp_gossip.xml
# The IP and port of the Gossip Servers (comma separated) to connect to
axon.distributed.jgroups.gossip.hosts=localhost[12001]
# when true, will start an embedded Gossip Server on bound to the port of
# the first mentioned gossip host.
axon.distributed.jgroups.gossip.auto-start=false
18.3 Kafka
Kafka的扩展目前只有一个发布候选版本。因此,这个扩展或Axon框架的小版本可能包括对API的破坏性更改。
Apache kafka是一个非常流行的发布和消费事件的系统。它的体系结构与大多数消息传递系统根本不同,它将速度和可靠性结合在一起。
Axon提供了一个专用于发布和接收来自Kafka的事件消息的扩展。除了(默认的)Axon服务器之外,Kafka扩展应该被视为分发事件的另一种方法。
扩展的实现可以在这里找到。共享存储库还包含一个使用扩展的示例项目。
要使用来自Axon的Kafka扩展组件,请确保类路径上有Axon-Kafka模块。使用扩展需要按照项目的要求设置和配置Kafka。如何实现这一点不在本参考指南的范围之内,应该在卡夫卡的文档中找到。
请注意,Kafka是一个非常好的事件分发机制,但它并不适合事件存储。沿着这些思路,这个扩展只提供了通过Kafka分发Axon事件的方法。因此,扩展不能用于事件源聚合,因为这需要一个事件存储实现。因此,我们建议使用专门构建的事件存储,如Axon服务器,或者基于RDBMS(例如JPA或JDBC实现)。
- 向kafka发布事件
当事件消息发布到事件总线(或事件存储)时,可以使用KafkaPublisher将它们转发到Kafka主题。为了实现这一点,它将利用一个卡夫卡生产商,通过Axon的生产工厂回收。KafkaPublisher反过来从kafkaventpublisher接收要发布的事件。
由于KafkaEventPublisher在Axon术语中是一个事件消息处理程序,所以我们可以将它提供给任何事件处理器来接收发布的事件。事件处理器的选择给卡夫卡事件发布带来了不同的特点:
- 订阅事件处理器-向Kafka发布消息将在同一线程(和工作单元)中发生它将事件发布到事件总线。这种方法确保无法发布到Kafka将强制事件总线上的初始事件发布失败
- 跟踪事件处理器(Tracking Event Processor)-向Kafka发布消息是在不同的线程(和工作单元)中运行的。然后是将事件发布到事件总线的那个。这种方法确保事件已在事件总线上发布,而不管发布到Kafka是否有效
在设置事件发布时,还必须考虑使用哪种确认模式。ConfirmationMode影响实际生成关于Kafka主题的事件消息的过程,但也影响ProducerFactory将实例化什么类型的生产者:
- 事务性-这将要求生产者启动、提交和(在失败的情况下)回滚发布事件消息的事务。除此之外,它还将在ProducerFactory中创建一个Producer实例池,以避免连续创建新的,要求用户提供“事务性id前缀”来唯一地标识池中的每个生产者。
- 等待确认(WAIT_FOR_ACK)-设置“等待确认”,因为确认模式将要求生产者实例等待在确认事件消息发布之前,默认值为1秒(可在KafkaPublisher上配置)。除此之外,它还将从ProducerFactory中创建一个单独的、可共享的Producer实例。
- 无-这是默认模式,仅确保,来自ProducerFactory中的可共享生产者实例。
- 将事件发布配置为Kafka
将事件发布配置到Kafka是一个多步骤的过程,从ProducerFactory开始。Axon提供ProducerFactory的DefaultProducerFactory实现,它应该通过提供的De实例化faultProducerFactory.Builder.
构建器有一个硬要求,即Producer配置映射。映射包含用于Kafka Producer客户端的设置,例如Kafka实例位置。请查看Kafka文档中可能的设置及其值。
public class KafkaEventPublicationConfiguration {
// ...
public ProducerFactory<String, byte[]> producerFactory(Duration closeTimeout, int producerCacheSize,
Map<String, Object> producerConfiguration,
ConfirmationMode confirmationMode, String transactionIdPrefix) {
return DefaultProducerFactory.<String, byte[]>builder()
.closeTimeout(closeTimeout)
// Defaults to "30" seconds
.producerCacheSize(producerCacheSize)
// Defaults to "10"; only used for "TRANSACTIONAL" mode
.configuration(producerConfiguration)
// Hard requirement
.confirmationMode(confirmationMode)
// Defaults to a Confirmation Mode of "NONE"
.transactionalIdPrefix(transactionIdPrefix)
// Hard requirement when in "TRANSACTIONAL" mode
.build();
}
// ...
}
要引入的第二个基础设施组件是KafkaPublisher,它对ProducerFactory有严格的要求。此外,这里将是定义Kafka主题的地方,Axon事件消息将在此基础上发布。请注意,需要正确关闭KafkaPublisher,以确保正确关闭所有生产者实例。
public class KafkaEventPublicationConfiguration {
// ...
public KafkaPublisher<String, byte[]> kafkaPublisher(String topic,
ProducerFactory<String, byte[]> producerFactory,
KafkaMessageConverter<String, byte[]> kafkaMessageConverter,
int publisherAckTimeout) {
return KafkaPublisher.<String, byte[]>builder()
.topic(topic)
// Defaults to "Axon.Events"
.producerFactory(producerFactory)
// Hard requirement
.messageConverter(kafkaMessageConverter)
// Defaults to a "DefaultKafkaMessageConverter"
.publisherAckTimeout(publisherAckTimeout)
// Defaults to "1000" milliseconds; only used for "WAIT_FOR_ACK" mode
.build();
}
// ...
}
最后,我们需要将Axon的事件消息提供给Kafkap发布者。为此,应该通过builder模式实例化KafkaEventPublisher。记住将KafkaEventPublisher添加到您选择的事件处理器实现中。建议使用KafkaEventPublisher默认的“处理”组作为事件处理器的处理组名称,以将其与其他事件处理器区分开。
public class KafkaEventPublicationConfiguration {
// ...
public KafkaEventPublisher<String, byte[]> kafkaEventPublisher(KafkaPublisher<String, byte[]> kafkaPublisher) {
return KafkaEventPublisher.<String, byte[]>builder()
.kafkaPublisher(kafkaPublisher)
// Hard requirement
.build();
}
public void registerPublisherToEventProcessor(EventProcessingConfigurer eventProcessingConfigurer,
KafkaEventPublisher<String, byte[]> kafkaEventPublisher) {
String processingGroup = KafkaEventPublisher.DEFAULT_PROCESSING_GROUP;
eventProcessingConfigurer.registerEventHandler(configuration -> kafkaEventPublisher)
.assignHandlerTypesMatching(processingGroup,
clazz -> clazz.isAssignableFrom(KafkaEventPublisher.class) )
.registerSubscribingEventProcessor(processingGroup);
// Replace `registerSubscribingEventProcessor` for //`registerTrackingEventProcessor` to use a tracking processor
}
// ...
}
ii主题分区发布注意事项
Kafka确保消息在主题分区级别上排序,而不是在整个主题上。例如,为了控制将某个组的事件放置在专用分区中,可以使用消息转换器的SequencingPolicy。
事件发布的主题分区对也会对事件消耗产生影响。此扩展通过确保使用者始终接收主题的所有事件来执行完整的排序,从而减轻了可流化解决方案的任何排序问题。但是,当使用可预订事件消费方法时,不提供此保证。可预订流将所有的排序细节交给Kafka,这意味着事件应该发布在一致的分区上,以确保排序。
(二)卡夫卡消费事件
Axon应用程序中的事件消息可以通过订阅或跟踪事件处理器来使用。当涉及到从Kafka主题消费事件时,这两个选项都会被保留,从设置的角度来看,这两个选项分别转换为SubscribbleMessageSource或StreamableKafKamMessageSource,稍后将对这两个选项进行更详细的描述,因为我们首先阐明了通过Kafka在Axon中使用事件的一般要求。
这两种方法都使用类似的机制来轮询Kafka消费者的事件,该机制分解为ConsumerFactory和Fetcher的组合。扩展提供了一个DefaultConsumerFactory,其唯一的需求是配置属性的映射。映射包含用于Kafka消费者客户端的设置,例如Kafka实例位置。请查看Kafka文档中可能的设置及其值:
public class KafkaEventConsumptionConfiguration {
// ...
public ConsumerFactory<String, byte[]> consumerFactory(
Map<String, Object> consumerConfiguration) {
return new DefaultConsumerFactory<>(consumerConfiguration);
}
// ...
}
Fetcher实例的工作是通过定向它从消息源接收的消费者实例来从Kafka检索实际消息。为此,您可以起草自己的实现或使用提供的AsyncFetcher。不需要显式启动AsyncFetcher,因为它将对启动它的消息源做出反应。它确实需要关闭,以确保任何线程池或活动连接都已正确关闭。
public class KafkaEventConsumptionConfiguration {
// ...
public Fetcher<?, ?, ?> fetcher(long timeoutMillis,
ExecutorService executorService) {
return AsyncFetcher.builder()
.pollTimeout(timeoutMillis)
// Defaults to "5000" milliseconds
.executorService(executorService)
// Defaults to a cached thread pool executor
.build();
}
// ...
}
i使用可订阅消息源的事件
使用SubscribableKafkaMessageSource意味着您倾向于使用订阅事件处理器来使用事件处理程序中的事件。
在使用这个源代码时,使用了Kafka将消费者实例配对为“消费者组”的思想。通过使groupId-on-source构造成为一个硬性要求,这一点得到了加强。使用一个通用的groupId本质上意味着事件流工作负载可以按照Kafka的术语共享,而订阅eventprocessor通常会自动工作,而不管实例数是多少。工作负载共享可以通过具有相同groupId的多个应用程序实例或通过subscribablekafkameMessageSource的构建器调整使用者计数来实现。重置事件流也有同样的好处,在Axon中,事件流是由TrackingEventProcessor保留的,但现在通过Kafka自己的API打开了。
尽管SubscribableKafKamMessageSource因此提供了跟踪事件处理器通常提供的精确性,但它确实具有两个缺陷:
- Axon的SequencingPolicy方法,用于推断哪个线程接收到哪些事件将完全丢失。因此,它取决于为处理程序接收的事件将哪个主题分区对提供给使用者。从使用角度来看,这意味着Axon不再保证事件消息的排序。因此,用户的工作就是确保事件在正确的主题分区对中发布。
- 提供重置的API Axon完全丢失,因为此API只能通过TrackingEventProcessor#resetTokens操作正确触发
鉴于上述情况,建议用户了解卡夫卡在信息消费方面的具体情况。
当要将subscribableKafKamMessageSource配置为订阅EventProcessor的消息源时,除了创建和注册源之外,还有一个附加要求。源应该只在所有感兴趣的订阅事件处理器都订阅了它之后才开始轮询事件。要确保在配置生命周期的正确点调用SubscribableKafKafKamMessageSource#start()操作,应使用KafKafNameMessageSource配置器:
public class KafkaEventConsumptionConfiguration {
// ...
public KafkaMessageSourceConfigurer kafkaMessageSourceConfigurer(Configurer configurer) {
KafkaMessageSourceConfigurer kafkaMessageSourceConfigurer =
new KafkaMessageSourceConfigurer();
configurer.registerModule(kafkaMessageSourceConfigurer);
return kafkaMessageSourceConfigurer;
}
public SubscribableKafkaMessageSource<String, byte[]> subscribableKafkaMessageSource(List<String> topics, String groupId, ConsumerFactory<String, byte[]> consumerFactory,
Fetcher<String, byte[], EventMessage<?>> fetcher,
KafkaMessageConverter<String, byte[]> messageConverter,
int consumerCount,
KafkaMessageSourceConfigurer kafkaMessageSourceConfigurer) {
SubscribableKafkaMessageSource<String, byte[]> subscribableKafkaMessageSource
= SubscribableKafkaMessageSource.<String, byte[]>builder()
.topics(topics)
// Defaults to a collection of "Axon.Events"
.groupId(groupId)
// Hard requirement
.consumerFactory(consumerFactory)
// Hard requirement
.fetcher(fetcher)
// Hard requirement
.messageConverter(messageConverter)
// Defaults to a "DefaultKafkaMessageConverter"
.consumerCount(consumerCount)
// Defaults to a single Consumer
.build();
// Registering the source is required to tie into the Configurers lifecycle to start //the source at the right stage
kafkaMessageSourceConfigurer.registerSubscribableSource(configuration -> subscribableKafkaMessageSource);
return subscribableKafkaMessageSource;
}
public void configureSubscribableKafkaSource(EventProcessingConfigurer eventProcessingConfigurer, String processorName,
SubscribableKafkaMessageSource<String, byte[]> subscribableKafkaMessageSource) {
eventProcessingConfigurer.registerSubscribingEventProcessor(
processorName, configuration -> subscribableKafkaMessageSource
);
}
// ...
}
kafkamessagesourceconfig是一个与应用程序的开始和结束生命周期相关的Axon模块配置。它应该接收subscribableKafKamMessageSource作为启动和停止的源。KafKamMessageSourceConfigure实例本身应该作为模块注册到主配置程序。
如果只有一个订阅事件处理器将订阅kafka消息源,SubscribabLekafKamMessageSource.Builder自动启动()可以打开。这将在第一次订阅时启动SubscribableKafKamMessageSource。
ii使用流式消息源处理事件
使用StreamableKafkaMessageSource意味着您倾向于使用TrackingEventProcessor来使用事件处理程序中的事件。
由于可订阅kafka消息源使用kafka的思想,即通过同一“消费者组”中的多个消费者实例共享工作负载,因此可流化方法为每个消费者实例强制实施一个唯一的消费者组。Axon需要唯一标识的使用者组/使用者对,以(1)确保事件顺序,(2)确保每个实例/线程在并行处理期间接收到事件流的正确部分。不同的组id是由StreamableKafkaMessageSource通过groupIdPrefix和groupdIdSuffixFactory派生的,它们可以通过源代码的生成器进行调整。
public class KafkaEventConsumptionConfiguration {
// ...
public StreamableKafkaMessageSource<String, byte[]> streamableKafkaMessageSource(List<String> topics,
String groupIdPrefix, Supplier<String> groupIdSuffixFactory,
ConsumerFactory<String, byte[]> consumerFactory,
Fetcher<String, byte[], KafkaEventMessage> fetcher,
KafkaMessageConverter<String, byte[]> messageConverter,int bufferCapacity) {
return StreamableKafkaMessageSource.<String, byte[]>builder()
.topics(topics) // Defaults to a collection of "Axon.Events"
.groupIdPrefix(groupIdPrefix) // Defaults to "Axon.Streamable.Consumer-"
.groupIdSuffixFactory(groupIdSuffixFactory) // Defaults to a random UUID
.consumerFactory(consumerFactory) // Hard requirement
.fetcher(fetcher) // Hard requirement
.messageConverter(messageConverter) // Defaults to a "DefaultKafkaMessageConverter"
.bufferFactory(
() -> new SortedKafkaMessageBuffer<>(bufferCapacity)) .build();
// Defaults to a "SortedKafkaMessageBuffer" with a buffer capacity of "1000"
}
public void configureStreamableKafkaSource(EventProcessingConfigurer eventProcessingConfigurer, String processorName,
StreamableKafkaMessageSource<String, byte[]> streamableKafkaMessageSource) {
eventProcessingConfigurer.registerTrackingEventProcessor(
processorName,
configuration -> streamableKafkaMessageSource
);
}
// ...
}
请注意,与任何跟踪事件处理器一样,事件流的进度存储在TrackingToken中。使用StreamableKafkaMessageSource意味着包含topic partition to offset pairs的kafkatracktoken存储在TokenStore中。
(三)自定义事件消息格式
在前面的章节中,KafkaMessageConverter<K,V>已经被显示为事件生产和消费的一个要求。K是消息密钥的格式,其中V代表消息的值。扩展提供了一个defaultKafKamMessageConverter,它将axon EventMessage转换为Kafka ProducerRecord,并将ConsumerRecord转换回EventMessage。此DefaultKafKamMessageConverter使用String作为键,byte[]作为要反序列化的消息的值。
尽管是默认的,这个实现允许一些定制,比如EventMessage的元数据如何映射到Kafka头。这是通过在DefaultKafKamMessageConverter的构建器中调整“头值映射器”来实现的。
可以调整SequencingPolicy来更改正在使用的记录键的行为。默认的排序策略是SequentialPerAggregatePolicy,这将导致事件的聚合标识符成为ProducerRecord和ConsumerRecord的键。
最后,可以调整转换器使用的序列化程序。有关这方面的详细信息,请参阅序列化程序部分。
public class KafkaMessageConversationConfiguration {
// ...
public KafkaMessageConverter<String, byte[]> kafkaMessageConverter(Serializer serializer,
SequencingPolicy<? super EventMessage<?>> sequencingPolicy,
BiFunction<String, Object, RecordHeader> headerValueMapper) {
return DefaultKafkaMessageConverter.builder()
.serializer(serializer)
// Hard requirement
.sequencingPolicy(sequencingPolicy)
// Defaults to a "SequentialPerAggregatePolicy"
.headerValueMapper(headerValueMapper)
// Defaults to "HeaderUtils#byteMapper()"
.build();
}
// ...
}
确保在生产端和消费端使用相同的KafkaMessageConverter,否则反序列化时会出现异常。
(四)springboot配置
可以使用组id org.axonframework.extensions.kafka和项目ID axon-kafka-spring-boot-starter将此扩展作为springboot starter依赖项添加到项目中。使用自动配置时,将自动为您创建以下组件:
- 通用组件:
使用配置的eventSerializer(默认为XStreamSerializer)的DefaultKafKamMessageConverter。对键使用字符串,对记录的值使用byte[]
- 生产者组件:
使用字符串作为键和字节[]作为记录值的DefaultProducerFactory。这将在确认模式“NONE”下创建一个ProducerFactory,正如这里指定的那样。
axon.kafka.publisher.confirmation-mode应调整更改,当“事务”模式需要axon.kafka.producer.transaction-id-prefix属性的时候
如果axon.kafka.producer.transaction-id-prefix为非null且非空,那么要求调整为“事务性”确认模式。
使用ProducerFactory中的Producer实例将事件发布到配置的Kafka主题。
KafkaEventPublisher用于向KafKaPublisher提供事件并分配处理器名称,处理组称为“uuaxon-kafka-event-publishing-group”。默认为SubscribingEventProcessor
。
如果需要TrackingEventProcessor,则axon.kafka.producer.event-processor-mode模式应设置为跟踪。
- 消费者组件:
一个DefaultConsumerFactory,使用字符串作为键,使用byte[]作为记录的值异步取数器。若要调整回迁程序的轮询超时,则可以设置axon.kafka.fetcher.poll-timeout。
可用于跟踪EventProcessor实例的StreamableKafKafNameMessageSource
当使用springboot自动配置时,请注意提供应用程序.属性文件。Kafka扩展配置细节应该放在前缀下面卡夫卡轴突. 在这个级别上,bootstrapServer(默认为本地主机:9092)并且可以定义生产和消费端使用的默认主题。
DefaultProducerFactory和DefaultConsumerFactory需要配置属性的映射,这些属性分别对应于Kafka Producer和Consumer特定的属性。因此,轴突本身传递这些属性而不直接使用它们本身。这个应用程序.属性文件在axon.kafka制作人. 以及卡夫卡消费者. 前缀。如果要查找的特性未在AxonKafkaProperties文件中预定义,则始终可以在地图样式中引入特性。
# This is a sample properties file to configure the Kafka Extension
axon:
kafka:
bootstrap-servers: localhost:9092
client-id: kafka-axon-example
default-topic: local.event
properties:
security.protocol: PLAINTEXT
publisher:
confirmation-mode: transactional
producer:
transaction-id-prefix: kafka-sample
retries: 0
event-processor-mode: subscribing
# For additional unnamed properties, add them to the `properties` map like so
properties:
some-key: [some-value]
fetcher:
poll-timeout: 3000
consumer:
enable-auto-commit: true
auto-commit-interval: 3000
event-processor-mode: tracking
# For additional unnamed properties, add them to the `properties` map like so
properties:
some-key: [some-value]
自动配置SubscribableKafKamMessageSource
可以通过设置卡夫卡消费者。事件处理模式为订阅。
请注意,这不会立即为您创建SubscribableKafKafNameMessageSource。要设置可订阅消息,建议阅读本节。
18.4 kotlin
Kotlin是一种与Java和JVM完全互操作的编程语言。由于Axon是用Java编写的,所以它也可以与Kotlin结合使用,在使用框架时提供了不同的感觉。
Axon的一些API在Java中工作得非常好,但是在转换到Kotlin时会有一种相当尴尬的感觉。Kotlin扩展的目标是通过提供Axon API的内联和具体化方法来消除这种尴尬。
目前给出了几种解决方案,这些方案大致可以分为Axon使用的不同类型的消息。因此,在这个页面上提供了一个命令、事件和查询部分。
测试发布
目前,Kotlin扩展已经在实验中发布(例如0.1.0版)。这意味着在发布完整版本(例如1.0.0版)之前,所有的实现都会发生变化。
因目前该扩展处于开发中,故暂不翻译,可自行查阅:https://docs.axoniq.io/reference-guide/extensions/kotlin
18.5 Mongo
MongoEventStorageEngine有一个@PostConstruct注释方法,称为ensureIndexes,它将生成正确操作所需的索引。这意味着,在自动调用@PostConstruct处理程序的容器中运行时,将在创建事件存储时创建“聚合标识符”和“事件序列号”所需的唯一索引。
请注意,查询优化和更新速度之间总是有平衡的。负载测试最终是发现哪些索引提供最佳性能的最佳方法。
- 正常运行使用
在domain events(默认名称:“domainevents”)集合中的“aggregateIdentifier”、“type”和“sequenceNumber”上自动创建索引。此外,在domainevents(默认名称:“domainevents”)集合上配置“timestamp”和“sequenceNumber”的非唯一索引,用于跟踪事件处理器。
- 快照
“aggregateIdentifier”和“sequenceNumber”的(唯一)索引将自动在快照事件(默认名称:“snapshotevents”)集合中创建。
- Saga
在saga(默认名称:“sagas”)集合中的“sagadentifier”上放置一个(唯一)索引。把索引放在“sagaType”上关联.key“和”关联.值“saga(默认名称:“sagas”)集合中的属性。
在前AxonFramework3版本中,我们发现MongoDb非常适合作为事件存储。然而,随着跟踪事件处理器的引入以及它们如何跟踪事件,我们在Mongo事件存储实现方面遇到了一些效率低下的问题。我们建议使用专门构建的事件存储,比如Axon服务器,或者基于RDBMS的(例如JPA或JDBC实现),并且只有当您发现Mongo的性能对您的应用程序有益时,才建议在这个用例中使用Mongo。
Springboot的配置
// The Event store `EmbeddedEventStore` delegates actual storage and retrieval //of events to an `EventStorageEngine`.
@Bean
public EmbeddedEventStore eventStore(EventStorageEngine storageEngine, AxonConfiguration configuration) {
return EmbeddedEventStore.builder()
.storageEngine(storageEngine)
.messageMonitor(configuration.messageMonitor(EventStore.class, "eventStore"))
.build();
}
// The `MongoEventStorageEngine` stores each event in a separate MongoDB //document
@Bean
public EventStorageEngine storageEngine(MongoClient client) {
return MongoEventStorageEngine.builder().mongoTemplate(DefaultMongoTemplate.builder().mongoDatabase(client).build()).build();
}
18.6 Reactor
纵观Axon框架体系结构,您可以注意到,通常使用该框架的系统是“消息驱动的”、“响应的”和“弹性的”。根据反应性宣言,反应性系统一般也是如此。
虽然我们可以说axon框架是一种反应系统,但我们不能说它是完全反应的。
反应式编程是一种编写包含异步I/O的软件的方法。异步I/O是一个预示着软件发生重大变化的小想法。这个想法很简单:通过回收在等待I/O活动时闲置的资源来缓解资源利用效率低下的问题。异步I/O颠倒了正常的设计I/O处理:客户机收到新数据的通知,而不是请求它,这使客户机在等待这些通知时可以自由地执行其他工作。
从本质上讲,反应式API和Axon非常适合,因为框架的大多数操作是异步和非阻塞的。因此,为此提供一个专用的扩展是一个合乎逻辑的步骤。为此,我们选择使用Pivotal的项目反应堆来建造这个扩建项目。Reactor构建在Reactive Streams规范之上,是Java企业和Spring应用程序的事实标准。因此,我们认为它是一个非常适合提供一个延伸,使Axon更具反应性。
并不是所有的Axon组件都提供反应式API。我们将逐步为这个扩展引入更多的“反应性”,优先考虑那些用户可以受益最大的组件。
要使用Axon Reactor扩展,请确保类路径上的Axon Reactor模块可用。
18.6.1 Reactor网关
“反应式网关”提供了一个围绕命令、查询和事件总线的反应式API包装器。大多数操作类似于非反应式网关的操作,只是用Mono或Flux替换CompletableFuture。在某些情况下,API被扩展以便于使用常见的反应模式。
Reactor不允许流中出现空值。从处理程序返回的任何null值都将映射到Mono#empty()。
正在重试操作
所有操作都支持反应堆的重试机制:
reactiveQueryGateway.query(查询,响应类型.class).重试(5);
此调用将在查询失败时重试发送最多五次查询。
springboot配置
可以使用组id将此扩展作为springbootstarter依赖项添加到项目中org.axonframework.extensions反应器和工件id轴突反应器弹簧启动启动器。扩展的实现可以在这里找到。
Reactor命令网关
本节介绍ReactorCommandGateway上的方法。
send-调用方订阅命令结果后发送给定命令。立即返回。
一个常见的模式是使用rest api发送命令。在这种情况下,建议使用WebFlux,并将命令result Mono直接返回给控制器:
class SpringCommandController {
private final ReactorCommandGateway reactiveCommandGateway;
@PostMapping
public Mono<CommandHandlerResponseBody> sendCommand(@RequestBody CommandBody command) {
return reactiveCommandGateway.send(command);
}
}
从springwebflux控制器发送命令。
如果命令处理函数返回void类型,Mono<commandhandleresponsebody>应替换为Mono<void>
另一个常见的模式是“发送并忘记”:
class CommandDispatcher {
private final ReactorCommandGateway reactiveCommandGateway;
public void sendAndForget(MyCommand command) {
reactiveCommandGateway.send(command)
.subscribe();
}
}
函数发送命令并立即返回而不等待结果。
sendAll-此方法使用给定的命令发布者来分派传入的命令。
此操作仅在reactor扩建中可用。使用它来连接传递命令的第三方流。
class CommandPublisher {
private final ReactorCommandGateway reactiveCommandGateway;
@PostConstruct
public void startReceivingCommands(Flux<CommandBody> inputStream) {
reactiveCommandGateway.sendAll(inputStream)
.subscribe();
}
}
将外部输入流直接连接到Reactor命令网关。
sendAll操作将一直发送命令,直到取消输入流。
send和sendAll还没有提供任何背压。唯一合适的背压机制是命令将按顺序发送;因此,一旦上一个命令的结果到达。命令的数量从传入流中预取,并存储在缓冲区中以供发送(请参阅Flux#concatMap)。这会减慢发送速度,但不能保证如果命令发送得太快,订阅者不会被命令淹没。
reactor查询网关
query-发送给定的查询,期望从单个源返回responseType形式的响应。
class SpringQueryController {
private final ReactorQueryGateway reactiveQueryGateway;
// The query's Mono is returned to the Spring controller. Subscribe //control is given to Spring Framework.
@GetMapping
public Mono<SomeResponseType> findAll(FindAllQuery query, Class<SomeResponseType> responseType) {
return reactiveQueryGateway.query(query, responseType);
}
}
在springrest控制器中使用Reactor查询网关的推荐方法。
scatterGather-发送给定的查询,期望在指定的持续时间内从多个源以responseType的形式返回响应。
class SpringQueryController {
private final ReactorQueryGateway reactiveQueryGateway;
@GetMapping
public Flux<SomeResponseType> findMany(FindManyQuery query) {
return reactiveQueryGateway.scatterGather(query, SomeResponseType.class, Duration.ofSeconds(5)).take(3);
}
}
发送一个给定的查询,该查询在收到三个结果或5秒后停止。
订阅查询
首先,Axon中用于订阅查询的Reactor API并不是什么新鲜事。
然而,我们注意到一些常用的模式,例如:
- 在单个流中连接初始结果和查询更新,或
- 一起跳过初始结果。
因此,Reactor扩展提供了几种方法来简化这些常见模式的使用。
subscriptionQuery-发送给定的查询,返回初始结果并保持流式增量更新,直到订户取消订阅流。
请注意,当初始结果的响应类型与增量更新匹配时,应使用此方法。
Flux<ResultType> resultFlux = reactiveQueryGateway.subscriptionQuery("criteriaQuery", ResultType.class);
通过ReactorQueryGateway进行的上述调用相当于:
class SubscriptionQuerySender {
private final ReactorQueryGateway reactiveQueryGateway;
public Flux<SomeResponseType> sendSubscriptionQuery(SomeQuery query, Class<SomeResponseType> responseType) {
return reactiveQueryGateway.subscriptionQuery(query, responseType, responseType).flatMapMany(result -> result.initialResult()
.concatWith(result.updates())
.doFinally(signal -> result.close()));
}
}
subscriptionQueryMany-发送给定的查询,返回初始结果并保持流式增量更新,直到订户取消订阅流。
当初始结果包含需要展平的响应类型的多个实例时,应使用此操作。此外,初始响应和增量更新的响应类型需要匹配。
Flux<ResultType> resultFlux = reactiveQueryGateway.subscriptionQueryMany("criteriaQuery", ResultType.class);
通过ReactorQueryGateway进行的上述调用相当于:
class SubscriptionQuerySender {
private final ReactorQueryGateway reactiveQueryGateway;
public Flux<SomeResponseType> sendSubscriptionQuery(SomeQuery query, Class<SomeResponseType> responseType) {
return reactiveQueryGateway.subscriptionQuery(query, ResponseTypes.multipleInstancesOf(responseType),
ResponseTypes.instanceOf(responseType))
.flatMapMany(result -> result.initialResult()
.flatMapMany(Flux::fromIterable)
.concatWith(result.updates())
.doFinally(signal -> result.close()));
}
}
queryUpdates-发送给定的查询并流式传输增量更新,直到订户取消订阅流。
当订阅服务器只对更新感兴趣时,可以使用此方法。
Flux<ResultType> updatesOnly = reactiveQueryGateway.queryUpdates("criteriaQuery", ResultType.class);
通过ReactorQueryGateway进行的上述调用相当于:
class SubscriptionQuerySender {
private final ReactorQueryGateway reactiveQueryGateway;
public Flux<SomeResponseType> sendSubscriptionQuery(SomeQuery query, Class<SomeResponseType> responseType) {
return reactiveQueryGateway.subscriptionQuery(query, ResponseTypes.instanceOf(Void.class), responseType)
.flatMapMany(result -> result.updates()
.doFinally(signal -> result.close()));
}
}
在上面显示的方法中,订阅查询在订阅服务器从Flux取消订阅后自动关闭。但是,使用常规QueryGateway时,需要手动关闭订阅查询。
reactor事件网关
事件网关的反应性变化。提供对无功返回类型(如磁通)的支持。
发布-一旦调用者订阅结果流,发布给定的事件。
此方法返回已发布的事件。请注意,返回的事件可能与用户发布的事件不同,只要注册了修改事件的拦截器。
class EventPublisher {
private final ReactorEventGateway reactiveEventGateway;
// Register a dispatch interceptor to modify the event messages
public EventPublisher() {
reactiveEventGateway.registerDispatchInterceptor(
eventMono -> eventMono.map(event -> GenericEventMessage.asEventMessage("intercepted" + event.getPayload()))
);
}
public void publishEvent() {
Flux<Object> result = reactiveEventGateway.publish("event");
}
}
dispatcher修改事件的示例,作为结果通量返回给用户。
拦截器
Axon提供了拦截器的概念。Reactor网关允许类似的拦截器逻辑,即ReactorMessageDispatchInterceptor和ReactorResultHandlerInterceptor。
这些拦截器允许我们集中定义将应用于消息流的规则和过滤器。
拦截器将按注册到给定组件的顺序应用。
反应堆调度拦截器
应使用ReactorMessageDispatchInterceptor集中应用规则和验证传出消息。请注意,ReactorMessageDispatchInterceptor是整个框架中使用的默认MessageDispatchInterceptor接口的实现。具体实现如下:
@FunctionalInterface
public interface ReactorMessageDispatchInterceptor<M extends Message<?>> extends MessageDispatchInterceptor<M> {
Mono<M> intercept(Mono<M> message);
@Override
default BiFunction<Integer, M, M> handle(List<? extends M> messages) {
return (position, message) -> intercept(Mono.just(message)).block();
}
}
因此,它默认为MessageDispatchInterceptor#handle(List<? extends M>方法以利用ReactorMessageDispatchInterceptor#intercept(Mono<M>)方法。因此,ReactorMessageDispatchInterceptor也可以配置在普通Axon网关上。以下是如何使用消息分派拦截器的两个示例:
class ReactorConfiguration {
public void registerDispatchInterceptor(ReactorCommandGateway reactiveGateway) {
reactiveGateway.registerDispatchInterceptor(
msgMono -> msgMono.map(msg -> msg.andMetaData(Collections.singletonMap("key1", "value1")))
);
}
}
分派拦截器,它将密钥值对添加到消息的元数据中。
class ReactorConfiguration {
public void registerDispatchInterceptor(ReactorEventGateway reactiveGateway) {
reactiveGateway.registerDispatchInterceptor(
msgMono -> msgMono.filterWhen(v -> Mono.subscriberContext()
.filter(ctx-> ctx.hasKey("security"))
.map(ctx->ctx.get("security")))
);
}
}
基于Reactor上下文中的安全标志丢弃消息的分派拦截器。
reactor结果处理器拦截器
ReactorResultHandlerInterceptor应用于集中应用规则和验证传入消息,即所谓的结果。具体实现如下:
@FunctionalInterface
public interface ReactorResultHandlerInterceptor<M extends Message<?>, R extends ResultMessage<?>> {
Flux<R> intercept(M message, Flux<R> results);
}
参数是已发送的消息,以及将被拦截的消息的结果流。如果要将给定的结果规则仅应用于特定消息,则message参数非常有用。以下是如何使用消息结果拦截器的一些示例:
这种类型的拦截器只能在反应堆扩建中使用。
class ReactorConfiguration {
public void registerResultInterceptor(ReactorQueryGateway reactiveGateway) {
reactiveGateway.registerResultHandlerInterceptor(
(msg, results) -> results.filter(r -> !r.getPayload().equals("blockedPayload"))
);
}
}
结果拦截器,它丢弃具有与blockedPayload匹配的负载的所有结果
class ReactorConfiguration {
public void registerResultInterceptor(ReactorQueryGateway reactiveGateway) {
reactiveQueryGateway.registerResultHandlerInterceptor(
(query, results) -> results.flatMap(r -> {
if (r.getPayload().equals("")) {
return Flux.<ResultMessage<?>>error(new RuntimeException("no empty strings allowed"));
} else {
return Flux.just(r);
}
})
);
}
}
结果拦截器,用于验证查询结果不包含空字符串。
class ReactorConfiguration {
public void registerResultInterceptor(ReactorQueryGateway reactiveGateway) {
reactiveQueryGateway.registerResultHandlerInterceptor(
(q, results) -> results.filter(it -> !((boolean) q.getQueryName().equals("myBlockedQuery")))
);
}
}
结果拦截器,丢弃queryName与myBlockedQuery匹配的所有结果
class ReactorConfiguration {
public void registerResultInterceptor(ReactorCommandGateway reactiveGateway) {
reactiveGateway.registerResultHandlerInterceptor(
(msg,results) -> results.timeout(Duration.ofSeconds(30))
);
}
}
结果拦截器,它将每个消息的结果等待时间限制为30秒。
class ReactorConfiguration {
public void registerResultInterceptor(ReactorCommandGateway reactiveGateway) {
reactiveGateway.registerResultHandlerInterceptor(
(msg,results) -> results.log().take(5)
);
}
}
结果拦截器,它将结果的数量限制为5个条目,并记录所有结果。
18.7 Springcloud
除了默认的Axon服务器之外,springcloud是一种分发命令总线(commands)的替代方法。
Springcloud connector设置使用springcloud描述的服务注册和发现机制来分发命令总线。因此,您可以自由选择使用哪个springcloud实现来分发命令。示例实现是Eureka Discovery/Eureka服务器组合。
要使用来自Axon的springcloud组件,请确保类路径上有axonspringcloud模块。
注意
SpringCloudCommandRouter使用特定于springcloud的ServiceInstance.Metadata用于通知系统中所有节点其消息路由信息的字段。因此,选择的springcloud实现支持ServiceInstance.Metadata字段。如果所需的springcloud实现不支持ServiceInstance.MetadataSpringCloudHttpBackupCommandRouter是一个可行的解决方案。有关SpringCloudHttpBackupCommandRouter的配置细节,请参阅本章末尾。
描述每一个springcloud实现将把这个参考指南推到很深的地方。因此,我们参考他们各自的文件以了解更多信息。
springcloudconnector设置是SpringCloudCommandRouter和springhttpcommandbussconnector的组合,它们分别填充了CommandRouter和commandbussconnector的位置。
注意
使用SpringCloudCommandRouter时,请确保Spring应用程序启用了heartbeat事件。该实现利用springcloud应用程序发布的心跳事件来检查它对其他所有节点的知识是否是最新的。如果heartbeat事件被禁用,那么集群中的大多数Axon应用程序将不知道整个设置。这将导致正确的命令路由问题。
SpringCloudCommandRouter必须通过提供以下内容来创建:
- 一个DiscoveryClient类型的“DiscoveryClient”可以通过用@EnableDiscoveryClient注释springboot应用程序来提供,它将在类路径上寻找Spring云实现。
- RoutingStrategy类型的“路由策略”——axon消息传递模块目前提供了几种实现,但函数调用也可以满足要求。
例如,如果要基于“聚合标识符”路由命令,则可以使用AnnotationRoutingStrategy并用@targetaggregatieIdentifier在有效负载上注释标识聚合的字段。
- 一个注册类型的“本地服务实例”——如果你是Spring Boot应用程序,它会自动创建一个引用实例本身的注册bean。
SpringCloudCommandRouter的其他可选参数包括:
- 类型为Predicate<ServiceInstance>的“服务实例筛选器”-此谓词用于筛选出DiscoveryClient可能会遇到的服务实例,您知道这些实例不会处理任何命令消息。如果您在springclouddiscovery服务中设置了几个不想在命令处理中考虑的服务,那么这可能会很有用。
- ConsistentHashChangeListener类型的“一致哈希更改侦听器”-添加一致的哈希更改侦听器可以在已知命令处理程序集中添加新成员时执行特定任务。
SpringHttpCommandBusConnector需要三个参数才能创建:
- CommandBus类型的“本地命令总线”——这是将命令发送到本地JVM的命令总线实现。这些命令可能是由其他jvm上的实例或本地jvm上的实例调度的。
- RestOperations对象,用于将命令消息发布到另一个实例。
- 最后是serializer类型的“serializer”-该序列化程序用于在命令消息通过网络发送之前对其进行序列化。
注意
分布式commandbus的springcloud连接器特定组件可以在axon分布式commandbus springcloud模块中找到。
SpringCloudCommandRouter和SpringHttpCommandBusConnector应该同时用于创建分布式CommandBus。在SpringJava配置中,如下所示:
// Simple Spring Boot App providing the `DiscoveryClient` bean
@EnableDiscoveryClient
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
// Example function providing a Spring Cloud Connector
@Bean
public CommandRouter springCloudCommandRouter(DiscoveryClient discoveryClient, Registration localServiceInstance) {
return SpringCloudCommandRouter.builder()
.discoveryClient(discoveryClient)
.routingStrategy(new AnnotationRoutingStrategy())
.localServiceInstance(localServiceInstance)
.build();
}
@Bean
public CommandBusConnector springHttpCommandBusConnector(
@Qualifier("localSegment") CommandBus localSegment,
RestOperations restOperations,
Serializer serializer) {
return SpringHttpCommandBusConnector.builder()
.localCommandBus(localSegment)
.restOperations(restOperations)
.serializer(serializer)
.build();
}
//@Primary to make sure this CommandBus implementation is used //for autowiring
@Bean
public DistributedCommandBus springCloudDistributedCommandBus(
CommandRouter commandRouter,
CommandBusConnector commandBusConnector) {
return DistributedCommandBus.builder()
.commandRouter(commandRouter)
.connector(commandBusConnector)
.build();
}
}
// if you don't use Spring Boot Autoconfiguration, you will need to explicitly // define the local segment:
@Bean
@Qualifier("localSegment")
public CommandBus localSegment() {
return SimpleCommandBus.builder().build();
}
注意
请注意,并不要求所有段都有相同类型命令的命令处理程序。对于不同的命令类型,可以使用不同的段。分布式命令总线将始终选择一个节点来发送命令,该节点支持该特定类型的命令。
- Spring云Http备份命令路由器
在内部,SpringCloudCommandRouter使用springcloudserviceinstance中包含的元数据映射来在整个分布式Axon环境中传递允许的消息路由信息。但是,如果所需的springcloud实现不允许修改ServiceInstance.Metadata字段(例如consur),可以选择实例化SpringCloudHttpBackupCommandRouter而不是SpringCloudCommandRouter。
顾名思义,SpringCloudHttpBackupCommandRouter有一个备份机制,如果ServiceInstance.Metadata字段不包含预期的消息路由信息。该备份机制提供一个HTTP端点,可以从该端点检索消息路由信息,同时添加查询集群中其他已知节点的端点以检索其消息路由信息的功能。因此,备份机制充当Spring控制器,在指定端点接收请求,并使用restemplate将请求发送到指定端点上的其他节点。
要使用SpringCloudHttpBackupCommandRouter而不是SpringCloudCommandRouter,请添加以下springjava配置(它将替换前面示例中的SpringCloudCommandRouter方法):
@Configuration
public class MyApplicationConfiguration {
@Bean
public CommandRouter springCloudHttpBackupCommandRouter(
DiscoveryClient discoveryClient,
RestTemplate restTemplate,
Registration localServiceInstance,
@Value("${axon.distributed.spring-cloud.fallback-url}")
String messageRoutingInformationEndpoint) {
return SpringCloudHttpBackupCommandRouter.builder()
.discoveryClient(discoveryClient)
.routingStrategy(new AnnotationRoutingStrategy())
.restTemplate(restTemplate)
.localServiceInstance(localServiceInstance)
.messageRoutingInformationEndpoint(messageRoutingInformationEndpoint)
.build();
}
}
- Springboot配置
springcloud自动配置没有太多要配置的。它使用现有的springclouddiscoveryclient(因此请确保使用了@enabledescoveryclient,并且所需的客户机位于类路径上)。
但是,有时发现客户端无法在服务器上动态更新实例元数据。如果Axon检测到这一点,它将自动返回到使用HTTP查询该节点。这在每个discovery心跳时执行一次(通常为30秒)。这种做法可以使用appplication.properties中的以下设置配置或禁用:
# whether to fall back to http when no meta-data is available
axon.distributed.spring-cloud.fallback-to-http-get=true
# the URL on which to publish local data and retrieve from other nodes.
axon.distributed.spring-cloud.fallback-url=/message-routing-information
18.8 Tracing
这个扩展通过提供CommandGateway、QueryGateway、MessageDispatchInterceptor和MessageHandlerInterceptor的特定实现,来提供跟踪通过Axon应用程序的命令、事件和查询消息的功能。开放跟踪标准用于提供跟踪功能,因此允许使用多个开放跟踪实现。
通过这种检测,我们可以链接同步和异步命令和查询,它们都属于同一个父span。当一个请求一起运行或分解并部署为单独的部分(分布式)时,可以在Axon客户端、命令处理程序、查询处理程序和事件处理程序之间可视化和分析请求。
<dependency>
<groupId>org.axonframework.extensions.tracing</groupId>
<artifactId>axon-tracing-spring-boot-starter</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
<version>3.2.2</version>
</dependency>
第一个依赖项是用于Axon跟踪扩展的springbootstarter,它是扩展配置中最快的启动。
第二个依赖项是用于OpenTracing的Jaeger实现。
还可以使用其他受支持的跟踪程序:LightStep、Instana、Apache SkyWalking、Datadog、VMware的Wavefront、elasticapm等等。
配置扩展
可以通过将axon.extension.tracing.enabled 属性设置为false禁用扩展(默认值为true)。这将使您有可能在需要时关闭它(例如,对于特定环境)。
此外,还有一个更细粒度的配置选项,用于跟踪命令、事件和查询上的span标记。您可以轻松定制span标记,在可用标记messageId、aggregateId、messageType、payloadType、messageName和payload之间混合和匹配。考虑到某些标记在某个范围类型上有意义,但在另一个类型上没有意义,而且其中一些标记在网络上有隐藏的成本(例如有效负载)。明智地使用它们!
axon.extension.tracing.span.commandTags=messageId, messageType, payloadType, messageName
axon.extension.tracing.span.eventTags=messageId, aggregateId, messageType, payloadType
axon.extension.tracing.span.queryTags=messageId, messageType, payloadType, messageName
上面是默认值的示例。“可用标记”字段在MessageTag.java 类中列出。