让我们说有一个调用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
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 bean的并发调用的问题。
另一种方式 - 使用自定义范围。
自定义范围
Spring Framework允许实现自定义范围。因此,我们能够实现一个范围,该范围将为每个excahange存储一个单独的bean实例。
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,因为对bean的调用将在与启动交换的线程不同的线程中执行。
任何想法如何解决这些缺点?是否有完全不同的方法在并发交换期间隔离状态bean?
答案 0 :(得分:2)
另一个需要考虑的选择是不要使你的bean有状态。您可以将状态数据存储在消息本身而不是bean中,因此您的方法应该类似于:
public class StatefulBean {
public StateInfo setSomeState(Message msg) {...}
public void getSomeDataDependingOnState(StateInfo stateinfo) {...}
}
答案 1 :(得分:0)
使用seda
队列,它专为此类问题而设计。
鉴于您可以比进入邮件更快地处理邮件,这应该是理想的。 seda队列大小限制的一般大概是大约10,000 - 显然你可以根据你的需要调整它。
我在项目中面临类似的情况,我收到大约2000条消息的初始块,然后是每秒1条消息。它们需要按指定的顺序处理,因此我将消息放入seda队列进行顺序处理,并且可能需要3-5秒才能清除它们。
否则你可以找到一种方法来使用bean的差异实例,每次交换......