简体   繁体   English

Spring 集成 - 优先聚合器

[英]Spring Integration - Priority aggregator

I have following app requirements:我有以下应用要求:

  • Messages are received from RabbitMq, and then aggregated based on some more complex rules eg - based on types property (with pre-given type-time mapping) and based on existing time message has been waiting in the queue ( old property)从 RabbitMq 接收消息,然后根据一些更复杂的规则进行聚合,例如 - 基于types属性(具有预先给定的类型时间映射)和基于现有时间消息已在队列中等待( old属性)
  • All messages should be released at some variable message rate, eg 1msg/sec up to 100msg/sec.所有消息都应该以某种可变的消息速率发布,例如 1msg/sec 到 100msg/sec。 This rate is controlled and set by service that will monitor rabbitmq queue size (one queue that is not related to this component, and is further up the pipeline) and if too much messages are in queue - would decrease the rate.此速率由将监控 rabbitmq 队列大小的服务控制和设置(一个与此组件无关的队列,并且在管道中更远),如果队列中的消息过多 - 将降低速率。

As you can see in the image one use-case: three messages are already aggregated and are waiting to be released next second (since current rate is 1msg/sec ), but just at that time, MSG arrives with id:10 , and it updated AGGREGATED 2 , making it become 1st message by priority.正如您在图像中看到的一个用例:三条消息已经聚合并等待下一秒发布(因为当前速率为1msg/sec ),但就在那时, MSGid:10到达,它更新了AGGREGATED 2 ,使其成为优先级的第一条消息。 So on next tick, instead of releasing AGGREGATED 3 , we release AGGREGATED 2 since it has now higher priority.所以在下一个滴答声中,我们没有释放AGGREGATED 3 ,而是释放AGGREGATED 2 ,因为它现在具有更高的优先级。

在此处输入图像描述

Now, the question is - can I use Spring Integration Aggregator for this, since I do not know if it supports prioritization of messages during aggregation?现在,问题是 - 我可以为此使用 Spring 集成聚合器,因为我不知道它是否支持聚合期间的消息优先级? I know of groupTimeout , but that one is only adjusting single message group - not changing priority of other groups.我知道groupTimeout ,但那只是调整单个消息组 - 不改变其他组的优先级。 Would it be possible to use MessageGroupStoreReaper that would adjust all other aggregated messages by priority when new MSG arrives?是否可以使用MessageGroupStoreReaper在新的 MSG 到达时按优先级调整所有其他聚合消息?

UPDATE更新

I did some implementation like this - seems OK for now - it is aggregating messages as it arrives, and comparator is sorting messages by my custom logic.我做了一些这样的实现——现在看起来还可以——它在消息到达时聚合消息,比较器通过我的自定义逻辑对消息进行排序。

Do you think there could be some problems with this (concurrency etc.)?您认为这可能存在一些问题(并发等)吗? I can see in the logs, that poller is invoked more than once on occations.我可以在日志中看到,轮询器在某些情况下被多次调用。 Is this normal?这是正常的吗?

2021-01-18 13:52:05.277  INFO 16080 --- [   scheduling-1] ggregatorConfig$PriorityAggregatingQueue : POLL
2021-01-18 13:52:05.277  INFO 16080 --- [   scheduling-1] ggregatorConfig$PriorityAggregatingQueue : POLL
2021-01-18 13:52:05.277  INFO 16080 --- [   scheduling-1] ggregatorConfig$PriorityAggregatingQueue : POLL
2021-01-18 13:52:05.277  INFO 16080 --- [   scheduling-1] ggregatorConfig$PriorityAggregatingQueue : POLL

Also, is this commented doit method, proper way to increase max number of polled messages in runtime?另外,这个注释的doit方法是在运行时增加最大轮询消息数的正确方法吗?

@Bean
    public MessageChannel aggregatingChannel(){
        return new QueueChannel(new PriorityAggregatingQueue<>((m1, m2) -> {//aggr here},
                Comparator.comparingInt(x -> x),
                (m) -> {
                    ExampleDTO d = (ExampleDTO) m.getPayload();
                    return d.getId();
                }
        ));
    }

    class PriorityAggregatingQueue<K> extends AbstractQueue<Message<?>> {
        private final Log logger = LogFactory.getLog(getClass());
        private final BiFunction<Message<?>, Message<?>, Message<?>> accumulator;
        private final Function<Message<?>, K> keyExtractor;
        private final NavigableMap<K, Message<?>> keyToAggregatedMessage;

        public PriorityAggregatingQueue(BiFunction<Message<?>, Message<?>, Message<?>> accumulator,
                                        Comparator<? super K> comparator,
                                        Function<Message<?>, K> keyExtractor) {
            this.accumulator = accumulator;
            this.keyExtractor = keyExtractor;
            keyToAggregatedMessage = new ConcurrentSkipListMap<>(comparator);
        }

        @Override
        public Iterator<Message<?>> iterator() {
            return keyToAggregatedMessage.values().iterator();
        }

        @Override
        public int size() {
            return keyToAggregatedMessage.size();
        }

        @Override
        public boolean offer(Message<?> m) {
            logger.info("OFFER");
            return keyToAggregatedMessage.compute(keyExtractor.apply(m), (k,old) -> accumulator.apply(old, m)) != null;
        }

        @Override
        public Message<?> poll() {
            logger.info("POLL");
            Map.Entry<K, Message<?>> m = keyToAggregatedMessage.pollLastEntry();
            return m != null ? m.getValue() : null;
        }

        @Override
        public Message<?> peek() {
            Map.Entry<K, Message<?>> m = keyToAggregatedMessage.lastEntry();
            return m!= null ? m.getValue() : null;
        }
    }

//    @Scheduled(fixedDelay = 10*1000)
//    public void doit(){
//        System.out.println("INCREASE POLL");
//        pollerMetadata().setMaxMessagesPerPoll(pollerMetadata().getMaxMessagesPerPoll() * 2);
//    }

    @Bean(name = PollerMetadata.DEFAULT_POLLER)
    public PollerMetadata pollerMetadata(){
        PollerMetadata metadata = new PollerMetadata();
        metadata.setTrigger(new DynamicPeriodicTrigger(Duration.ofSeconds(30)));
        metadata.setMaxMessagesPerPoll(1);
        return metadata;
    }

    @Bean
    public IntegrationFlow aggregatingFlow(
            AmqpInboundChannelAdapter aggregatorInboundChannel,
            AmqpOutboundEndpoint aggregatorOutboundChannel,
            MessageChannel wtChannel,
            MessageChannel aggregatingChannel,
            PollerMetadata pollerMetadata
    ) {
    return IntegrationFlows.from(aggregatorInboundChannel)
        .wireTap(wtChannel)
        .channel(aggregatingChannel)
        .handle(aggregatorOutboundChannel)
        .get();
    }

Well, if there is a new message for group to complete it arrives into an aggregator, then such a group is released immediately (if your ReleaseStrategy says that though).好吧,如果有一条新消息需要组完成,它会到达聚合器,那么这样的组会立即释放(如果您的ReleaseStrategy这么说的话)。 The rest of group under timeout will continue to wait for the schedule.超时组的 rest 将继续等待调度。

It is probably possible to come up with smart algorithm to rely on a single common schedule with the MessageGroupStoreReaper to decide if we need to release that partial group or just discard it.可能会想出智能算法来依靠MessageGroupStoreReaper的单个公共调度来决定我们是否需要释放该部分组或只是丢弃它。 Again: the ReleaseStrategy should give us a clue to release or not, even if partial.再说一遍: ReleaseStrategy应该给我们一个发布与否的线索,即使是部分发布。 When discard happens and we want to keep those messages in the aggregator, we need to resend them back to the aggregator after some delay.当丢弃发生并且我们希望将这些消息保留在聚合器中时,我们需要在延迟一段时间后将它们重新发送回聚合器。 After expiration the group is removed from the store and this happens when we have already sent into a discard channel, so it is better to delay them and let an aggregator to clean up those groups, so after delay we can safely send them back to the aggregator for a new expiration period as parts of new groups.过期后,组从存储中删除,这发生在我们已经发送到丢弃通道时,所以最好延迟它们并让聚合器清理这些组,这样延迟后我们可以安全地将它们发送回作为新组的一部分的新到期期限的聚合器。

You probably also can iterate all of the messages in the store after releases normal group to adjust some time key in their headers for the next expiration time.您可能还可以在发布普通组后迭代存储中的所有消息,以调整其标题中的一些时间键以用于下一个到期时间。

I know this is hard matter, but there is really no any out-of-the-box solution since it was not designed to affect other groups from one we just dealt with...我知道这很困难,但实际上没有任何开箱即用的解决方案,因为它的设计目的不是为了影响我们刚刚处理的其他群体......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM