我有一个JMS队列,里面填充了一些时间序列数据。为了防止成千上万的单事务SQL插入,我想以大体积的方式而不是MessageListener onMessage“每条消息”的方式处理它们。
我想到的唯一解决方案是安排从队列中获取大量消息并定期保存它们的时间表。
@Stateless
public class SensorDataReceiver {
private static final int THRESHOLD_IN_SECONDS = 10;
private static final int QUEUE_TIMEOUT_IN_MILLIS = 1000;
@Resource(mappedName = "java:jboss/jms/queue/sensorData")
private Queue queue;
@Inject
private JMSContext context;
@Inject
private SensorDataDAO sensorDataDAO;
@SneakyThrows
@Schedule(hour = "*", minute = "*", second = "*/15", persistent = false)
public void scheduled() {
LocalDateTime statUpPlusThreshold = now().plusSeconds(THRESHOLD_IN_SECONDS);
JMSConsumer consumer = context.createConsumer(queue);
List<SensorData> sensorDataToInsert = new ArrayList<>();
do {
ObjectMessage message = (ObjectMessage) consumer.receive(QUEUE_TIMEOUT_IN_MILLIS);
if (message == null) {
break;
}
sensorDataToInsert.add((sensorData) message.getObject());
} while (now().isBefore(statUpPlusThreshold) && sensorDataToInsert.size() < 10_000);
logger.info(format("Got \"%d\" SensorData to persist.", sensorDataToInsert.size()));
sensorDataDAO.batchSaveOrUpdate(sensorDataToInsert);
logger.info(format("Persisted \"%d\" SensorData.", sensorDataToInsert.size()));
}
}
但是我不认为这是最聪明的方法,因此,当日程安排的执行速度超过配置的间隔时,我会浪费时间每分钟处理更多消息(我可以在2-3秒内插入1万行。测试系统),另一方面,此代码容易产生“重叠的计划执行”。
答案 0 :(得分:2)
我建议使用无状态bean池,这些池始终处于活动状态(即未调度),它们消耗一定数量的消息(即直到队列为空,这将是任意数量的消息),并且然后将这些消息中的数据插入单个数据库操作中。
池中的所有bean可以同时处于活动状态,并且可以尽快消耗并插入其批次。这样可以确保及时使用消息,从而有望避免队列中消息的堆积。
您可以在receive
上设置超时,这样,如果在达到批处理大小之前确实到达队列的末尾,数据仍会及时插入。
为了在应用服务器启动时启动此操作,您可以使用@Startup
和@Singleton
注释一个bean,然后使用@PostConstruct
注释一个方法,该方法循环足够的时间来填充您的“池”并在您的@Stateless
bean上调用该方法,该bean将接收并处理大量消息。