Как надежно удалить записи из Guava LoadingCache?

Я использую гуаву LoadingCache заполнить некоторые данные в него, и я хочу удалить все записи из этого LoadingCache каждую 1 минуту

public class MetricHolder {
  private final ExecutorService executor = Executors.newFixedThreadPool(2);
  private final LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
      CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
          .removalListener(RemovalListeners.asynchronous(new SendToDatabase(), executor))
          .build(new CacheLoader<String, AtomicLongMap<String>>() {
            @Override
            public AtomicLongMap<String> load(String key) throws Exception {
              return AtomicLongMap.create();
            }
          });

  private static class Holder {
    private static final MetricHolder INSTANCE = new MetricHolder();
  }

  public static MetricHolder getInstance() {
    return Holder.INSTANCE;
  }

  private MetricHolder() {}

  public void increment(String clientId, String name) throws ExecutionException {
    clientIdMetricCounterCache.get(clientId).incrementAndGet(name);
  }

  public LoadingCache<String, AtomicLongMap<String>> getClientIdMetricCounterCache() {
    return clientIdMetricCounterCache;
  }

  private static class SendToDatabase implements RemovalListener<String, AtomicLongMap<String>> {
    @Override
    public void onRemoval(RemovalNotification<String, AtomicLongMap<String>> notification) {
      String key = notification.getKey();
      AtomicLongMap<String> value = notification.getValue();
      System.out.println(key);
      System.out.println(value);
      // sending these key/value to some other system

    }
  }
}

я звоню increment метод из множества разных мест в коде многопоточным способом. Таким образом, в течение 1 минуты он будет заполнять множество метрик в clientIdMetricCounterCache, Теперь я хочу надежно отбрасывать все эти метрики через каждую минуту и ​​отправлять все эти метрики в базу данных.

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

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

Так как же expireAfterWrite работает? Работает ли он как планировщик, который будет запускаться каждую минуту и ​​удалять все записи, которые есть в clientIdMetricCounterCache а потом снова он проснется через 1 минуту и ​​удалит все записи из того же кэша и продолжит в том же духе? После прочтения вики я сомневаюсь, что это так работает. Если этого не произойдет, то как я могу надежно удалять эти записи каждую минуту и ​​отправлять их в какую-либо другую систему, поскольку мои записи могут быть редкими в течение некоторого времени?

Похоже, мне придется использовать Guava TimeLimiter интерфейс и SimpleTimeLimiter или, может быть ScheduledExecutorService надежно тайм-аут вызова, а затем отбросить записи? Если да, кто-нибудь может привести пример, как это будет работать в моем текущем примере?

1 ответ

Решение

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

О единственной функции, которую вы используете, является аспект загрузки, и это не стоит того.

Я бы предложил использовать AtomicReference<ConcurrentHashMap<String, AtomicLongMap>> вместо:

  • При обновлении вы получаете версию для текущей минуты через AtomicReference::get,
  • С использованием clientIdсмотришь AtomicLongMap в вашем ConcurrentHashMap и создайте новый, если не найден (используйте putIfAbsent на Java 7 или computeIfAbsent на Java 8).
  • С использованием nameВы обновляете AtomicLongMap так же, как вы отправили.
  • Раз в минуту вы заменяете все через AtomicReference::getAndSet,

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

Он будет производить больше мусора, чем оригинальный подход, но весь этот мусор будет недолгим, так что вы можете сделать GC более счастливым.

Это просто и не требует глубоких знаний библиотеки или деталей ее реализации.


Похоже, volatile вместо AtomicReference также сделал бы.

Другие вопросы по тегам