개발/Apache Camel

[Apache Camel] Netty4 maximumPoolSize 설정

Morian Kim 2020. 10. 2. 13:45

Apache Camel Netty4, Netty4-Http 에서 maximumPoolSize가 뜻하는 것은 실제 Exchange를 가지고 Processor를 진행하는 주체인 NettyEventExecutorGroup 의 갯수이다.

Netty4 Endpoint 설정이 아닌 Netty4 Component 설정은

SpringBoot의 application.properties에서 auto-configuration이 가능하다. ( 아시다 시피 starter 여야 auto-configuration이 가능하므로 메이븐으로 camel-netty4-starter 받는다 )

그러나 아래와 같이 netty4 같은경우 maximumPoolSize를 설정해도

application.properties
NettyComponentConfiguration
NettyComponentConfiguration.setMaximumPoolSize()

 

이렇게 application.properties의 값을 가져와서 NettyComponentConfiguration 에다가 입력이 된다.

그럼 이제 NettyComponent를 생성 시  NettyComponentConfiguration을 이용해서 ComponentConfiguration을 설정하면 EventExecutorGroup의 PoolSize를 입력 시 properties에서 설정한 2가 들어가야하는데 전혀 반영이 되지 않았다.

NettyComponent.createExecutorService()

후 ... 아무리 살펴봐도 NettyComponentConfiguration 값이 반영되는 로직을 찾을 수가 없었다.

현업에서 사용한 2.21.1 버전, 지금 이 글을 작성할때 쓴 최신버전 3.0.0-M4 다 그렇다.

 

하지만 웃긴것은

Netty4Component를 기반으로한 Netty4HttpComponent는 또 application.properties에 설정한 옵션이 먹는다.

netty4-http.maximum-pool-size 4로설정


디버그로 무슨 차이인지 확인을 해보았다.

결국 Bean을 생성할 때 factoryMethodName이 있는지 없는지 여부에 의해 Bean 생성이 갈리게 되었다!!!

그럼 factoryMethod가 뭔지 살펴봤더니

바로 starter에 의해 AutoConfiguration이 진행되는 클래스의 메소드 였던것이다.

그래서 바로 configureNettyHttpComponent 메소드를 살펴보았다.

 @Lazy
    @Bean({"netty-http-component", "netty4-http-component"})
    @ConditionalOnMissingBean(NettyHttpComponent.class)
    public NettyHttpComponent configureNettyHttpComponent() throws Exception {
        NettyHttpComponent component = new NettyHttpComponent();
        component.setCamelContext(camelContext);
        Map<String, Object> parameters = new HashMap<>();
        IntrospectionSupport.getProperties(configuration, parameters, null,
                false);
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            Object value = entry.getValue();
            Class<?> paramClass = value.getClass();
            if (paramClass.getName().endsWith("NestedConfiguration")) {
                Class nestedClass = null;
                try {
                    nestedClass = (Class) paramClass.getDeclaredField(
                            "CAMEL_NESTED_CLASS").get(null);
                    HashMap<String, Object> nestedParameters = new HashMap<>();
                    IntrospectionSupport.getProperties(value, nestedParameters,
                            null, false);
                    Object nestedProperty = nestedClass.newInstance();
                    CamelPropertiesHelper.setCamelProperties(camelContext,
                            nestedProperty, nestedParameters, false);
                    entry.setValue(nestedProperty);
                } catch (NoSuchFieldException e) {
                }
            }
        }
        CamelPropertiesHelper.setCamelProperties(camelContext, component,
                parameters, false);
        if (ObjectHelper.isNotEmpty(customizers)) {
            for (ComponentCustomizer<NettyHttpComponent> customizer : customizers) {
                boolean useCustomizer = (customizer instanceof HasId)
                        ? HierarchicalPropertiesEvaluator.evaluate(
                                applicationContext.getEnvironment(),
                                "camel.component.customizer",
                                "camel.component.netty4-http.customizer",
                                ((HasId) customizer).getId())
                        : HierarchicalPropertiesEvaluator.evaluate(
                                applicationContext.getEnvironment(),
                                "camel.component.customizer",
                                "camel.component.netty4-http.customizer");
                if (useCustomizer) {
                    LOGGER.debug("Configure component {}, with customizer {}",
                            component, customizer);
                    customizer.customize(component);
                }
            }
        }
        return component;
    }

이렇게 NettyHttpComponent 생성 후 CamelPropetiesHelper.setCamelProperties() 메소드에 의해 application.properties에 등록되어있던 값들이 채워진 후 return 하게 되었다.

그래서 maximum-pool-size 가 적용되었던 것이다.

 

그러면 우리 NettyComponent는 ..?

@Lazy
    @Bean({"netty-component", "netty4-component"})
    @ConditionalOnMissingBean(NettyComponent.class)
    public NettyComponent configureNettyComponent() throws Exception {
        NettyComponent component = new NettyComponent();
        component.setCamelContext(camelContext);
        Map<String, Object> parameters = new HashMap<>();
        IntrospectionSupport.getProperties(configuration, parameters, null,
                false);
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            Object value = entry.getValue();
            Class<?> paramClass = value.getClass();
            if (paramClass.getName().endsWith("NestedConfiguration")) {
                Class nestedClass = null;
                try {
                    nestedClass = (Class) paramClass.getDeclaredField(
                            "CAMEL_NESTED_CLASS").get(null);
                    HashMap<String, Object> nestedParameters = new HashMap<>();
                    IntrospectionSupport.getProperties(value, nestedParameters,
                            null, false);
                    Object nestedProperty = nestedClass.newInstance();
                    CamelPropertiesHelper.setCamelProperties(camelContext,
                            nestedProperty, nestedParameters, false);
                    entry.setValue(nestedProperty);
                } catch (NoSuchFieldException e) {
                }
            }
        }
        CamelPropertiesHelper.setCamelProperties(camelContext, component,
                parameters, false);
        if (ObjectHelper.isNotEmpty(customizers)) {
            for (ComponentCustomizer<NettyComponent> customizer : customizers) {
                boolean useCustomizer = (customizer instanceof HasId)
                        ? HierarchicalPropertiesEvaluator.evaluate(
                                applicationContext.getEnvironment(),
                                "camel.component.customizer",
                                "camel.component.netty4.customizer",
                                ((HasId) customizer).getId())
                        : HierarchicalPropertiesEvaluator.evaluate(
                                applicationContext.getEnvironment(),
                                "camel.component.customizer",
                                "camel.component.netty4.customizer");
                if (useCustomizer) {
                    LOGGER.debug("Configure component {}, with customizer {}",
                            component, customizer);
                    customizer.customize(component);
                }
            }
        }
        return component;
    }

이렇게 똑같은 메소드를 가지고있다.

그런데 Bean생성 시  factoryBeanName, factoryMethodName이 존재하지 않는다 ... NettyComponentAutoConfiguration의 configureNettyComponent() 메소드로 생성이 되지않는다는 것

그냥 beanClass를 통해 new NettyComponent()만이 이루어 지는 것이다. 그래서 maximum-pool-size default값인 16이 계속 입력되었던것

 

후 .. 그럼 더 거슬러 올라가서 어디서 이러한 mbd가 생성되는지 확인을 해봐야겠다.

SpringBootCamelContext에서 Component를 초기화하는 시점에서부터 이 둘의 생성방법이 달라지게 되었다.

결론적으로 "netty4", "netty4-component" Component가 Singleton으로 Bean이 생성되어있지 않아서 발생한 문제였다.

@Lazy
@Bean({"netty-component", "netty4-component"})
@ConditionalOnMissingBean(NettyComponent.class)
public NettyComponent configureNettyComponent() throws Exception {
    .
    .
    .
}

분명히 Bean이 달려있는데 어째서 ... 흠 ..

 

그럼 진짜로 Bean으로 생성이 되어있지 않은건지 테스트 해보았다.

@Configuration
public class TcpRouter extends RouteBuilder{
	
	@Bean
	public StringDecoder stringDecoder() {
		return new StringDecoder();
	}

	@Bean
	public StringEncoder stringEncoder() {
		return new StringEncoder();
	}
	
	@Autowired
	private NettyHttpComponentAutoConfiguration nettyHttpComponent;
	
	@Autowired
	private NettyComponentAutoConfiguration nettyComponent;
	
	
	@Override
	public void configure() throws Exception {	
		
		NettyHttpComponent nhc = nettyHttpComponent.configureNettyHttpComponent();
		NettyComponent nc = nettyComponent.configureNettyComponent();
		
		from("netty4:tcp://localhost:60010?decoders=stringDecoder&encoders=stringEncoder&workerCount=2")
		.process(exchange->{
			System.out.println("start tcp:"+exchange.getMessage().getBody(String.class));
			Thread.sleep(3000);
			System.out.println("end tcp:"+exchange.getMessage().getBody(String.class));
		})
		.end();
	}

}

 

위와 같이 실행하니 NettyHttpComponent.configureNettyHttpComponent() 메소드는 무사히 지나갔다.

그러나

***************************
APPLICATION FAILED TO START
***************************

Description:

A component required a bean named 'netty-component' that could not be found.

The following candidates were found but could not be injected:
	- Bean method 'configureNettyComponent' in 'NettyComponentAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: org.apache.camel.component.netty4.NettyComponent; SearchStrategy: all) found beans of type 'org.apache.camel.component.netty4.NettyComponent' netty-http-component


Action:

Consider revisiting the entries above or defining a bean named 'netty-component' in your configuration.

 

netty-http-component 가 netty-component를 상속하여서 생긴 문제같은데 실제로 netty-component만 auto-configuration을 하면 에러가 발생하지 않는다.

 

그래서 Apache Camel 측에 메일을 보냈더니

old version이라고 3.4 이상만 지원한단다...

결국 현재 버전에서 netty-http-component 와 netty-component 를 동시에 사용할 경우 auto-configuration 은 netty-http-component만 적용되고 netty-component는 따로 custom하게 Bean으로 내가 직접 생성해서 해야한다는 것이다.