Самый правильный способ изолировать statefull bean-компоненты во время параллельных обменов

1

Скажем, есть маршрут, из которого вызывается statefull bean:

<camel:route id="Concurrently-called-route">
    <camel:from uri="direct:concurrentlyCalledRoute"/>
    <camel:bean ref="statefullBean" method="setSomeState"/>
    <camel:bean ref="statefullBean" method="getSomeDataDependingOnState"/>
</camel:route>

Сообщения могут отправляться по этому маршруту одновременно, т. requestBody Метод requestBody для ProducerTemplate вызывается из параллельных потоков. Таким образом, возникла бы проблема, если бы были задействованы два excahnges и setSomeState был вызван во время одного обмена между вызовами setSomeState и getSomeDataDependingOnState выполненными во время другого обмена. Я вижу два пути решения этой проблемы, каждый из которых имеет недостаток.

Использование SEDA

<camel:route id="Councurrently-called-route">
    <camel:from uri="direct:concurrentlyCalledRoute"/>
    <camel:to uri="seda:sedaRoute"/>
</camel:route>

<camel:route id="SEDA-route">
    <camel:from uri="seda:sedaRoute"/>
    <camel:bean ref="statefullBean" method="setSomeState"/>
    <camel:bean ref="statefullBean" method="getSomeDataDependingOnState"/>
</camel:route>

В этом случае сообщения, отправленные из разных потоков, собираются в очереди конечной точки SEDA. Сообщения из этой очереди обрабатываются одним потоком, проходя по SEDA-route. Поэтому обработка сообщения не будет мешать обработке другого. Однако, если бы было много потоков, отправляющих сообщения на concurrentlyCalledRoute SEDA-route это было бы узким местом. Если для обработки элементов из очереди seda использовалось более одного потока, проблема с одновременными вызовами statefull beans возникла бы снова.

Другой способ - использовать настраиваемую область.

Пользовательская область

Spring Framework позволяет реализовать пользовательские области. Таким образом, мы можем реализовать область, которая будет хранить отдельный экземпляр компонента для каждого excahange.

public class ExchangeScope implements Scope {

    private Map<String, Map<String,Object>> instances = new ConcurrentHashMap<>();

    private Map<String,Runnable> destructionCallbacks = new ConcurrentHashMap<>();

    private final ThreadLocal<String> currentExchangeId = new ThreadLocal<>();

    public void activate(String exchangeId) {
        if (!this.instances.containsKey(exchangeId)) {
            Map<String, Object> instancesInCurrentExchangeScope = new ConcurrentHashMap<>();
            this.instances.put(exchangeId, instancesInCurrentExchangeScope);
        }
        this.currentExchangeId.set(exchangeId);
    }

    public void destroy() {
        String currentExchangeId = this.currentExchangeId.get();
        Map<String,Object> instancesInCurrentExchangeScope = instances.get(currentExchangeId);
        if (instancesInCurrentExchangeScope == null)
            throw new RuntimeException("ExchangeScope with id = " + currentExchangeId + " doesn't exist");
        for (String name : instancesInCurrentExchangeScope.keySet()) {
            this.remove(name);
        }
        instances.remove(currentExchangeId);
        this.currentExchangeId.set(null);
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
    // selects by name a bean instance from a map storing instances for current exchange
    // creates a new bean instance if necessary
    }

    @Override
    public Object remove(String name) {
    // removes a bean instance
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        this.destructionCallbacks.put(name, callback);
    }

    @Override
    public Object resolveContextualObject(String name) {
        String currentExchangeId = this.currentExchangeId.get();
        if (currentExchangeId == null)
            return null;

        Map<String,Object> instancesInCurrentExchangeScope = this.instances.get(currentExchangeId);
        if (instancesInCurrentExchangeScope == null)
            return null;

        return instancesInCurrentExchangeScope.get(name);
    }

    @Override
    public String getConversationId() {
        return this.currentExchangeId.get();
    }
}

Теперь мы можем зарегистрировать эту настраиваемую область и объявить statefullBean как область обмена:

<bean id="exchangeScope" class="org.my.ExchangeScope"/>

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="ExchangeScope" value-ref="exchangeScope"/>
        </map>
    </property>
</bean>

<bean id="statefullBean" class="org.my.StatefullBean" scope="ExchangeScope"/>

Для того, чтобы использовать обмен сферу мы должны вызвать activate метод ExchangeScope перед отправкой сообщения и позвонить destroy после этого:

this.exchangeScope.activate(exchangeId);
this.producerTemplate.requestBody(request);
this.exchangeScope.destroy(exchangeId);

С этой реализацией область обмена фактически представляет собой область потока. И это недостаток. Если, например, многопоточный сплиттер использовался в маршруте, он не смог бы вызывать компоненты с областью обмена из потоков, созданных сплиттером, потому что вызовы bean-компонентов будут выполняться в потоках, отличных от потока, в котором запускается обмен.

Есть идеи, как обойти эти недостатки? Существуют ли совершенно разные способы изолировать statefull beans во время параллельных обменов?

Теги:
spring
concurrency
apache-camel

2 ответа

2

Еще одна альтернатива, которую следует учитывать, заключается в том, чтобы не сделать ваши бобы устойчивыми. Вы можете хранить данные состояния в самом сообщении, а не в компоненте, поэтому ваши методы будут выглядеть примерно так:

public class StatefulBean {
    public StateInfo setSomeState(Message msg) {...}

    public void getSomeDataDependingOnState(StateInfo stateinfo) {...}
}
  • 0
    +1 - я согласен, попытка обойти проблему более сложна, чем переработка / упаковка ваших бинов с состоянием ... Exchange должен содержать информацию о границах потока ... все, на что ссылаются внешние по отношению к Exchange, должно быть поточно-безопасным по замыслу ...
0

Используйте очередь seda, которая предназначена для такого рода проблем.

Учитывая, что вы можете обрабатывать сообщения быстрее, чем они приходят, это должно быть идеальным. Общий ориентир для ограничения размера очереди для седов будет составлять около 10 000 - очевидно, вы можете настроить его в соответствии с вашими потребностями.

Я столкнулся с аналогичной ситуацией в моем проекте, где я получаю начальный блок из примерно 2000 сообщений, а затем 1 сообщение в секунду после этого. Они должны обрабатываться в определенном порядке, поэтому я помещаю сообщения в очередь seda для последовательной обработки, и для их устранения может потребоваться 3-5 секунд.

В противном случае вы можете найти способ использовать экземпляр разметки вашего компонента, за обмен...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню