Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

Spring - Mise en cache des données avec GemFire

Par Bastien JULIÉ Publié le 12/08/2017 à 22:06:02 Noter cet article:
(0 votes)
En attente de relecture par le comité de lecture

Introduction

Cet article va vous présenter comment utiliser GemFire afin de mettre en cache certains appels de votre code.

Ce que vous allez construire

Vous allez créer un service qui fera des requêtes sur un service de sitation hébergé par CloudFoundry, et les mettra en cache dans GemFire. Vous verrez alors que l'obtention d'une même citation n'éffectuera pas d'appel couteux au service de citation.

L'adresse du service de citation est :

http://gturnquist-quoters.cfapps.io

Le service de citation propose l'API suivante :

GET / api - obtenir toutes les citations
GET / api / random - obtenir une citation aléatoire
GET / api / {id} - obtenir une citation spécifique

Compiler avec Maven

Commencons par créer un script basique de compilation. Vous pouvez utiliser votre IDE préféré pour créer votre application, mais vous trouverez le code nécessaire à la compilation via Maven ci-dessous.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-caching-gemfire</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring-shell.version>1.0.0.RELEASE</spring-shell.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-gemfire</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell</artifactId>
            <version>${spring-shell.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>

</project>

Le plugin Spring Boot Maven offre de nombreuses fonctionnalités pratiques:

  • Il rassemble tous les jars dans le classpath et construit un «über-jar» exécutable, ce qui rend votre service plus pratique à exécuter et à transporter.

  • Il recherche la méthode public static void main () pour flagger en tant que classe exécutable.

  • Il fournit un "résolveur" de dépendance intégré qui définit le numéro de version pour correspondre aux dépendances Spring Boot. Vous pouvez remplacer n'importe qu'elle version souhaitée, mais elle sera par défaut celle associée à la versions de Spring Boot.

Créer un objet "bindable" pour récupérer des données

Maintenant que vous avez configuré votre projet et sa compilation, vous pouvez vous concentrer sur la définition des objets de domaines nécessaires à la capture des bits dont vous avez besoin pour aspirer les citations (les données) du service de citation.

src/main/java/hello/Quote.java
package hello;

import org.springframework.util.ObjectUtils;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("unused")
public class Quote {

	private Long id;

	private String quote;

	public Long getId() {
		return id;
	}

	public void setId(final Long id) {
		this.id = id;
	}

	public String getQuote() {
		return quote;
	}

	public void setQuote(final String quote) {
		this.quote = quote;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}

		if (!(obj instanceof Quote)) {
			return false;
		}

		Quote that = (Quote) obj;

		return ObjectUtils.nullSafeEquals(this.getId(), that.getId());
	}

	@Override
	public int hashCode() {
		int hashValue = 17;
		hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getId());
		return hashValue;
	}

	@Override
	public String toString() {
		return getQuote();
	}

}>

La classe de domaine Quote à les propriétés id et quote ainsi que les getters et setters standards. Ce sont les deux principaux attributs que vous trouverez plus loin dans ce guide.

En plus de Quote, QuoteResponse assure toute la charge du service de citation envoyé en réponse aux requêtes. Cela comprend le status (aka type) de la demande et la Quote.

src/main/java/hello/QuoteResponse.java
package hello;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("unused")
public class QuoteResponse {

	@JsonProperty("value")
	private Quote quote;

	@JsonProperty("type")
	private String status;

	public Quote getQuote() {
		return quote;
	}

	public void setQuote(final Quote quote) {
		this.quote = quote;
	}

	public String getStatus() {
		return status;
	}

	public void setStatus(final String status) {
		this.status = status;
	}

	@Override
	public String toString() {
		return String.format("{ @type = %1$s, quote = '%2$s', status = %3$s }",
			getClass().getName(), getQuote(), getStatus());
	}

}

Une réponse typique du service de citation apparaitra comme ceci :

{
  "type":"success",
  "value": {
    "id":1,
    "quote":"Working with Spring Boot is like pair-programming with the Spring developers."
  }
}

Les deux classes sont marquées avec @JsonIgnoreProperties(ignoreUnknown=true). Cela signifie que même si d'autres attributs JSON peuvent être récupérés, ils seront ignorés.

Service de requête

L'étape suivante est de créer un service qui effectue des requêtes au service de citation.

src/main/java/hello/QuoteService.java
package hello;

import java.util.Collections;
import java.util.Map;

import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.client.RestTemplate;

@SuppressWarnings("unused")
public class QuoteService {

	protected static final String ID_BASED_QUOTE_SERVICE_URL = "http://gturnquist-quoters.cfapps.io/api/{id}";
	protected static final String RANDOM_QUOTE_SERVICE_URL = "http://gturnquist-quoters.cfapps.io/api/random";

	private volatile boolean cacheMiss = false;

	private RestTemplate quoteServiceTemplate = new RestTemplate();

	/**
	 * Determines whether the previous service method invocation resulted in a cache miss.
	 *
	 * @return a boolean value indicating whether the previous service method invocation resulted in a cache miss.
	 */
	public boolean isCacheMiss() {
		boolean cacheMiss = this.cacheMiss;
		this.cacheMiss = false;
		return cacheMiss;
	}

	protected void setCacheMiss() {
		this.cacheMiss = true;
	}

	/**
	 * Requests a quote with the given identifier.
	 *
	 * @param id the identifier of the {@link Quote} to request.
	 * @return a {@link Quote} with the given ID.
	 */
	@Cacheable("Quotes")
	public Quote requestQuote(Long id) {
		setCacheMiss();
		return requestQuote(ID_BASED_QUOTE_SERVICE_URL, Collections.singletonMap("id", id));
	}

	/**
	 * Requests a random quote.
	 *
	 * @return a random {@link Quote}.
	 */
	@CachePut(cacheNames = "Quotes", key = "#result.id")
	public Quote requestRandomQuote() {
		setCacheMiss();
		return requestQuote(RANDOM_QUOTE_SERVICE_URL);
	}

	protected Quote requestQuote(String URL) {
		return requestQuote(URL, Collections.emptyMap());
	}

	protected Quote requestQuote(String URL, Map<String, Object> urlVariables) {
		QuoteResponse quoteResponse = quoteServiceTemplate.getForObject(URL, QuoteResponse.class, urlVariables);
		return quoteResponse.getQuote();
	}

}

Ce service utilise le RestTemplate de Springpour effectuer les requêtes sur l'API du service https://gturnquist-quoters.cfapps.io/api. Le service répond avec un objet Json, mais Spring associe la donnée à un objet QuoteResponse et donc un onbjet Quote.

La clé de ce service est la façon dont requestQuote a été annoté avec @Cacheable("Quotes"). L'abstraction de cache de Spring intercepte l'appel à requestQuote pour vérifier si cela a déjà été appelé. Si tel est le cas, l'abstraction de cache de Spring renvoie la copie en cache. Sinon, il procède à l'appel de la méthode, stocke la réponse dans le cache, puis renvoie les résultats à l'appelant.

Nous avons également utilisé l'annotation @CachePut sur la méthode de service requestRandomQuote. Étant donné que la citation renvoyée par cette invocation de service sera aléatoire, nous ne savons pas quel citation nous recevrons. Par conséquent, nous ne pouvons pas consulter le cache (c.-à-d. Les Quotes ) avant l'appel, mais nous pouvons mettre en cache le résultat de l'appel, ce qui aura un effet positif sur les appels postérieur de requestQuote(id), en supposant que la citation en question ai été sélectionnée au hasard et mise en cache précédemment.

Le @CachePut comprend une expression SpEL ( #result.id ) pour accéder au résultat de l'appel de la méthode de service et récupérer l'ID de la citation à utiliser comme clé de cache.

Plus tard, lorsque vous exécutez le code, vous verrez le temps nécessaire pour exécuter chaque appel et pouvoir discerner l'impact de la mise en cache sur les temps de réponse du service. Cela démontre la valeur de la mise en cache de certains appels. Si votre application recherche constamment les mêmes données, la mise en cache des résultats peut améliorer considérablement votre performance.

Rentre l'appication exécutable

Bien que la mise en cache GemFire ​​puisse être intégrée dans les applications Web et les fichiers WAR, l'approche plus simple démontrée ci-dessous crée une application autonome. Vous mettez tout dans un seul fichier JAR exécutable, piloté par une bonne ancienne méthode Java main().

src/main/java/hello/Application.java
package hello;

import java.util.Properties;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.LocalRegionFactoryBean;
import org.springframework.data.gemfire.support.GemfireCacheManager;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.GemFireCache;

@SpringBootApplication
@EnableCaching
@SuppressWarnings("unused")
public class Application implements CommandLineRunner {

    @Bean
    QuoteService quoteService() {
        return new QuoteService();
    }

    @Bean
    Properties gemfireProperties() {
        Properties gemfireProperties = new Properties();
        gemfireProperties.setProperty("name", "DataGemFireCachingApplication");
        gemfireProperties.setProperty("mcast-port", "0");
        gemfireProperties.setProperty("log-level", "config");
        return gemfireProperties;
    }

    @Bean
    CacheFactoryBean gemfireCache() {
        CacheFactoryBean gemfireCache = new CacheFactoryBean();
        gemfireCache.setClose(true);
        gemfireCache.setProperties(gemfireProperties());
        return gemfireCache;
    }

    @Bean
    LocalRegionFactoryBean<Integer, Integer> quotesRegion(GemFireCache cache) {
        LocalRegionFactoryBean<Integer, Integer> quotesRegion = new LocalRegionFactoryBean<>();
        quotesRegion.setCache(cache);
        quotesRegion.setClose(false);
        quotesRegion.setName("Quotes");
        quotesRegion.setPersistent(false);
        return quotesRegion;
    }

    @Bean
    GemfireCacheManager cacheManager(Cache gemfireCache) {
        GemfireCacheManager cacheManager = new GemfireCacheManager();
        cacheManager.setCache(gemfireCache);
        return cacheManager;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        Quote quote = requestQuote(12l);
        requestQuote(quote.getId());
        requestQuote(10l);
    }

    private Quote requestQuote(Long id) {
        QuoteService quoteService = quoteService();
        long startTime = System.currentTimeMillis();
        Quote quote = (id != null ? quoteService.requestQuote(id) : quoteService.requestRandomQuote());
        long elapsedTime = System.currentTimeMillis();
        System.out.printf("\"%1$s\"%nCache Miss [%2$s] - Elapsed Time [%3$s ms]%n", quote,
            quoteService.isCacheMiss(), (elapsedTime - startTime));
        return quote;
    }

}

@SpringBootApplication est une annotation de commodité qui ajoute tous les éléments suivants:

  • @Configuration marque la classe comme source de définitions de beans pour le contexte de l'application.

  • @EnableAutoConfiguration indique à Spring Boot de commencer à ajouter des beans en fonction des paramètres classpath, d'autres beans et de plusieurs paramètres de propriété.

  • Normalement, vous pouvez ajouter @EnableWebMvc pour une application Spring MVC, mais Spring Boot l'ajoute automatiquement quand il voit Spring-webmvc dans le classpath. Cela indique l'application en tant qu'application Web et active les comportements clés tels que la configuration d'un DispatcherServlet .

  • @ComponentScan dit à Spring de chercher d'autres composants, configurations et services dans le package hello , ce qui lui permet de trouver les contrôleurs.

La méthode main () utilise la méthode SpringApplication.run() de Spring Boot pour lancer une application. Avez-vous remarqué qu'il n'y avait pas une seule ligne de XML? Pas de fichier web.xml . Cette application Web est 100% pure Java et vous n'avez pas eu à gérer la plomberie ou l'infrastructure.

Au sommet de la configuration est une seule annotation vitale: @EnableCaching. Cela active la mise en cache et ajoute des beans importants dans les coulisses pour prendre en charge la mise en cache avec GemFire .

Le premier bean est une instance de QuoteService.

Les quatre prochains sont nécessaires pour se connecter à GemFire ​​et fournir une mise en cache.

  • gemfireProperties : Propriétés du système GemFire ​​utilisées pour configurer un nœud de données GemFire ​​autonome.

  • gemfireCache : crée un bean de cache GemFire ​​dans le contexte de l'application Spring.

  • quotesRegion définit une Région GemFire ​​dans le cache pour stocker des citations. Il s'appelle "Quotes" et doit correspondre à votre utilisation de @Cacheable("Quotes").

  • cacheManager prend en charge l'abstraction de cache de Spring en utilisant GemFire ​​comme fournisseur.

La première fois qu'une citation est demandée (à l'aide de requestQuote(id) ), son abscence dans le cache est mise en évidence et la méthode de service sera invoquée, entraînant un délai notable, c'est-à-dire pas près de zéro ms. Dans ce cas, la mise en cache est liée par les paramètres d'entrée (c.-à-d. id ) de la méthode de service, requestQuote. En d'autres termes, le paramètre de méthode id est la clé de cache. Les demandes ultérieures pour le même devis identifié par ID entraînent l'accès au cache, évitant ainsi l'appel de service coûteux.

À des fins de démonstration, l'appel au service QuoteService s'effectue dans une méthode distincte ( requestQuote à l'intérieur de la classe Application ) afin de capturer le temps nécessaire pour effectuer l'appel de service. Cela vous permet de voir exactement la durée de la demande.

Créer un exécutable Jar

Vous pouvez exécuter l'application à partir de la ligne de commande avec Gradle ou Maven. Ou vous pouvez créer un fichier JAR exécutable unique qui contient toutes les dépendances, les classes et les ressources nécessaires, et exécutez-le. Cela facilite l'expédition, la version et le déploiement du service en tant qu'application tout au long du cycle de développement, dans différents environnements, etc.

Si vous utilisez Maven, vous pouvez exécuter l'application en utilisant ./mvnw spring-boot:run. . Ou vous pouvez créer le fichier JAR avec ./mvnw clean package . Ensuite, vous pouvez exécuter le fichier JAR:

java -jar cible / gs-caching-gemfire-0.1.0.jar 

La sortie de journalisation s'affiche. Le service devrait fonctionner en quelques secondes.

"@springboot with @springframework is pure productivity! Who said in #java one has to write double the code than in other langs? #newFavLib"
Cache Miss [true] - Elapsed Time [776 ms]
"@springboot with @springframework is pure productivity! Who said in #java one has to write double the code than in other langs? #newFavLib"
Cache Miss [false] - Elapsed Time [0 ms]
"Really loving Spring Boot, makes stand alone Spring apps easy."
Cache Miss [true] - Elapsed Time [96 ms]

De cela, vous pouvez voir que le premier appel au service Quote a pris 776 ms et a entraîné une absence de cache. Cependant, le deuxième appel demandant la même citation a pris 0 ms et a entraîné une lecture du cache. Cela montre clairement que le deuxième appel a été mis en cache et n'a jamais appelé le service Quote. Cependant, lorsqu'un appel de service final pour une demande de devis spécifique non mise en cache est effectué, il a fallu 96 ms et a entraîné une absence de cache car cette nouvelle citation n'était pas dans le cache avant l'appel.

Fini !

Félicitations! Vous venez de créer un service effectuant des opérations couteuses en l'annotant pour mettre en cache les résultats.

Bibliographie

Le guide qui m'a inspiré cet article : https://spring.io/guides/gs/caching-gemfire/

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels