从多个线程获取输入并将具有固定大小的文件上载到S3

时间:2015-05-17 09:24:18

标签: java multithreading

我编写了一个线程安全类来从多个线程获取输入,并在结果运行到固定大小后将结果上传到S3。

S3Exporter类

// this class is thread safe.
public class S3Exporter {
    private static final int BUFFER_PADDING = 1000;
    private final int targetSize;
    private final ByteArrayOutputStream buf;
    private volatile boolean started;

    public S3Exporter(final int targetSize) {
        buf = new ByteArrayOutputStream(targetSize + BUFFER_PADDING);
        this.targetSize = targetSize;
        started = false;
    }

    public synchronized void start() {
        started = true;
    }

    public synchronized void end() {
        started = false;
        flush();
    }

    public synchronized void export(byte[] data) throws IOException {
        Preconditions.checkState(started, "Not started!");
        buf.write(b, buf.size(), b.length);
        flushIfNeeded();
    }

    private void flushIfNeeded() {
        if (buf.size() >= targetSize) {
            flush();
        }
    }

    public synchronized void flush() {
        if (buf.size() > 0) {
            // upload buf to s3, it's a time-consuming operation
            buf.reset();
        }
    }
}

客户端调用export方法传递数据,如果抛出异常,客户端将在稍后传递该数据。 为避免在重新启动应用程序时丢失数据,我在创建S3Exporter对象时添加了一个关闭钩子:

    S3Exporter exporter = new S3Exporter(10000);
    Runtime.getRuntime().addShutdownHook(new Thread(() -> exporter.end()));

我担心的是课程不可扩展,我的意思是当数据越来越多时,它可能成为系统的瓶颈。我可以想出两种方法来改善这种状况:

  
      
  1. 异步执行耗时的上传操作:使用执行程序上传并在关机钩子中调用ThreadPoolExecutor.awaitTermination()。
  2.   
  3. 只是在导出方法中将数据放入LinkedBlockingQueue并使用多个线程来处理它。(这种方式比我理解的第一个更具可扩展性)
  4.   

然后我需要在关闭钩子线程中做更多的工作,以确保不会丢失已接受的数据,这不是我所知道的好主意。我会冒重新启动应用程序时丢失数据的风险,这是我想要看到的最后一件事。

我的问题

  
      
  1. 我对可扩展性的关注是一个真正的问题吗?(为了使问题更简单,让我们说数据大小是几个字节而调用导出方法的TPS是500)
  2.   
  3. 如果第一个问题的答案是肯定的,那么我的改进是什么,他们是对的吗?如何进行清理工作以避免丢失数据?
  4.   

1 个答案:

答案 0 :(得分:0)

可扩展性取决于需求,约束,期望的服务级别,个人偏好,预期的用户增长率,尤其是金钱:给定无限的资源,每个软件都可以扩展。你没有提到任何,所以我猜你没有任何实际的数字。在此阶段,作为程序员,您的工作是制作使用可预测数量资源的正确计划。

您的计划似乎是正确的,您的大部分假设也是正确的。但是我建议立即将块存储到某个本地持久数据库(或原始文件系统)并定期作业,在一个单独的线程中运行,将一组块上传到S3,并删除任何关闭钩子(可以使用Camel作为无聊的部分)。这是因为这样的钩子是不可靠的,并且应该仅用作最后的资源以进行快速和可选的清理(在必须准备清理无法正常运行直到结束的意义上,可选)。

使用文件而不是内存,您的数据可以承受致命错误,并且您的应用程序所需的工作内存几乎与加载无关:有一个无关的额外CPU和一些磁盘I / O比内存便宜得多