Как наиболее эффективно получить доступ к файлу из нескольких потоков? - PullRequest
1 голос
/ 03 ноября 2019

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

Первая цель - сделать чтение / запись файла безопасным . Для этой цели я создал вспомогательный класс FileReaderWriter, предоставляющий некоторые статические методы для поточно-ориентированного доступа к файлам. Методы чтения и записи координируются ReentrantReadWiteLock. Правило довольно простое: несколько потоков могут читать из файла в любое время, если никакой другой поток не пишет в него одновременно.

public class FileReaderWriter {
    private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public static List<String> read(Path path) {
        List<String> list = new ArrayList<>();
        rwLock.readLock().lock();
        try {
            list = Files.readAllLines(path);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
        return list;
    }

    public static void write(Path path, List<String> list) {
        rwLock.writeLock().lock();
        try {
            Files.write(path, list);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

Затем каждый сервелт может использовать вышеуказанный метод для файлачитаем так:

String dataDir = getServletContext().getInitParameter("data-directory");
Path filePath = Paths.get(dataDir, "test.txt");
ArrayList<String> list  = FileReaderWriter.read(filePath);

Аналогично, запись может быть выполнена методом FileReaderWriter.write(filePath, list). Примечание: если некоторые данные необходимо заменить или удалить (что означает выборку данных из файла, обработку их и запись обновленных данных обратно в файл), то все пути кода для этой операции должны быть заблокированы rwLock.writeLock() для атомарностипричины.

Теперь, когда доступ к общему файлу кажется безопасным (по крайней мере, я на это надеюсь), следующий шаг - сделать его быстрым . С точки зрения масштабируемости, чтение файла по запросу каждого пользователя сервлету не звучит разумно. Итак, я подумал о том, чтобы прочитать содержимое файла в ArrayList (или другую коллекцию) только один раз за время инициализации контекста, а затем поделиться этим ArrayList (а не файлом) в качестве атрибута держателя контекста в области видимости. Затем атрибут контекстной области может совместно использоваться сервлетами с тем же механизмом блокировки, как описано выше, и содержимое обновленного ArrayList может независимо сохраняться обратно в файл на некоторой регулярной основе.

Другим решением (во избежание блокировки) будет использование CopyOnWriteArrayList (или некоторого другого набора из пакета java.util.concurrent) для хранения общих данных и назначение однопоточного ExecutorService для выгрузки егосодержимое в файл, когда это необходимо. Я также слышал о Java-отображаемых файлах для отображения всего файла во внутреннюю память, но не уверен, подходит ли такой подход для данной конкретной ситуации.

Итак, кто-нибудь может, пожалуйста, подробно рассказать мне о наиболее эффективных способах (возможно, предложить некоторые другие альтернативы) для решения проблемы с доступом к файлу общего доступа, при условии, что запись в файл является довольно редкой иего содержание не должно превышать десятков строк.

1 Ответ

1 голос
/ 03 ноября 2019

Вы не объясняете свою реальную проблему, только ваша текущая попытка трудно обеспечить хорошее решение.

У вашего подхода есть две серьезные проблемы:

Задача 1: concurrency

общий ресурс, представляющий собой простой текстовый файл на стороне сервера, содержащий несколько строк изменяемых данных

90% решения дляПроблема в хорошей структуре данных. Изменчивый файл это не так. Даже популярные движки баз данных имеют важные ограничения параллелизма (например, SQLite ), не пытайтесь заново изобретать колесо.

Проблема 2: горизонтальная масштабируемость

Даже если он решит свои локальные проблемы параллелизма (например, синхронные методы), вы не сможете развернуть несколько экземпляров (узлов / серверов) своего приложения.

Решение 1: используйтеправильный инструмент для работы

Вы не можете точно объяснить природу вашей проблемы (управления данными), но, вероятно, любая база данных NoSQL пойдет вам на пользу (чтение о )MongoDB может быть хорошей отправной точкой).

(Плохое) решение 2: используйте FileLock

Если по какой-то причине вы настаиваетевыполняя то, что вы указываете, используйте низкоуровневые блокировки файлов, используя FileLock . Вам придется иметь дело только с частичными блокировками файлов, и даже они могут быть распределены горизонтально. Вам также не придется беспокоиться о синхронизации других ресурсов, так как будет достаточно блокировок на уровне файлов.

(Limited) решение 3: в структуре памяти

Если выгоризонтальная масштабируемость не требуется, вы можете использовать разделяемую структуру памяти, такую ​​как ConcurrentHashMap , но вы потеряете горизонтальную масштабируемость и потеряете транзакции, если не сохраните информацию до остановки приложения.

Заключение

Хотя существуют более экзотические модели распределенных данных, использование базы данных даже для одной таблицы может быть лучшим и самым простым решением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...