Java REST оптимизирует доступ к структуре данных - PullRequest
0 голосов
/ 20 января 2019

У меня есть приложение Java REST, где одна конечная точка всегда имеет дело с ConcurrentMap.Я делаю нагрузочные тесты, и это очень плохо, когда нагрузочный тест начинает увеличиваться.

Какие стратегии я могу реализовать, чтобы повысить эффективность приложения?

Должен ли я играть сJetty темы, как сервер я использую?Или это в основном код?Или и то, и другое?

Ниже описан метод, который становится узким местом.

В основном мне нужно прочитать какую-то строку из данного файла.Я не могу сохранить его в БД, поэтому я придумал эту обработку с картой.Тем не менее, я знаю, что для больших файлов потребуется не только длительное время, и я рискую тем, что Карта будет занимать много памяти, когда в ней много записей ...

dictConcurrentMap.

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 ]

0 голосов
/ 20 января 2019

Это решение основано на ConcurrentHashMap # computeIfAbsent , с двумя допущениями:

  1. Несколько потоков, читающих один и тот же файл, не являются проблемой.
  2. Хотя в документации сказано, что вычисления должны быть простыми и короткими из-за блокировки, я полагаю, что это проблема только для доступа с тем же ключом (или bucket / stripe) и только для обновлений (не для чтения)? В этом сценарии это не проблема, поскольку мы либо успешно вычисляем значение, либо бросаем 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 и делает это немедленно.

Попробуйте, может, это работает, или, может быть, предположения неверны и это отстой. Карта использует внутренние сегменты, поэтому вычисления могут стать узким местом даже при использовании разных ключей, поскольку она блокирует сегмент / полосу.

0 голосов
/ 20 января 2019

Смешивание ConcurrentMap с synchronized(this), вероятно, не правильный подход. Классы из пакета java.util.concurrent предназначены для конкретных случаев использования и пытаются оптимизировать внутреннюю оптимизацию.

Вместо этого я бы предложил сначала попробовать хорошо спроектированную библиотеку кэширования и посмотреть, достаточно ли высока производительность. Одним из примеров будет кофеин . В соответствии с Population документами, вы можете объявить, как загружать данные, даже асинхронно:

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));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...