17 Eylül 2020 Perşembe

LoadingCache Arayüzü - Kullanmayın Caffeine Daha İyi

Giriş
Giriş
Şu satırı dahil ederiz
import google.common.cache.LoadingCache;
Cache arayüzünden kalıtır. Açıklaması şöyle.
LoadingCache should be used when you know how to populate it with a given key (the loader can load a value for any key it is invoked with).
Yani bu nesne eğer aranılan nesne kendisinde yoksa bir CacheLoader kullanarak yükler. Dolayısıyla
Bu nesneyi yaratırken kullanılan CacheBuilder sınıfının build() metoduna CacheLoader nesnesi geçilir.

Multi Threaded Kullanım
Açıklaması şöyle
Guava doesn't have an AsyncLoadingCache, but its LoadingCache will discard refresh calls if an existing load for that entry is in-flight. 
constructor
Şöyle yaparız. Burada CacheBuilder sınıfının build() metoduna CacheLoader geçildiği görülebilir.
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
  .maximumSize(100000)
  .expireAfterAccess(5, TimeUnit.MINUTES)
  .build(
    new CacheLoader<String, Integer>() {
      public Integerload(String user) {
        return ...;
      }
    }
);
get metodu
Şöyle yaparız
CacheLoader<String, T> loader = new CacheLoader<>() {
  @Override
  public T load(String key) {
    return "...";
  }
};

LoadingCache<String, T> loadingCache = CacheBuilder.newBuilder()
  .expireAfterWrite(expiryDuration, timeUnit)
  .concurrencyLevel(Runtime.getRuntime().availableProcessors())
  .build(loader);

public T get(String key) throws ExecutionException {
  return loadingCache.get(key);
}
getIfPresent metodu
Şöyle yaparız.
LoadingCache<Integer, ConcurrentHashMap<Point, Boolean>> loadingCache = ...;

ConcurrentHashMap<Point, Boolean> points = loadingCache.getIfPresent(...);
if (points == null) {
  ...
}
getUnchecked metodu
CacheLoader sınıfının load() metodunu tetikler.
Örnek
Şöyle yaparız.
LoadingCache<Long, String> myCache = ...;

public String myget(Long key) {
  String v = myCache.getUnchecked(key);
  
  if (v == "INITIAL") {...}
}
refresh metodu
refreshAfterWrite() gibi ayarlar bu metodu tetikler. CacheLoader sınıfının reload() metodunu tetikler. Açıklaması şöyle.
Returns without doing anything if another thread is currently loading the value for {@code key}. If the cache loader associated with this cache performs refresh asynchronously then this method may return before refresh completes.
Yani bir başka thread asenkron olarak refresh() işlemini başlattıysa, yeni bir thread başlatmaz ve mevcut değeri döner. Açıklaması şöyle
It sees another refresh thread is running and it just returns the old data without spawning a new thread.
Örnek
Şöyle yaparız.
LoadingCache<Long, String> myCache = ...;

public String myget(Long key) {
  String v = myCache.getUnchecked(key);

  if (v == "INITIAL") {
    myCache.refresh(key);
    myCache.getUnchecked(key);

  }

  return v;
}


16 Eylül 2020 Çarşamba

CacheLoader Sınıfı - Kullanmayın Caffeine Daha İyi

Giriş
Şu satırı dahil ederiz.
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
Kullanım
Şöyle yaparız. Burada CacheLoader'ın null döndürebileceği kontrol edilmiyor. Normalde etmek lazım.
LoadingCache<String, Plan> something = 
   CacheBuilder.newBuilder()
     .concurrencyLevel(10)
     .maximumSize(1000) // Maximum of 1000 records can be cached
     .expireAfterWrite(60, TimeUnit.MINUTES) // Cache will expire after 60 minutes
     .build(new CacheLoader<String, Plan>() { // Build the CacheLoader
       @Override
       public Plan load(String key) throws Exception{
        Plan record = getGraphFromDB(key);
        return record;
      }
});
asyncReloading metodu
Bir Executor tarafından çalıştırılan reload() metodunu kolayca tanımlamayı sağlar.
Örnek
Şöyle yaparız.
LoadingCache<String, Graph> cache1 = CacheBuilder.newBuilder()
  .refreshAfterWrite(15, TimeUnit.MINUTES)
 .build(CacheLoader.asyncReloading(CacheLoader.from(this::getGraphFromDB),
    Executors.newSingleThreadExecutor()));
Örnek
Şöyle yaparız
LoadingCache<String, Graph> loadingCacheSuccint = CacheBuilder.newBuilder()
  .refreshAfterWrite(15, TimeUnit.MINUTES)
  .recordStats()
  .build(CacheLoader.asyncReloading(CacheLoader.from(this::getGraphFromDB),
         Executors.newSingleThreadExecutor())
);
load metodu
İmzası şöyle.
public V load(K key);
Açıklaması şöyle. Yani null dönemez
Returns:
  the value associated with key; must not be null
Throws:
  Exception - if unable to load the result
Eğer load işlemi null dönerse exception fırlatmak gerekir. Şöyle yaparız
new CacheLoader<ObjectId, User>() {
  @Override
  public User load(ObjectId k) throws Exception {
    User u = DataLoader.datastore.find(User.class).field("_id").equal(k).get();
    if (u != null) {
      return u;
    } else {
      throw new UserNotFoundException();
    }
  }
}
LoadingCache sınıfının getUnchecked() metodu çağrılınca tetiklenir. Şöyle yaparız.
String v = myCache.getUnchecked(key);
Örnek
Şöyle yaparız.
new CacheLoader<String, Graph>() {
  @Override
  public Graph load(String key) {
    return ...;
  }
  ...
});
Örnek
Şöyle yaparız.
new CacheLoader<Long, String>() {

  public String load(Long key) {
    System.out.println("load() " + key);
    return "INITIAL";
  }
  ...
});
loadAll metodu
İmzası şöyle.
public Map<K, V> loadAll(Iterable<? extends K> ids);
LoadingCache sınıfının getAll() metodu çağrılınca tetiklenir.
Örnek
Şöyle yaparız.
this.cache = CacheBuilder.newBuilder().maximumSize(1000).build(
  new CacheLoader<String, NamedEntity>() {

    @Override
    public Map<String, NamedEntity> loadAll(Iterable<? extends String> keys)
    throws Exception {
      Map<String, NamedEntity> map = new ConcurrentHashMap<>();
      ...
      return map;
  }
});


try {
  entities = cache.getAll(keys);
} catch (Exception e) {
  e.printStackTrace();
}
reload metodu
Açıklaması şöyle.
CacheLoader.reload() takes a key and old value and returns a ListenableFuture.
Eğer bu metodu override etmezsek load() metodunu çağırır.Açıklaması şöyle.
... default implementation calls load() synchronously, 
LoadingCache sınıfının refresh() metodu çağrılınca tetiklenir. Şöyle yaparız.
myCache.refresh(key);
Örnek - asenkron çalışma
Şöyle yaparız. Burada işi kendi ExecutorService nesnemize havale ediyoruz
LoadingCache<String, Graph> cache = CacheBuilder.newBuilder()
  .refreshAfterWrite(2, TimeUnit.MINUTES)
  .recordStats()           
   .build(new CacheLoader<String, Graph>() {
    @Override
    public Graph load(String key) {
      return ...;
    }

    public ListenableFuture<Graph> reload(final String key, Graph prev) {
      ListenableFutureTask<Graph> task = ListenableFutureTask.create(
      new Callable<Graph>() {
        public Graph call() {
          Graph graph = ...
          ...
          return graph;
        }
      });
      executor.execute(task);
      return task;
  }
});
Örnek - asenkron çalışma
Şöyle yaparız. Burada işi kendi ExecutorService nesnemize havale ediyoruz
new CacheLoader<Long, String>() {

  ExecutorService executor = Executors.newFixedThreadPool(10);

  public ListenableFuture<String> reload(final Long key, String prevString) {
    // asynchronous!
    ListenableFutureTask<String> task = ListenableFutureTask.create(() -> {
      ...
      return "Calculated value for " + key;
    });
    executor.execute(task);
    return task;
  }  
});
Örnek
Şöyle yaparız. Burada kendi ExecutorService sınıfımız yerine redis kullanılıyor.
@Override
public ListenableFuture<Object> reload(RedisKey<?> key, Object oldValue)
  throws Exception {
  CompletionStage<byte[]> f = redisClusterClient.asyncGet(key.getActualKey())
    .asCompletionStage();
  ListenableFutureTask<Object> task = ListenableFutureTask.create(
    () -> f.toCompletableFuture().get());
  f.thenRun(task::run);
  return task;
}
Açıklaması şöyle.
My RedisClient has a asyncGet() function, so I wanted to utilize it instead of maintaining a executorservice of my own and submitting it a ListenableFutureTask