Эффективное кэширование и обновление текущей даты в долгоживущем серверном приложении. - PullRequest
1 голос
/ 16 февраля 2012

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

Для обслуживания каждого запроса.часть приложения должна знать текущую дату (хотя не время) и должна храниться в объекте java.util.Date из-за стороннего API.

Однако, объекты Date дорогичтобы конструировать так, создание нового для каждого запроса не имеет смысла.

Разделение объекта Date между запросами и его обновление один раз в день означало бы, что должен быть создан только один объект (на серверрабочий поток) при запуске, но тогда как вы можете обновить его безопасным способом?

Например, использование ScheduledExecutorService, которое запускается сразу после полуночи, может увеличить Date, но вводит синхронизацию потока вmix: объект Date теперь используется совместно основным потоком и потоком, который ScheduledExecutorService порождает для запуска задачи обновления.

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

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

Ответы [ 5 ]

2 голосов
/ 16 февраля 2012

Я предполагаю, что дорогой конструктор, о котором вы говорите, это new Date(), который вызывает System.currentTimeMillis ().Самый простой выход - использовать new Date(long), используя значение, хранящееся в поле volatile.Внешний поток может затем обновить это поле в соответствующее время, и другие потоки создадут свои Date объекты из этого обновленного значения.

Редактировать: в то время как текущий вопрос может показаться преждевременной оптимизацией, System.currentTimeMillis()иногда может быть узким местом.Проверьте эту ссылку, если вы в этой ситуации: http://dow.ngra.de/2008/10/27/when-systemcurrenttimemillis-is-too-slow/

2 голосов
/ 16 февраля 2012

Не беспокойтесь об этой оптимизации. Это не будет иметь ощутимого эффекта. Пустая трата времени.

1 голос
/ 17 февраля 2012

@ chrisbunney, пара ответов в этой теме предложила использовать специальные классы параллелизма, но если вам действительно нужно было кэшировать дату, как вы изначально просили, вам нужно только одно: ключевое слово volatile .

AtomicReference хорошо, если вам нужна атомарная операция проверки и замены, но в этом случае вы ничего не проверяете. Вы просто устанавливаете ссылку на новое значение и хотите, чтобы это значение было видимым для всех потоков. Вот что делает volatile.

Если вы изменяли внутреннее состояние существующего объекта, то вам могут понадобиться блокировки. Или, если вы собирались прочитать значение существующего Date, сделать некоторые вычисления на его основе и сгенерировать новый объект Date в результате, снова какой-то тип блокировки (или что-то вроде AtomicReference) будет необходимо. Но вы этого не делаете; опять же, вы заменяете только ссылку, без учета ее предыдущего значения.

В общем, если у вас только один поток , который когда-либо заменяет значение, а другие потоки только читают значение, достаточно volatile. Никакой другой контроль параллелизма не требуется. Или, если у вас есть несколько потоков, которые могут заменить значение, но они только заменяют, а не изменяют на месте, и заменяют его, не обращая внимания на его предыдущее значение, то опять-таки достаточно volatile. Больше ничего не нужно.

1 голос
/ 17 февраля 2012

Я настоятельно рекомендую вам использовать функцию кэширования из библиотеки Google Guava .Библиотека кэширования имеет хорошую поддержку параллелизма и предоставляет несколько альтернативных подходов к вашей проблеме.

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

LoadingCache<Object, Date> tDateCache 
    = CacheBuilder.newBuilder()
                  .maximumSize(1)
                  .expireAfterWrite(100, TimeUnit.SECONDS)
                  .build( new CacheLoader<Object, Date>() {
                              public Date load(Object object) {
                                     return new Date();
                                     }
                              });

Для доступа к кэшированной дате используйте tDateCache.get(tDateCache).Параметр expireAfterWrite(100, TimeUnit.SECONDS) приведет к автоматическому обновлению кеша каждые 100 секунд.

У вас также может быть один поток монитора для автоматического вызова Cache.invalidateAll() каждый раз, когда вы обнаружите, что день катится, что приведет к загрузкеметод повторного вызова загрузчика кэша и создания новой даты.Не нужно беспокоиться о параллелизме, библиотека справится с этим за вас.

1 голос
/ 16 февраля 2012

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicReference.html может избежать накладных расходов на синхронизацию.Если существует один поток, выполняющий запросы, вы можете выполнить задачу в том же потоке, чтобы обновить дату без каких-либо накладных расходов на синхронизацию.Если потоков несколько, то эту же идею можно применить, сделав поток в поле даты локальным и обновив его отдельно в каждом потоке.

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