Как обрабатывать кэш пропускает: NotFoundException, содержит () или `if (null == результат)`? - PullRequest
4 голосов
/ 30 июля 2009

Может быть, это немного академично, но если я реализую кеш для ускорения приложения, как мне лучше всего обрабатывать промахи кеша? (В моем случае языком будет Java, но, возможно, ответ может быть более общим.)

Брось исключение:

ResultType res;
try {
    res = Cache.resLookup(someKey);
} catch (NotFoundException e) {
    res = Cache.resInsert(someKey, SlowDataSource.resLookup(someKey));
}

Спросите перед извлечением:

ResultType res;
if (Cache.contains(someKey) {
    res = Cache.resLookup(someKey);
} else {
    res = Cache.resInsert(someKey, SlowDataSource.resLookup(someKey));
}

Возврат null:

ResultType res;
res = Cache.resLookup(someKey);
if (null == res) {
    res = Cache.resInsert(someKey, SlowDataSource.resLookup(someKey));
}

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

Ответы [ 7 ]

6 голосов
/ 30 июля 2009

Первое, я думаю, излишне и не очень полезно для исключений. Есть ли у вас ожидание, что будет попадание в кэш? Я думаю, промах кеша - это вполне нормальное явление, и, таким образом, исключением становится просто управление потоком. Не хорошо имхо.

Второе - состояние гонки. Существует задержка между проверкой существования записи в кеше и запросом к ней. Это может привести к всевозможным неприятностям.

Возвращение нуля, вероятно, уместно в общем смысле, но это сопровождается некоторыми оговорками.

Во-первых, что это за кеш? В идеале вы будете разговаривать с сквозным кешем, и в этом случае, если его нет в кеше, он просто получит его из источника, что не соответствует стилю написанного вами кода.

Во-вторых, вставка get then - еще одно условие гонки. Посмотрите на интерфейс для ConcurrentHashMap, чтобы найти хороший общий способ решения подобных проблем. В частности, вызов putIfAbsent() является атомарной операцией, которая эквивалентна вашему третьему вызову.

4 голосов
/ 30 июля 2009

Последний вариант (если ноль == результат) является лучшим, на мой взгляд.

Отсутствие кэша не является исключительным условием и должно обрабатываться в обычном потоке кода.

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

2 голосов
/ 30 июля 2009

А как насчет 4-го варианта? Вы могли бы использовать держатель для возвращаемого значения, и чтобы поиск возвращал логическое значение для успеха:

ResultHolder result = new ResultHolder();
if(!cache.resLookup(someKey, result))
{
    // get from slower source and insert to cache
}

if(result.value == null)
{
    // special case if you wanted null as a valid value
}

Это, в основном, ваш третий вариант, сохранение одного вызова, но если вы хотите иметь значение null, вы можете.

1 голос
/ 30 июля 2009

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

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

0 голосов
/ 30 июля 2009

С точки зрения «чистого кода»

  1. Отсутствие кэша - не исключение, а обычный случай. Так что я бы не использовал здесь исключение.
  2. Следует по возможности избегать нулевых значений. Вместо этого выберите что-то осмысленное
  3. Это оставляет опцию 'ask before fetch' или вариант вашего третьего варианта, где вы не возвращаете 'null', а специальный объект ResultType, который сигнализирует об отсутствии кэша

Пример:

public class ResultType {

  public final static ResultType CACHE_MISS = new ResultType();

  // ... rest of the class implementation

}

и позже

ResultType res;
res = Cache.resLookup(someKey);
if (ResultType.CACHE_MISS == res) {
    res = Cache.resInsert(someKey, SlowDataSource.resLookup(someKey));
}

Преимущество перед «нулевым» решением: теперь читатель сразу узнает, что «если» обрабатывает промах кэша.

0 голосов
/ 30 июля 2009

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

С помощью (2) вы должны сделать два поиска в кэше (сначала «содержит», а затем «resLookup»).

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

0 голосов
/ 30 июля 2009

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

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