[英]Spring Cloud Stream Kafka Streams Binder 3.x: No output to the second output topic in case of multiple output bindings
[英]Spring cloud streams - multiple routing functions in multimodule project creates output bindings
我們有一個需要多個路由功能的多模塊項目(我們的案例是我們正在開發模塊化單體應用程序,我們需要在多個模塊中使用一條消息)。 問題是路由函數在消息代理中創建 *-out-0 主題,但在我們的例子中,我們只路由到消費者(而不是其他函數)。 文檔說
我們從不為
RoutingFunction
創建輸出綁定,只有輸入。 因此,當您路由到Consumer
時,RoutingFunction
通過沒有任何輸出綁定有效地成為Consumer
。 但是,如果RoutingFunction
碰巧路由到另一個產生輸出的Function
,則 RoutingFunction 的輸出綁定將動態創建,RoutingFunction
RoutingFunction
作為一個常規Function
來處理綁定(具有輸入和輸出綁定)
( https://github.com/spring-cloud/spring-cloud-stream/commit/b2b95dc7d292578c56ae42b066f437b0f1436e65 )
在上面的提交中,您可以看到在 FunctionConfiguration 的第 806 行添加了 if 語句條件,它檢查 functionDefinition 是否具有默認函數名稱( functionRouter
)。
我們在 3.2.4 版本中使用 spring-cloud-stream
在消息的有效載荷中,我們有自定義標頭,它告訴有效載荷中對象的類型(例如 DocumentCreatedEvent、DocumentFinishedEvent)和消費者:
class DocumentCreatedEventConsumer implements Consumer<DocumentCreatedEvent> {
@Override
void accept(final DocumentCreatedEvent event) {
// do something here...
}
}
class DocumentFinishedEventConsumer implements Consumer<DocumentFinishedEvent> {
@Override
void accept(final DocumentFinishedEvent event) {
// do something here...
}
}
注冊為 beans:
@Bean
Consumer<DocumentCreatedEvent> firstModuleDocumentCreatedEventConsumer() {
return new DocumentCreatedEventConsumer();
}
@Bean
Consumer<DocumentCreatedEvent> secondModuleDocumentCreatedEventConsumer() {
return new DocumentCreatedEventConsumer();
}
@Bean
Consumer<DocumentFinishedEvent> firstModuleDocumentFinishedEventConsumer() {
return new DocumentFinishedEventConsumer();
}
@Bean
Consumer<DocumentFinishedEvent> secondModuleDocumentFinishedEventConsumer() {
return new DocumentFinishedEventConsumer();
}
我們的 yaml 片段如下:
spring:
cloud:
function:
autodetect: false
routing.enabled: true
definition: firstModuleRouter;secondModuleRouter
stream:
bindings:
firstModuleRouter-in-0:
destination: output-topic
secondModuleRouter-in-0:
destination: output-topic
我們注冊路由函數的 bean:
@Slf4j
@Configuration
@RequiredArgsConstructor
class RegisterMessageBrokerRouters {
private final ConfigurableListableBeanFactory configurableListableBeanFactory;
private final FunctionCatalog functionCatalog;
private final FunctionProperties functionProperties;
private final BindingServiceProperties bindingServiceProperties;
@PostConstruct
void registerRouters() {
bindingServiceProperties.getBindings().keySet().stream()
.filter(it -> it.contains("Router-in-"))
.map(it -> it.replaceAll("Router-in-\\d+", ""))
.forEach(module -> {
final String beanName = module + "Router";
configurableListableBeanFactory.registerSingleton(beanName, createRoutingFunction(module));
log.info("Registered bean '" + beanName + "' for module: " + module);
});
}
private RoutingFunction createRoutingFunction(final String moduleName) {
return new RoutingFunction(functionCatalog, functionProperties, new BeanFactoryResolver(configurableListableBeanFactory),
new ConsumerMessageRoutingCallback(functionCatalog, moduleName));
}
}
以上代碼注冊路由函數: firstModuleRouter和secondModuleRouter
我們的 ConsumerMessageRoutingCallback 類:
@RequiredArgsConstructor
public class ConsumerMessageRoutingCallback implements MessageRoutingCallback {
private static final String CONSUMER_SUFFIX = "Consumer";
private static final String DEFAULT_MESSAGE_ROUTING_HANDLER_NAME = "defaultMessageRoutingHandler";
private final FunctionCatalog functionCatalog;
private final String moduleName;
@Override
public FunctionRoutingResult routingResult(final Message<?> message) {
final Object contentTypeHeader = message.getHeaders().get(MessagingHeaders.PAYLOAD_TYPE);
if (contentTypeHeader != null) {
return new FunctionRoutingResult(route(contentTypeHeader.toString()));
}
return new FunctionRoutingResult(DEFAULT_MESSAGE_ROUTING_HANDLER_NAME);
}
String route(final String qualifiedPayloadType) {
final String expectedBeanName = buildExpectedBeanName(moduleName, qualifiedPayloadType);
return beanExists(expectedBeanName) ? expectedBeanName : DEFAULT_MESSAGE_ROUTING_HANDLER_NAME;
}
private String buildExpectedBeanName(final String moduleName, final String qualifiedPayloadType) {
return replaceFirstLetterToLowerCase(nullable(moduleName) + simpleClassName(qualifiedPayloadType) + CONSUMER_SUFFIX);
}
private String replaceFirstLetterToLowerCase(final String input) {
return input.substring(0, 1).toLowerCase() + input.substring(1);
}
private String nullable(final String value) {
return StringUtils.isNotBlank(value) ? value : "";
}
private String simpleClassName(final String fullClassName) {
return fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
}
private boolean beanExists(final String expectedCallMethod) {
return functionCatalog.lookup(expectedCallMethod) != null;
}
}
在這種情況下,spring-cloud-stream create 的輸出綁定(以及消息代理中的主題)未被使用:
所以問題是:有沒有辦法禁止從路由函數創建輸出綁定? 我們已經檢查了 spring-cloud-stream 的來源,但我們沒有找到任何方法來做到這一點。
作為解決方法,我們找到了將創建的輸出綁定轉發到 yaml 中定義的輸出主題的解決方案:
spring:
cloud:
function:
autodetect: false
routing.enabled: true
definition: firstModuleRouter;secondModuleRouter
stream:
bindings:
firstModuleRouter-in-0:
destination: output-topic
firstModuleRouter-out-0:
destination: output-topic
secondModuleRouter-in-0:
destination: output-topic
secondModuleRouter-out-0:
destination: output-topic
但我們相信應該有任何其他解決方案來禁止在消息代理中創建額外的主題。
我已經遇到了那個問題。 我們已經通過在 kafka 端獲取創建主題的權利來解決它,是的,這是一個糟糕的解決方案,但仍然.. ))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.