简体   繁体   English

如何同时运行共享同一读写实例的Spring Batch作业?

[英]How to run spring batch jobs simultaneously which share same readers and writers instances?

This is how my existing system works. 这就是我现有系统的工作方式。

I have batch written using spring batch which writes messages to queues ASYNCHRONOUSLY. 我有使用spring batch编写的批处理,该批处理将消息异步地写入队列。 The writers once send certain number of messages to queue, starts listening to LINKED_BLOCKING_QUEUE for same number of messages. 编写者一旦将一定数量的消息发送到队列,就开始收听LINKED_BLOCKING_QUEUE以获取相同数量的消息。

I have spring amqp listeners which consumes messages and process them. 我有spring amqp侦听器,它使用消息并对其进行处理。 Once processed, consumer replies back on reply queue. 处理后,使用者将回复返回到回复队列。 There are listeners which listens to reply queue to check whether messages are successfully processed or not. 有一些侦听器,它们侦听答复队列以检查消息是否被成功处理。 The reply listener retrives response and add it to LINKED_BLOCKING_QUEUE which is then fetched by writer. 回复侦听器检索响应并将其添加到LINKED_BLOCKING_QUEUE中,然后由编写者获取。 Once writer fetch all responses finishes batch. 一旦作者获取所有答复,便完成批处理。 If there is exception, it stops the batch. 如果有异常,它将停止该批处理。

This is my job configurations 这是我的工作配置

<beans:bean id="computeListener" class="com.st.symfony.Foundation"
    p:symfony-ref="symfony" p:replyTimeout="${compute.reply.timeout}" />

<rabbit:queue name="${compute.queue}" />
<rabbit:queue name="${compute.reply.queue}" />

<rabbit:direct-exchange name="${compute.exchange}">
    <rabbit:bindings>
        <rabbit:binding queue="${compute.queue}" key="${compute.routing.key}" />
    </rabbit:bindings>
</rabbit:direct-exchange>

<rabbit:listener-container
    connection-factory="rabbitConnectionFactory" concurrency="${compute.listener.concurrency}"
    requeue-rejected="false" prefetch="1">
    <rabbit:listener queues="${compute.queue}" ref="computeListener"
        method="run" />
</rabbit:listener-container>


<beans:beans profile="master">

    <beans:bean id="computeLbq" class="java.util.concurrent.LinkedBlockingQueue" />

    <beans:bean id="computeReplyHandler" p:blockingQueue-ref="computeLbq"
        class="com.st.batch.foundation.ReplyHandler" />

    <rabbit:listener-container
        connection-factory="rabbitConnectionFactory" concurrency="1"
        requeue-rejected="false">
        <rabbit:listener queues="${compute.reply.queue}" ref="computeReplyHandler"
            method="onMessage" />
    </rabbit:listener-container>


    <beans:bean id="computeItemWriter"
        class="com.st.batch.foundation.AmqpAsynchItemWriter"
        p:template-ref="amqpTemplate" p:queue="${compute.queue}"
        p:replyQueue="${compute.reply.queue}" p:exchange="${compute.exchange}"
        p:replyTimeout="${compute.reply.timeout}" p:routingKey="${compute.routing.key}"
        p:blockingQueue-ref="computeLbq"
        p:logFilePath="${spring.tmp.batch.dir}/#{jobParameters[batch_id]}/log.txt"
        p:admin-ref="rabbitmqAdmin" scope="step" />


    <job id="computeJob" restartable="true">
        <step id="computeStep">
            <tasklet transaction-manager="transactionManager">
                <chunk reader="computeFileItemReader" processor="computeItemProcessor"
                    writer="computeItemWriter" commit-interval="${compute.commit.interval}" />
            </tasklet>
        </step>
    </job>      


</beans:beans>

This is my writer code, 这是我的作者代码,

public class AmqpAsynchRpcItemWriter<T> implements ItemWriter<T> {

    protected String exchange;
    protected String routingKey;
    protected String queue;
    protected String replyQueue;
    protected RabbitTemplate template;
    protected AmqpAdmin admin;
    BlockingQueue<Object> blockingQueue;
    String logFilePath;
    long replyTimeout;


    // Getters and Setters

    @Override
    public void write(List<? extends T> items) throws Exception {

        for (T item : items) {


            Message message = MessageBuilder
                    .withBody(item.toString().getBytes())
                    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
                    .setReplyTo(this.replyQueue)
                    .setCorrelationId(item.toString().getBytes()).build();

            template.send(this.exchange, this.routingKey, message);
        }

        for (T item : items) {

            Object msg = blockingQueue
                    .poll(this.replyTimeout, TimeUnit.MILLISECONDS);

            if (msg instanceof Exception) {

                admin.purgeQueue(this.queue, true);
                throw (Exception) msg;

            } else if (msg == null) {
                throw new Exception("reply timeout...");
            } 

        }

        System.out.println("All items are processed.. Command completed.  ");

    }

}

Listener pojo 侦听器pojo

public class Foundation {

    Symfony symfony;

    long replyTimeout;

    //Getters Setters

    public Object run(String command) {

        System.out.println("Running:" + command);

        try {
            symfony.run(command, this.replyTimeout);
        } catch (Exception e) {
            return e;
        }

        return "Completed : " + command;
    }
}

This is reply handler 这是回复处理程序

public class ReplyHandler {

    BlockingQueue<Object> blockingQueue;

    public void onMessage(Object msgContent) {

        try {

            blockingQueue.put(msgContent);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }
    }

}

Now, the problem is, I want to run multiple batches with unique batch id simultaneously which will process different data (of same type) for different batches. 现在,问题是,我想同时运行具有唯一批次ID的多个批次,这将为不同批次处理不同数据(相同类型)。

As the number of batches are going to be increased in future, I don't want to keep adding separate queues and reply queues for each batch. 随着将来批次数量的增加,我不想为每个批次继续添加单独的队列和答复队列。

And also, to process messages simultaneously, I have multiple listeners (set with listener concurrency ) listening to queue. 而且,为了同时处理消息,我有多个侦听器(设置有侦听器并发性)正在监听队列。 If I add different queue for different batches, number of listeners running will be increased which may overload servers (CPU/Memory usage goes high). 如果我为不同批次添加不同的队列,则会增加正在运行的侦听器的数量,这可能会使服务器超负荷(CPU /内存使用率很高)。

So I don't want to replicate same infrastructure for each type of batch I am going to add. 因此,我不想为要添加的每种批次复制相同的基础结构。 I want to use same infrastructure just writers of specific batch should get only its responses not the responses of other batches running simultaneously. 我想使用相同的基础结构,只是特定批次的编写者应该只获得其响应,而不是同时运行的其他批次的响应。

Can we use same instances of item writers which use same blocking queue instances for multiple instances of batches running parallel ? 我们是否可以使用项目编写器的相同实例,对多个并行运行的批次实例使用相同的阻塞队列实例?

You may want to look into JMS Message Selectors . 您可能需要研究JMS消息选择器

As per Docs 根据文档

The createConsumer and createDurableSubscriber methods allow you to specify a message selector as an argument when you create a message consumer. 使用createConsumer和createDurableSubscriber方法,可以在创建消息使用者时将消息选择器指定为参数。

The message consumer then receives only messages whose headers and properties match the selector. 然后,消息使用者仅接收其标题和属性与选择器匹配的消息。

There is no equivalent of a JMS message selector expression in the AMQP (RabbitMQ) world. 在AMQP(RabbitMQ)世界中,没有等效的JMS消息选择器表达式。

Each consumer has to have his own queue and you use an exchange to route to the appropriate queue, using a routing key set by the sender. 每个使用者都必须有自己的队列,并且您可以使用发送方设置的路由密钥,通过交换机将其路由到适当的队列。

It is not as burdensome as you might think; 它没有您想像的那么繁重; you don't have to statically configure the broker; 您不必静态配置代理; the consumers can use a RabbitAdmin to declare/delete exchanges, queues, bindings on demand. 使用者可以使用RabbitAdminRabbitAdmin声明/删除交换,队列和绑定。

See Configuring the Broker in the Spring AMQP documentation. 请参阅Spring AMQP文档中的配置代理

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

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