Group在RabbitMQ中收到消息,最好使用Spring AMQP?

时间:2017-03-23 06:52:30

标签: java spring rabbitmq spring-integration spring-amqp

我从服务(S)接收消息,该服务将每个单独的属性更改作为单独的消息发布到实体。一个人为的例子就是这样的实体:

Person {
    id: 123
    name: "Something",
    address: {...}
}

如果在同一事务中更新了名称和地址,则(S)将发布两条消息PersonNameCorrectedPersonMoved。问题出在接收方,我存储了这个Person实体的投影,每个属性更改都会导致写入数据库。因此,在这个示例中,将有两次写入数据库,但如果我可以在短时间内批量处理消息并按id分组,那么我只需要对数据库进行一次写入。

如何在RabbitMQ中处理这个问题? Spring AMQP是否提供了更简单的抽象?

请注意,我已简要介绍了prefetch,但我不确定这是否可行。如果我理解正确的话,预取也是基于连接的。我试图在 per-queue 的基础上实现这一点,因为如果要进行批处理(以及因此增加的延迟),我不想将此延迟添加到ALL我的服务所使用的队列(但仅限于那些需要" group-by-id"功能)的队列。

2 个答案:

答案 0 :(得分:4)

预取对这样的案例没有帮助。

考虑使用Spring Integration,它具有位于Spring AMQP之上的适配器;它还提供了一个聚合器,可以在将消息发送到管道的下一个阶段之前将它们组合在一起。

修改

这是一个快速启动应用程序来展示...

@SpringBootApplication
public class So42969130Application implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(So42969130Application.class, args)
            .close();
    }

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private Handler handler;

    @Override
    public void run(String... args) throws Exception {
        this.template.convertAndSend("so9130", new PersonNameChanged(123));
        this.template.convertAndSend("so9130", new PersonMoved(123));
        this.handler.latch.await(10, TimeUnit.SECONDS);
    }

    @Bean
    public IntegrationFlow flow(ConnectionFactory connectionFactory) {
        return IntegrationFlows.from(Amqp.inboundAdapter(connectionFactory, "so9130")
                        .messageConverter(converter()))
                .aggregate(a -> a
                        .correlationExpression("payload.id")
                        .releaseExpression("false") // open-ended release, timeout only
                        .sendPartialResultOnExpiry(true)
                        .groupTimeout(2000))
                .handle(handler())
                .get();
    }

    @Bean
    public Jackson2JsonMessageConverter converter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public Handler handler() {
        return new Handler();
    }

    @Bean
    public Queue queue() {
        return new Queue("so9130", false, false, true);
    }

    public static class Handler {

        private final CountDownLatch latch = new CountDownLatch(1);

        @ServiceActivator
        public void handle(Collection<?> aggregatedData) {
            System.out.println(aggregatedData);
            this.latch.countDown();
        }

    }

    public static class PersonNameChanged {

        private int id;

        PersonNameChanged() {
        }

        PersonNameChanged(int id) {
            this.id = id;
        }

        public int getId() {
            return this.id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "PersonNameChanged [id=" + this.id + "]";
        }

    }

    public static class PersonMoved {

        private int id;

        PersonMoved() {
        }

        PersonMoved(int id) {
            this.id = id;
        }

        public int getId() {
            return this.id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "PersonMoved [id=" + this.id + "]";
        }

    }

}

波姆:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>so42969130</artifactId>
    <version>2.0.0-BUILD-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>so42969130</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-java-dsl</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

结果:

2017-03-23 09:56:57.501  INFO 75217 --- [ask-scheduler-2] .s.i.a.AbstractCorrelatingMessageHandler : 
    Expiring MessageGroup with correlationKey[123]
[PersonNameChanged [id=123], PersonMoved [id=123]]

答案 1 :(得分:1)

使用Spring-Integration将消息传递系统的缺点转移到软件/服务端上,这是完全错误的。对于Spring Integration或任何框架而言,都不是这种情况。它也不能很好地扩展并且不能容错

此问题的核心是将路由消息与业务逻辑/发送消息分开

仅AFAIK的Kafka和Apache Artemis支持来自JMS API的JMSXGroup,以查看当前成熟的队列提供程序RabbitMQ没有它,但是AMQP被再次指定,尽管有社区的请求,RabbitMq却从未实现过。

在企业体系结构中确保从许多独立来源进行有序顺序处理的单一但也是非常经常且重要的情况,将RabbitMQ排除在进一步考虑之外,不将其作为默认消息传递解决方案