Java REST 优化数据结构访问

我有一个 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);

    }


海绵宝宝撒
浏览 151回答 2
2回答

ibeautiful

该解决方案基于ConcurrentHashMap#computeIfAbsent,有两个假设:多个线程读取同一个文件不是问题。虽然文档说由于阻塞,计算应该简单而简短,但我相信这只是相同密钥(或存储桶/条带)访问的问题,并且仅适用于更新(而不是读取)?在这种情况下,这不是问题,因为我们要么成功计算了 value,要么 throw&nbsp;IllegalArgumentException。使用它,我们通过将其作为放置密钥所需的计算来实现每个密钥只打开一次文件。public String getLine(int lineNr) throws IllegalArgumentException {&nbsp; &nbsp; &nbsp; &nbsp; if (lineNr > nrLines) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new IllegalArgumentException();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return cache.computeIfAbsent(lineNr, (l) -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try (Stream<String> st = Files.lines(path)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Optional<String> optionalLine = st.skip(lineNr - 1).findFirst();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (optionalLine.isPresent()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return optionalLine.get();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nrLines = nrLines > lineNr ? lineNr : nrLines;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new IllegalArgumentException();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (IOException e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; }我通过产生 3 个线程来“验证”第二个假设,其中:Thread1 通过无限循环(永远阻塞)来计算密钥 0。Thread2 尝试放入键 0,但从未这样做,因为 Thread1 阻塞。Thread3 尝试放入键 1,并立即这样做。试试看,也许它有效,或者假设是错误的,它很糟糕。Map 在内部使用存储桶,因此即使使用不同的键,计算也可能成为瓶颈,因为它锁定了存储桶/条带。

绝地无双

混ConcurrentMap在一起synchronized(this)可能不是正确的方法。包中的类java.util.concurrent是为特定用例设计的,并尝试在内部优化同步。相反,我建议先尝试一个设计良好的缓存库,看看性能是否足够好。一个例子是咖啡因。根据Population docs,它为您提供了一种声明如何加载数据的方法,即使是异步的:AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()&nbsp; &nbsp; .maximumSize(10_000)&nbsp; &nbsp; .expireAfterWrite(10, TimeUnit.MINUTES)&nbsp; &nbsp; // Either: Build with a synchronous computation that is wrapped as asynchronous&nbsp;&nbsp; &nbsp; .buildAsync(key -> createExpensiveGraph(key));&nbsp; &nbsp; // Or: Build with a asynchronous computation that returns a future&nbsp; &nbsp; .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java