简体   繁体   English

生产者消费者模式-处理生产者失败

[英]Producer Consumer pattern - handling producer failure

I have a producer/consumer pattern like the following 我有一个生产者/消费者模式,如下所示

  • A fixed number of producer threads, each writing on to their own BlockingQueue, invoked via an Executor 固定数量的生产者线程,每个线程都写入自己的BlockingQueue,并通过执行程序调用
  • A single consumer thread, reading off the producer threads 单个使用者线程,读取生产者线程

Each producer is running a database query and writing the results to its Queue. 每个生产者都在运行数据库查询并将结果写入其队列。 The consumer polls all the producer Queues. 消费者轮询所有生产者队列。 At the moment if there is a database error the producer thread dies and then the consumer gets stuck forever waiting for more results on the product Queue. 目前,如果存在数据库错误,生产者线程将死亡,然后消费者永远卡住,等待产品队列上的更多结果。

How should I be structuring this to handle catch errors correctly? 我应该如何构造它以正确处理捕获错误?

I once did a similar thing and decided to use a sentinel value that the dying producer thread would push into the queue from the catch-block. 我曾经做过类似的事情,并决定使用垂死的生产者线程将其从catch块推入队列的哨兵值。 You can push the exception itself (this works in most scenarios), or have a special object for that. 您可以推送异常本身(在大多数情况下都可以使用),或为此创建一个特殊的对象。 In any case it is great to push the exception to the consumer for debugging purposes. 无论如何,将异常推送给使用者以进行调试是非常好的。

It appears that the only option you have when a producer dies is to stop the consumer. 看来,当生产者死亡时,您唯一的选择就是停止消费者。

To do this you can use a poison pill. 为此,您可以使用毒药。 This is a special object which the producer adds when it stops and the consumer knows to stop when it receives it. 这是一个特殊的对象,生产者在停止时添加该对象,而消费者知道在接收到它时停止。 The poison pill can be added into a finally block so it is always added no matter how the producer is killed/dies. 可以将毒药添加到finally块中,因此无论生产者是如何被杀死/死亡,总是将其添加。

Given you have only one consumer, I would use one queue. 假设您只有一个消费者,我将使用一个队列。 This way your consumer will only block where all the producers have died. 这样,您的消费者只会阻止所有生产者死亡的地方。

Whatever class it is that you actually push onto the queue/s, it should contain success/fail/error members so that the consumer/s can check for fails. 无论您实际将哪个类推入队列,它都应包含成功/失败/错误成员,以便使用者可以检查失败。

Peter has already suggested using only one queue - I don't see how avoiding all that polling should be any particular problem - the objects on the queue can have members that identify which producer they came from, and any other metadata, if required. Peter已经建议仅使用一个队列-我不认为如何避免所有轮询都应该是任何特定问题-队列上的对象可以具有标识它们来自哪个生产者的成员以及任何其他元数据(如果需要)。

You might add some timeout to kill the consumer when there are no more elements in the queue(s) for a certain time. 当在特定时间内队列中没有更多元素时,您可以添加一些超时以杀死使用者。

Another approach might be to have the producers maintain an "alive" flag and signal that they are dying by setting it to false. 另一种方法可能是让生产者保持“活动”标志,并通过将其设置为false来表明他们快死了。 If the producers run continuously but might not always get results from the database the "alive" flag could be the time the producer reported to be alive the last time and then use the timeout to check whether the producer might have died (when the last report of being alive was too long ago). 如果生产者连续运行,但可能并不总是从数据库中获取结果,则“活动”标志可以是生产者上次报告存活时间的时间,然后使用超时检查生产者是否已死亡(上一次报告时)还活着的时间太久了)。

Answering my own question. 回答我自己的问题。

I used the following class. 我使用了以下课程。 It takes a list of Runnable and executes them all in parallel, if one fails, it interrupts all the others. 它获取一个Runnable列表并并行执行它们,如果一个失败,则中断所有其他事件。 Then I have interrupt handling in my producers and consumers to die gracefully when interrupted. 然后,我在生产者和消费者中进行中断处理,以便在被中断时优雅地死掉。

This works nicely for my case. 这对于我的情况很好。

Thanks for all the comments/answers as they gave me some ideas. 感谢所有的评论/答案,因为它们给了我一些想法。

// helper class that does the following
//
// if any thread has an exception then interrupt all the others with an eye to cancelling them
// if the thread calling execute() is interrupted then interrupt all the child threads
public class LinkedExecutor
{
    private final Collection<Runnable> runnables;
    private final String name;

    public LinkedExecutor( String name, Collection<Runnable> runnables )
    {
        this.runnables = runnables;
        this.name = name;
    }

    public void execute()
    {
        ExecutorService executorService = Executors.newCachedThreadPool( ConfigurableThreadFactory.newWithPrefix( name ) );

        // use a completion service to poll the results
        CompletionService<Object> completionService = new ExecutorCompletionService<Object>( executorService );

        for ( Runnable runnable : runnables )
        {
            completionService.submit( runnable, null );
        }

        try
        {
            for ( int i = 0; i < runnables.size(); i++ )
            {
                Future<?> future = completionService.take();
                future.get();
            }
        }
        catch ( InterruptedException e )
        {
            // on an interruption of this thread interrupt all sub-threads in the executor
            executorService.shutdownNow();

            throw new RuntimeException( "Executor '" + name + "' interrupted", e );
        }
        catch ( ExecutionException e )
        {
            // on a failure of any of the sub-threads interrupt all the threads
            executorService.shutdownNow();

            throw new RuntimeException( "Execution execution in executor '" + name + "'", e );
        }
    }
}

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

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