카페인 로컬 캐시

 

즉, Java 8의 고성능 캐시 라이브러리 인 GuavaCache를 죽입니다. Caffeine은 로컬 캐시의 왕입니다.

Caffeine은 최고의 캐시 라이브러리에 가까운 Java 8 기반의 고성능입니다.

Caffeine은 Google Guava에서 영감을 얻은 API를 사용하여 메모리 캐싱을 제공합니다. 개선 사항은 Guava 캐시 및 ConcurrentLinkedHashMap 설계 경험에 따라 달라집니다.

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .refreshAfterWrite(1, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));

머리말:

공식 소개 Caffeine은 JDK8 기반의 고성능 로컬 캐시 라이브러리로 거의 완벽한 적중률을 제공합니다. JDK의 ConcurrentMap과 약간 유사합니다. 사실 Caffeine의 LocalCache 인터페이스는 JDK의 ConcurrentMap 인터페이스를 구현하지만 두 가지가 정확히 동일하지는 않습니다. 가장 근본적인 차이점은 ConcurrentMap은 추가 된 모든 요소를 ​​표시하고 삭제하지 않는 한 (예 : remove 메서드 호출) 저장한다는 것입니다. 로컬 캐시는 일반적으로 자동 제거 전략으로 구성되어 애플리케이션을 보호하고, 메모리 사용량을 제한하고, 메모리 오버플로를 방지합니다.

Caffeine은 다음 특성을 충족 할 수있는 로컬 캐시를 생성하는 유연한 구성 방법을 제공합니다.

  1. 자동으로 데이터를 로컬 캐시에로드하고 비동기식으로 구성 할 수 있습니다.

  2. 수량에 따른 제거 전략;

  3. 만료 시간 제거 전략에 따라이 시간은 마지막 액세스 또는 쓰기에서 계산됩니다.

  4. 비동기 새로 고침;

  5. 키는 약한 참조로 패키지됩니다.

  6. 값은 메모리 누수없이 GC 해제 될 수 있도록 Weak 또는 Soft 참조로 패키징됩니다.

  7. 데이터 제거 알림;

  8. 방송 메커니즘 작성;

  9. 캐시 액세스를 계산할 수 있습니다.

상부 압력 테스트 :

비교는 다음과 같습니다.

공식적인 스트레스 테스트 결과를 보면 전체 읽기 씬이든 전체 쓰기 씬이든 혼합 읽기-쓰기 씬이든 8 스레드이든 16 스레드이든 Caffeine이 이기고 압도했으며 손입니다.

 

사용하다:

Caffeine은 여전히 ​​사용하기 매우 간단합니다. GuavaCache를 사용한 적이 있다면 Caffeine의 API 디자인이 GuavaCache를 많이 사용하기 때문에 훨씬 간단합니다. 먼저 Maven 종속성을 소개합니다.

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.8.4</version>
</dependency>
public static void main(String[] args) {
    Cache<String,String> cache = Caffeine.newBuilder()
        .maximumSize(1024)
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .weakKeys()
        .weakValues()
        .removalListener((RemovalListener<String,String>) (key,value,cause) ->
                System.out.println("key:"+ key + ",value:"+value + ",cause:"+cause.toString()))
        .build();

    //将数据放到本地缓存中
    cache.put("username","caffer");
    cache.put("password","123456");

    //此处可以设置过期时间
    try {
      Thread.sleep(4000L);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //从本地取出数据
    System.out.println(cache.getIfPresent("username"));
    System.out.println(cache.getIfPresent("password"));
    System.out.println(cache.get("bolog",key -> {
      return "从redis缓存获取";
    }));
  }
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
        // 数量上限
        .maximumSize(2)
        // 失效时间
        .expireAfterWrite(5, TimeUnit.MINUTES)
        .refreshAfterWrite(1, TimeUnit.MINUTES)
        // 异步加载机制
        .buildAsync(new CacheLoader<String, String>() {
            @Nullable
            @Override
            public String load(@NonNull String key) throws Exception {
                return getValue(key);
            }
        });
System.out.println(cache.get("username").get());
System.out.println(cache.get("password").get(6, TimeUnit.MINUTES));
System.out.println(cache.get("username").get(6, TimeUnit.MINUTES));
System.out.println(cache.get("blog").get());

 

만료 메커니즘

로컬 캐시의 만료 메커니즘은 로컬 캐시의 데이터가 비즈니스 데이터처럼 손실되지 않도록 보장 할 필요가 없기 때문에 매우 중요합니다. 로컬에서 캐시 된 데이터는 일반적으로 적중률을 보장한다는 전제하에 가능한 한 적은 메모리를 필요로하며 극단적 인 경우 GC가 떨어질 수 있습니다.

Caffeine의 만료 메커니즘은 주로 다음과 같이 Cache를 구성 할 때 선언됩니다.

  1. expireAfterWrite : 마지막 쓰기 후 만료되는 기간을 나타냅니다.

  2. expireAfterAccess : 마지막 액세스 (쓰기 또는 읽기) 후 만료되는 기간을 나타냅니다.

  3. expireAfter : 사용자 지정 만료 전략;

새로 고침 메커니즘

Cache를 구성 할 때 refreshAfterWrite 메서드를 통해 새로 고침주기를 지정합니다. 예를 들어 refreshAfterWrite (10, TimeUnit.SECONDS)는 10 초마다 새로 고침을 의미합니다.

.build(new CacheLoader<String, String>() {
    @Override
    public String load(String k) {
        // 这里我们就可以从数据库或者其他地方查询最新的数据
        return getValue(k);
    }
});

팁 : 카페인의 새로 고침 메커니즘은 "수동적" 입니다. 예를 들어 10 초마다 새로 고침을 선언하는 경우입니다. 시간 T에 v1에 액세스하여 값을 얻습니다. T + 5 초에 데이터베이스의 값이 v2로 업데이트되었습니다. 그러나 T + 12 초, 즉 10 초가 지나면 Caffeine을 통해 로컬 캐시에서 얻은 "still v1"v2 아닙니다. 이 획득 과정에서 Caffeine은 10 초가 지난 것을 발견하고 v2를 로컬 캐시에로드하고 v2는 다음에 획득 할 때 사용할 수 있습니다. 즉, 구현 원칙은 get 메서드에서 afterRead를 호출 할 때 데이터를 새로 고쳐야하는지 여부를 결정하기 위해 refreshIfNeeded 메서드를 호출하는 것입니다. 즉, 새로 고침 간격에 관계없이 로컬 캐시의 데이터를 읽지 않으면 로컬 캐시의 데이터는 항상 이전 데이터가됩니다!

 

제거 메커니즘

removalListener 메서드는 Cache를 구성 할 때 리스너 제거를 선언하는 데 사용할 수 있으므로 로컬 캐시에서 제거 된 데이터의 기록 정보를 추적 할 수 있습니다. RemovalCause.java의 열거 형 값에 따라 5 가지 제거 전략이 있습니다.

  • "EXPLICIT" : 삭제 된 데이터를 표시하려면 메서드 (예 : cache.invalidate (key), cache.invalidateAll)를 호출합니다.

  • "REPLACED" : 실제로 제거되지는 않지만 사용자가 이전 값을 덮어 쓰기 위해 일부 메서드 (예 : put (), putAll () 등)를 호출합니다.

  • "COLLECTED" : 캐시의 키 또는 값이 가비지 수집되었음을 나타냅니다.

  • "EXPIRED" : expireAfterWrite / expireAfterAccess는 합의 된 기간 내에 액세스 할 수 없으므로 제거됩니다.

  • "SIZE" : maximumSize 제한을 초과하는 요소 수가 제거 된 이유.

 

GuavaCache와 Caffeine의 차이점

  1. 제거 알고리즘 측면에서 GuavaCache는 "LRU" 알고리즘을 사용하는 반면 Caffeine은 "Window TinyLFU" 알고리즘을 사용하는데 이것이 둘 사이의 가장 크고 근본적인 차이점입니다.

  2. 즉시 만료를 위해 Guava는 즉시 만료 (예 : expireAfterAccess (0) 및 expireAfterWrite (0))를 변경하여 최대 크기를 0으로 설정합니다. EXPIRED 대신 SIZE로 인해 알림이 제거됩니다. Caffiene은 이러한 거부의 이유를 정확하게 식별 할 수 있습니다.

  3. 교체 알림과 관련하여 Guava에서 데이터가 교체되는 한 이유가 무엇이든간에 컬링 리스너가 트리거됩니다. Caffiene은 대체 값이 이전 값 참조와 정확히 동일 할 때 리스너를 트리거하지 않습니다.

  4. 비 동기화 측면에서 많은 Caffiene의 작업이 스레드 풀 (기본값 : ForkJoinPool.commonPool ())로 전달됩니다 (예 : 리스너 제거, 메커니즘 새로 고침, 유지 관리 작업 등).

메모리 풋 프린트 비교

카페인은 사용량에 따라 초기화를 지연 시키거나 내부 데이터 구조를 동적으로 조정할 수 있습니다. 이것은 메모리 사용량을 줄일 수 있습니다. 아래 그림과 같이 gradle memoryOverhead는 메모리 사용량을 테스트하는 데 사용됩니다. 결과는 JVM 포인터 압축, 오브젝트 패딩 등에 의해 영향을받을 수 있습니다.

 

LRU PK W-TinyLFU

캐시 제거 전략은 단기간에 다시 사용될 데이터를 예측하여 캐시 적중률을 높이는 것입니다. 간결한 구현, 효율적인 런타임 성능 및 일반적인 사용 시나리오에서 우수한 적중률로 인해 LRU (Least Latest Used) 전략이 가장 인기있는 제거 전략 일 수 있습니다. 알고리즘을 단순하게 유지하면서 뛰어난 효과를 발휘합니다. 그러나 미래에 대한 LRU의 예측에는 명백한 한계 가 있으며, "최근 도착한 데이터가 다시 액세스 할 가능성이 가장 높다" 고 고려하여 가장 높은 우선 순위를 부여합니다.

최신 캐시는 데이터를 더 잘 예측하기 위해 최신 성과 빈도를 결합하여 기록 데이터의 사용을 확대했습니다.

 

구아바 마이그레이션

내 프로젝트가 이전에 GuavaCache를 사용한 경우 가능한 가장 낮은 비용으로 어떻게 Caffeine으로 마이그레이션 할 수 있습니까? Caffeine은 이미 이것을 생각하고 있으며 Guava의 인터페이스로 캐시를 작동 할 수있는 어댑터를 제공합니다. 코드 조각은 다음과 같습니다.

// Guava's LoadingCache interface
LoadingCache<Key, Graph> graphs = CaffeinatedGuava.build(
    Caffeine.newBuilder().maximumSize(10_000),
    new CacheLoader<Key, Graph>() { // Guava's CacheLoader
        @Override public Graph load(Key key) throws Exception {
          return createExpensiveGraph(key);
        }
    });

 

실제 전투 :

채우기 전략 (인구)

Caffeine은 수동, 동기 및 비동기의 세 가지 충전 전략을 제공합니다.

퇴거 전략 (퇴거)

카페인은 크기 기반, 시간 기반 및 참조 기반의 세 가지 퇴거 전략을 제공합니다.

참조 기준 :

링크를 클릭하여 강력한 참조, 소프트 참조 및 약한 참조의 개념설명하십시오 . 다음은 각 참조 간의 차이점입니다.

높은 수준에서 낮은 수준으로 Java 4 참조 수준은 다음과 같습니다. strong reference> soft reference> weak reference> phantom reference

참조 유형 가비지 수집 시간 사용하다 생존 시간
강력한 참조 개체의 일반적인 상태 JVM 실행이 중지되면 종료
소프트 참조 메모리가 부족할 때 개체 캐시 메모리가 부족할 때 종료
약한 참조 가비지 수집 중 개체 캐시 gc 실행 후 종료
팬텀 참조 알 수 없는 알 수 없는 알 수 없는

리스너 제거 (제거)

개념:

  • 퇴거 : 특정 퇴거 전략이 충족되면 삭제 작업이 백그라운드에서 자동으로 수행됩니다.
  • 무효화 : 호출자가 캐시를 수동으로 삭제함을 의미합니다.
  • 제거 : 제거 또는 잘못된 작업을 수신하는 리스너

새롭게 하다

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    // 指定在创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
    .refreshAfterWrite(1, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));

새로 고침과 제거는 동일하지 않습니다.

새로 고침은 LoadingCache.refresh (key) 메서드에 의해 지정되고 CacheLoader.reload 메서드를 호출하여 실행됩니다. 새로 고침 키는이 키의 새 값을 비동기 적으로로드하고 이전 값 (있는 경우)을 반환합니다. 제거는 제거가 완료되고 다른 작업이 수행되지 않을 때까지 쿼리 작업을 차단합니다.

expireAfterWrite와의 차이점은 refreshAfterWrite가 데이터를 쿼리 할 때 데이터가 쿼리 조건을 충족하는지 여부를 결정하고 조건이 충족되면 캐시가 새로 고침 작업을 수행한다는 것입니다. 예를 들어 동일한 캐시에서 refreshAfterWrite와 expireAfterWrite를 동시에 지정할 수 있습니다. 데이터가 새로 고침 조건을 충족하는 경우에만 데이터가 새로 고쳐지며, 새로 고침 작업은 맹목적으로 수행되지 않습니다.

새로 고친 후 데이터를 다시 쿼리하지 않은 경우 데이터도 만료됩니다.

새로 고침 작업은 Executor를 사용하여 비동기 적으로 수행됩니다. 기본 실행기는 ForkJoinPool.commonPool ()이며, Caffeine.executor (Executor)에 의해 재정의 될 수 있습니다.

새로 고침 중에 예외가 발생하면 로그를 사용하여 로그를 기록하고 처리되지 않습니다.

통계 (통계)

Cache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .recordStats()
    .build();

Caffeine.recordStats ()를 사용하여 통계 수집을 켤 수 있습니다. Cache.stats () 메서드는 다음과 같은 통계 정보를 제공하는 CacheStats를 반환합니다.

  • hitRate () : 요청에 대한 히트 비율을 반환합니다.
  • hitCount () : 캐시의 총 적중 수를 반환합니다.
  • evictionCount () : 캐시 제거 횟수
  • averageLoadPenalty () : 새 값을로드하는 데 걸리는 평균 시간

 

팁 :

  • expireAfterAccess (long, TimeUnit) : 마지막 액세스 (읽기 또는 쓰기) 후 만료되는 기간
  • expireAfterWrite (long, TimeUnit) : 마지막으로 생성 또는 수정 된 후 만료되는 기간
  • expireAfter (Expiry) : 생성 후 만료되는 기간 

 

참조 문서:

Kill GuavaCache : 카페인 은 로컬 캐시의 왕입니다 :  https://blog.csdn.net/u013256816/article/details/106740641

카페인 소스 코드 :  https://github.com/ben-manes/caffeine 

카페인 캐시 : https://my.oschina.net/xiaolyuh/blog/3109290

 

 

추천

출처blog.csdn.net/chajinglong/article/details/113079264