我编写了一个线程安全类来从多个线程获取输入,并在结果运行到固定大小后将结果上传到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()));
我担心的是课程不可扩展,我的意思是当数据越来越多时,它可能成为系统的瓶颈。我可以想出两种方法来改善这种状况:
- 异步执行耗时的上传操作:使用执行程序上传并在关机钩子中调用ThreadPoolExecutor.awaitTermination()。
- 只是在导出方法中将数据放入LinkedBlockingQueue并使用多个线程来处理它。(这种方式比我理解的第一个更具可扩展性)
醇>
然后我需要在关闭钩子线程中做更多的工作,以确保不会丢失已接受的数据,这不是我所知道的好主意。我会冒重新启动应用程序时丢失数据的风险,这是我想要看到的最后一件事。
我的问题
- 我对可扩展性的关注是一个真正的问题吗?(为了使问题更简单,让我们说数据大小是几个字节而调用导出方法的TPS是500)
- 如果第一个问题的答案是肯定的,那么我的改进是什么,他们是对的吗?如何进行清理工作以避免丢失数据?
醇>
答案 0 :(得分:0)
可扩展性取决于需求,约束,期望的服务级别,个人偏好,预期的用户增长率,尤其是金钱:给定无限的资源,每个软件都可以扩展。你没有提到任何,所以我猜你没有任何实际的数字。在此阶段,作为程序员,您的工作是制作使用可预测数量资源的正确计划。
您的计划似乎是正确的,您的大部分假设也是正确的。但是我建议立即将块存储到某个本地持久数据库(或原始文件系统)并定期作业,在一个单独的线程中运行,将一组块上传到S3,并删除任何关闭钩子(可以使用Camel作为无聊的部分)。这是因为这样的钩子是不可靠的,并且应该仅用作最后的资源以进行快速和可选的清理(在必须准备清理无法正常运行直到结束的意义上,可选)。
使用文件而不是内存,您的数据可以承受致命错误,并且您的应用程序所需的工作内存几乎与加载无关:有一个无关的额外CPU和一些磁盘I / O比内存便宜得多