为了账号安全,请及时绑定邮箱和手机立即绑定

Java REST 优化数据结构访问

Java REST 优化数据结构访问

海绵宝宝撒 2022-05-12 18:35:02
我有一个 Java REST 应用程序,其中一个端点总是处理ConcurrentMap. 我正在做负载测试,当负载测试开始增加时真的很糟糕。为了提高应用程序的效率,我可以实施哪些策略?我应该使用 Jetty 线程,因为它是我正在使用的服务器吗?还是主要是代码?或两者?成为瓶颈的方法如下。基本上我需要从给定文件中读取一些行。我不能将它存储在数据库中,所以我想出了一个用 Map 进行处理的方法。但是,我知道对于大文件,不仅需要很长时间才能到达线路,而且我冒着 Map 在有很多条目时会消耗大量内存的风险......dict是ConcurrentMap。public String getLine(int lineNr) throws IllegalArgumentException {    if (lineNr > nrLines) {        throw new IllegalArgumentException();    }    if (dict.containsKey(lineNr)) {        return dict.get(lineNr);    }    synchronized (this) {        try (Stream<String> st = Files.lines(doc.toPath())            Optional<String> optionalLine = st.skip(lineNr - 1).findFirst();            if (optionalLine.isPresent()) {                dict.put(lineNr, optionalLine.get());            } else {                nrLines = nrLines > lineNr ? lineNr : nrLines;                throw new IllegalArgumentException();            }        } catch (IOException e) {            e.printStackTrace();        }        return cache.get(lineNr);    }
查看完整描述

2 回答

?
ibeautiful

TA贡献1993条经验 获得超6个赞

该解决方案基于ConcurrentHashMap#computeIfAbsent,有两个假设:

  1. 多个线程读取同一个文件不是问题。

  2. 虽然文档说由于阻塞,计算应该简单而简短,但我相信这只是相同密钥(或存储桶/条带)访问的问题,并且仅适用于更新(而不是读取)?在这种情况下,这不是问题,因为我们要么成功计算了 value,要么 throw IllegalArgumentException

使用它,我们通过将其作为放置密钥所需的计算来实现每个密钥只打开一次文件。

public String getLine(int lineNr) throws IllegalArgumentException {

        if (lineNr > nrLines) {

            throw new IllegalArgumentException();

        }

        return cache.computeIfAbsent(lineNr, (l) -> {

            try (Stream<String> st = Files.lines(path)) {

                Optional<String> optionalLine = st.skip(lineNr - 1).findFirst();

                if (optionalLine.isPresent()) {

                    return optionalLine.get();

                } else {

                    nrLines = nrLines > lineNr ? lineNr : nrLines;

                    throw new IllegalArgumentException();

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

            return null;

        });

    }

我通过产生 3 个线程来“验证”第二个假设,其中:

  1. Thread1 通过无限循环(永远阻塞)来计算密钥 0。

  2. Thread2 尝试放入键 0,但从未这样做,因为 Thread1 阻塞。

  3. Thread3 尝试放入键 1,并立即这样做。

试试看,也许它有效,或者假设是错误的,它很糟糕。Map 在内部使用存储桶,因此即使使用不同的键,计算也可能成为瓶颈,因为它锁定了存储桶/条带。


查看完整回答
反对 回复 2022-05-12
?
绝地无双

TA贡献1946条经验 获得超4个赞

混ConcurrentMap在一起synchronized(this)可能不是正确的方法。包中的类java.util.concurrent是为特定用例设计的,并尝试在内部优化同步。


相反,我建议先尝试一个设计良好的缓存库,看看性能是否足够好。一个例子是咖啡因。根据Population docs,它为您提供了一种声明如何加载数据的方法,即使是异步的:


AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()

    .maximumSize(10_000)

    .expireAfterWrite(10, TimeUnit.MINUTES)

    // Either: Build with a synchronous computation that is wrapped as asynchronous 

    .buildAsync(key -> createExpensiveGraph(key));

    // Or: Build with a asynchronous computation that returns a future

    .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));


查看完整回答
反对 回复 2022-05-12
  • 2 回答
  • 0 关注
  • 150 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号