簡體   English   中英

Spring Cloud Streams - 多模塊項目中的多個路由功能創建輸出綁定

[英]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));
    }
}

以上代碼注冊路由函數: firstModuleRoutersecondModuleRouter

我們的 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 的輸出綁定(以及消息代理中的主題)未被使用:

  • firstModuleRouter-out-0
  • secondModuleRouter-out-0

所以問題是:有沒有辦法禁止從路由函數創建輸出綁定? 我們已經檢查了 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM