Caféine : Une Solution de Cache Haute Performance en Java

Caféine est une réimplémentation du cache Guava utilisant Java 8. Elle offre une expérience considérablement améliorée par rapport au cache de Guava et à ConcurrentLinkedHashMap. C'est un bibliothèque de cache hautes performances, considérée comme le cadre de cache optimal pour Java 8.

Le Cache Guava

Le cache Guava de Google est une excellente solutino de cache local qui propose des méthodes d'éviction basées sur la capacité, le temps et les références.

Les Capacités de Caféine

Le stockage des données en cache dans la JVM permet d'améliorer considérablement les performances des applications.

Pourquoi Utiliser un Cache Local ?

Par rapport aux opérations d'E/S, les caches locaux sont plus rapides et plus efficaces. Par rapport à Redis, qui est une excellente implémentation de cache distribué, les caches locaux évitent les limitations liées au réseau.

Inconvénients des Caches Locaux

En cas de redémarrage de nœud, le cache est perdu. Le stockage est limité par la JVM. Dans un cluster, les différents nœuds peuvent avoir des caches incohérents.

Quand Utiliser un Cache Local ?

Quand les mêmes résultats sont accédés de manière répétée. Quand on est prêt à sacrifier de l'espace mémoire pour gagner en vitesse. Quand la quantité totale de données en cache ne dépasse pas la capacité de la mémoire.

Comparaison des Caches Principaux

L'algorithme d'éviction de Caféine est plus avancé et bénéficie du support de Spring Cache (les nouvelles verisons de Spring Cache ne supportent plus Guava Cache).

Intégration avec Spring 2.x

Étape 1 : Ajout des Dépendances

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.2</version>
</dependency>

Étape 2 : Création de CacheConfig.java

@Configuration
public class ConfigurationCache {

    @Bean
    public Cache<String, Object> cacheCafeine() {
        return Caffeine.newBuilder()
                // Définit l'expiration après un temps fixe depuis la dernière écriture ou accès
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // Taille initiale de l'espace cache
                .initialCapacity(100)
                // Nombre maximum d'entrées dans le cache
                .maximumSize(1000)
                .build();
    }
}

Étape 3 : Injection

@Autowired
Cache<String, Object> cacheCafeine;

Étape 4 : Utilisation

1. Ajout au cache

cacheCafeine.put("cle","valeur");

2. Récupération depuis le cache

Object objet = cacheCafeine.getIfPresent("cle");

3. Récupération avec chargement si absent

Object o = cacheCafeine.get("cle", k -> chargerDonnee("cle"));

4. Stockage d'une entité

cacheCafeine.put("utilisateur", new DonneeUtilisateur(3, "chat"));
Object obj2 = cacheCafeine.getIfPresent("utilisateur");
DonneeUtilisateur ui2 = (DonneeUtilisateur)obj2;

5. Stockage d'une liste de chaînes

List<String> listeChaines = new ArrayList<>();
listeChaines.add("a");
listeChaines.add("b");
listeChaines.add("c");
listeChaines.add("d");
cacheCafeine.put("listeChaines", listeChaines);
Object listeCachee = cacheCafeine.getIfPresent("listeChaines");
List<String> listeChaines2 = (List<String>)listeCachee;

6. Stokcage d'une liste d'utilisateurs

List<DonneeUtilisateur> listeObjets = new ArrayList<>();
listeObjets.add(new DonneeUtilisateur(3, "chat"));
listeObjets.add(new DonneeUtilisateur(34, "chat4"));
cacheCafeine.put("listeObjets", listeObjets);
Object listeObjetsCachee = cacheCafeine.getIfPresent("listeObjets");
List<DonneeUtilisateur> listeObjets2 = (List<DonneeUtilisateur>)listeObjetsCachee;

7. Stockage d'une carte de listes

Map<String,List<DonneeUtilisateur>> carte = new HashMap<>();
List<DonneeUtilisateur> premiereListe = new ArrayList<>();
premiereListe.add(new DonneeUtilisateur(55, "oiseau"));
carte.put("11", premiereListe);

List<DonneeUtilisateur> deuxiemeListe = new ArrayList<>();
deuxiemeListe.add(new DonneeUtilisateur(55, "oiseau"));
deuxiemeListe.add(new DonneeUtilisateur(56, "chat"));
carte.put("12", deuxiemeListe);

cacheCafeine.put("carte", carte);
Object carteCachee = cacheCafeine.getIfPresent("carte");
Map<String,List<DonneeUtilisateur>> carte2 = (Map<String,List<DonneeUtilisateur>>)carteCachee;

Code Connexe

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class DonneeUtilisateur {

    public DonneeUtilisateur(){}

    public DonneeUtilisateur(int id ,String nom){
        this.id = id;
        this.nom = nom;
    }

    private Integer id;
    private String nom;
    private String sexe;
    private Integer age;
}

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Caféine est une réimplémentation du cache Guava utilisant Java 8
 *
 * Dans Spring Boot 2.0, il remplacera Guava. Si Caféine est détecté, 
 * CaffeineCacheManager sera automatiquement configuré.
 *
 * Base de données + Redis + Cache Local = Stockage efficace, accès efficace
 *
 *
 * Quand l'utiliser ?
 * Prêt à sacrifier de l'espace mémoire pour gagner en vitesse
 * On s'attend à ce que certaines clés soient interrogées plusieurs fois
 * La quantité totale de données en cache ne dépassera pas la capacité mémoire
 *
 *
 * Que peut-il faire ?
 * Définir la capacité du cache
 * Définir un délai d'expiration
 * Fournir des écouteurs de suppression
 * Fournir des chargeurs de cache
 * Construire un cache
 *
 *
 * Pour Spring 2.x, il suffit d'ajouter
 * <dependency>
 *     <groupId>com.github.ben-manes.caffeine</groupId>
 *     <artifactId>caffeine</artifactId>
 * </dependency>
 *
 *
 * initialCapacity=[entier] : Taille initiale de l'espace cache
 * maximumSize=[long] : Nombre maximum d'entrées dans le cache
 * maximumWeight=[long] : Poids maximum du cache
 * expireAfterAccess=[durée] : Expire après un temps fixe depuis la dernière écriture ou accès
 * expireAfterWrite=[durée] : Expire après un temps fixe depuis la dernière écriture
 * refreshAfterWrite=[durée] : Rafraîchit le cache après un intervalle de temps fixe
 * weakKeys : Active les références faibles pour les clés
 * weakValues : Active les références faibles pour les valeurs
 * softValues : Active les références douces pour les valeurs
 * recordStats : Active les statistiques
 * Remarque :
 * Quand expireAfterWrite et expireAfterAccess coexistent, c'est expireAfterWrite qui prévaut.
 * maximumSize et maximumWeight ne peuvent pas être utilisés simultanément
 * weakValues et softValues ne peuvent pas être utilisés simultanément
 *
 */
@Configuration
public class ConfigurationCache {

    @Bean
    public Cache<String, Object> cacheCafeine() {
        return Caffeine.newBuilder()
                // Définit l'expiration après un temps fixe depuis la dernière écriture
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // Taille initiale de l'espace cache
                .initialCapacity(100)
                // Nombre maximum d'entrées dans le cache
                .maximumSize(1000)
                .build();
    }
}

package com.example.tousdemo.mo.outil.cache.guava;

import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/cache")
public class ControleurUtilisateur {

    @Autowired
    Cache<String, Object> cacheCafeine;

    public Object chargerDonnee(String cle){
        return "Grand prédateur";
    }

    public void tester() {
        log.info("entrée dans tester");

        Object obj = cacheCafeine.getIfPresent("cle");
        log.info("cle est [" + obj + "].");

        Object o = cacheCafeine.get("cle", k -> chargerDonnee("cle"));

        cacheCafeine.put("cle", "valeur");
        obj = cacheCafeine.getIfPresent("cle");
        if(obj != null){
            log.info("cle est [" + obj.toString() + "].");
        }

        // Test d'objet
        cacheCafeine.put("utilisateur", new DonneeUtilisateur(3, "chat"));
        Object obj2 = cacheCafeine.getIfPresent("utilisateur");
        DonneeUtilisateur ui2 = (DonneeUtilisateur)obj2;

        // Test de liste de chaînes
        List<String> listeChaines = new ArrayList<>();
        listeChaines.add("a");
        listeChaines.add("b");
        listeChaines.add("c");
        listeChaines.add("d");
        cacheCafeine.put("listeChaines", listeChaines);
        Object listeCachee = cacheCafeine.getIfPresent("listeChaines");
        List<String> listeChaines2 = (List<String>)listeCachee;

        // Test de liste
        List<DonneeUtilisateur> listeObjets = new ArrayList<>();
        listeObjets.add(new DonneeUtilisateur(3, "chat"));
        listeObjets.add(new DonneeUtilisateur(34, "chat4"));
        cacheCafeine.put("listeObjets", listeObjets);
        Object listeObjetsCachee = cacheCafeine.getIfPresent("listeObjets");
        List<DonneeUtilisateur> listeObjets2 = (List<DonneeUtilisateur>)listeObjetsCachee;

        // Test de carte
        Map<String,List<DonneeUtilisateur>> carte = new HashMap<>();

        List<DonneeUtilisateur> premiereListe = new ArrayList<>();
        premiereListe.add(new DonneeUtilisateur(55, "oiseau"));
        carte.put("11", premiereListe);

        List<DonneeUtilisateur> deuxiemeListe = new ArrayList<>();
        deuxiemeListe.add(new DonneeUtilisateur(55, "oiseau"));
        deuxiemeListe.add(new DonneeUtilisateur(56, "chat"));
        carte.put("12", deuxiemeListe);

        cacheCafeine.put("carte", carte);
        Object carteCachee = cacheCafeine.getIfPresent("carte");
        Map<String,List<DonneeUtilisateur>> carte2 = (Map<String,List<DonneeUtilisateur>>)carteCachee;

        log.info("sortie de tester");
    }
}

Étiquettes: Java Cache Spring Caféine performance

Publié le 16 juin à 01h49