Rabbitmq崩溃时的回退机制

时间:2019-09-26 06:37:36

标签: java spring rabbitmq spring-rabbitmq messagebroker

我的Spring Boot应用程序通过RabbitMQ将事件发送到Timescale。 如果RabbitMQ出现故障,我需要知道如何保存事件。

详细信息:

RabbitMQ发布的消息是持久的。当消息代理发生故障时,事件不会被发布,并且我计划将这些事件存储在数据库中,并在事件发生时再次将它们发布到RabbitMQ。 欢迎任何解决方案和建议。

2 个答案:

答案 0 :(得分:0)

弹性,并发,高效,可移植和本地

正如Kayaman所建议的那样,如果保留消息至关重要,则应该使用可恢复,并发,高效并且最好是本地(在同一台计算机上)的数据库系统。

  • 如果RabbitMQ不可用,则原因可能与网络中断有关。因此,如果可能的话,您的后备机制应该是同一台计算机上的本地机制。
  • 我们不想给您的本地服务器增加负担。因此,理想情况下,用作后备数据库的数据库在使用RAM,CPU和存储方面应该是高效的。
  • 如果对RabbitMQ的访问连续快速重复失败,则我们可能会将消息添加到我们的后备数据库中,同时在时间上有所重叠,将记录的消息移出数据库又回到RabbitMQ。因此,后备机制应该能够处理并发访问。
  • 如果保留这些消息很重要,那么我们的后备数据库应该具有弹性,能够承受崩溃/停电。
  • 如果重新部署服务器,则后备机制是可移植的,而不依赖于任何特定的OS或CPU指令集将是很好的选择。因此基于Java的解决了这个问题。

H2数据库引擎

我满足这些需求的第一个想法是使用H2 Database Engine。 H2是一个关系数据库,使用纯Java内置,经过积极开发,并被证明具有生产价值。

要考虑的类似产品是Apache Derby。但是我听说过各种问题,尽管您应该研究其当前状况,但这可能意味着它不值得生产。

H2的关系部分可能无关紧要,因为您可能只需要一个表来跟踪您的消息流,以便稍后将其重新发送到RabbitMQ。至于本地化,高效,有弹性,可移植和并发的其他要求,H2非常适合。而且,如果您需要添加其他表,H2可以作为完全关系数据库系统使用。

H2可以通过以下两种模式之一启动:

没有更多信息很难说哪种模式适合您的需求。如果要附加外部监视工具,则可能需要服务器模式。如果您想要简单而精益的嵌入式模式。

您的Java应用通过包含的JDBC driver连接到H2。

答案 1 :(得分:-1)

进一步说明一下:确实不需要使用数据库进行持久缓冲。

编辑:另外,无法将消息发送到RabbitMQ的原因很可能是网络连接丢失。在这种情况下,大多数DBMS几乎没有用。 编辑结束。

package io.mahlberg.stackoverflow.questions.objpersistencedemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;

public class PersistenceDemo {

  /*
   * A decent default number of objects to be buffered.
   */
  private static long COUNT = 10000L;

  /*
   * Default filename
   */
  private static final String OBJ_DAT = "./obj.dat";

  private static FileOutputStream   fos;
  private static ObjectOutputStream oos;

  private static FileInputStream   fis;
  private static ObjectInputStream ois;

  private static File dat;

  private static Object lock;

  public static void main(String[] args) throws InterruptedException, IOException {

    // Get the actual number of counts
    if (args[0] != null) {
      COUNT = Long.parseLong(args[0]);
    }

    // Initialize out lock
    lock = new Object();

    // Ensure the datafile exists.
    dat = new File(OBJ_DAT);
    dat.createNewFile();

    // Initialize our streams.
    try {
      fos = new FileOutputStream(dat);
    } catch (Exception e1) {
      e1.printStackTrace();
      System.exit(1);
    }

    oos = new ObjectOutputStream(fos);

    // Define the writer thread.
    Thread writer = new Thread(new Runnable() {

      public void run() {
        Data obj;

        // Make sure we have the behaviour of the queue.
        synchronized (lock) {

          for (int i = 0; i < COUNT; i++) {
            obj = new Data(String.format("Obj-%d", i), new Date());
            try {
              oos.writeObject(obj);
              oos.flush();
              fos.flush();

              // Notify the reader...
              lock.notify();
            } catch (IOException e1) {
              // TODO Auto-generated catch block
              e1.printStackTrace();
            }
            try {
              // ... and wait until the reader is finished.
              lock.wait();
            } catch (InterruptedException e) {
              e.printStackTrace();
              break;
            }
          }
          // We need to notify the reader one last time for the last
          // Object we put into the stream.
          lock.notify();
        }

      }
    });

    // Initialize the streams used by reader.
    fis = new FileInputStream(dat);
    ois = new ObjectInputStream(fis);

    Thread reader = new Thread(new Runnable() {

      public void run() {
        Data obj;
        while (true) {
          synchronized (lock) {

            try {
              obj = (Data) ois.readObject();

              // Notify writer we are finished with reading the latest entry...
              lock.notify();
            } catch (ClassNotFoundException e1) {
              e1.printStackTrace();
            } catch (IOException e1) {
              break;
            }

            try {
              // ...and wait till writer is done writing.
              lock.wait();
            } catch (InterruptedException e) {
              e.printStackTrace();
              break;
            }

          }
        }
      }
    });

    // For doing a rough performance measurement.
    Instant start = Instant.now();
    writer.start();
    reader.start();

    // Wait till both threads are done.
    writer.join();
    reader.join();

    Instant end = Instant.now();
    Duration timeElapsed = Duration.between(start, end);
    System.out.format("Took %sms for %d objects\n", timeElapsed.toMillis(), COUNT);
    System.out.format("Avg: %.3fms/object\n", ((double) timeElapsed.toMillis() / COUNT));

    // Cleanup
    oos.close();
    fos.close();
    ois.close();
    fis.close();
  }

}

基本上,我们使用synchronizenotifywait用文件模拟FIFO缓冲区。

请注意,为了简洁和易读起见,我采取了一些捷径,但我想您已经明白了。读者应不时检查文件大小(多久一次取决于数据大小),然后截断文件,错误处理几乎不存在。我从该类和一个数据类创建了一个jar,下面是一些示例结果:

$ for i in {1..10}; do java -jar target/objpersistencedemo-0.0.1-SNAPSHOT.jar 20000; done
20000
Took 1470ms for 20000 objects
Avg: 0,074ms/object
20000
Took 1510ms for 20000 objects
Avg: 0,076ms/object
20000
Took 1614ms for 20000 objects
Avg: 0,081ms/object
20000
Took 1600ms for 20000 objects
Avg: 0,080ms/object
20000
Took 1626ms for 20000 objects
Avg: 0,081ms/object
20000
Took 1620ms for 20000 objects
Avg: 0,081ms/object
20000
Took 1489ms for 20000 objects
Avg: 0,074ms/object
20000
Took 1604ms for 20000 objects
Avg: 0,080ms/object
20000
Took 1632ms for 20000 objects
Avg: 0,082ms/object
20000
Took 1564ms for 20000 objects
Avg: 0,078ms/object

请注意,这些值是用于编写阅读内容的。我猜每个对象少于0.1ms比从RDBMS进行写入和随后读取要快得多。

如果让读者将消息发送到RabbitMQ实例并添加一些截断和退避逻辑,则可以基本上确保所有事件都在缓冲区文件中或写入RabbitMQ。