Spring Cloud Gateway Real Three: Dynamic Routing

Programmeur xinchen 2021-08-19 23:01:55 阅读数:42

spring cloud gateway real dynamic

Bienvenue à monGitHub

Ici, nous classons et résumons toutes les créations originales de xinchen(Y compris le code source correspondant):https://github.com/zq2599/blog_demos

Aperçu du présent article

  • Cet article est《Spring Cloud GatewaySur le terrain》Le troisième titre de la série,PrécédentPlusieurs configurations de routage sont introduites,Ils ont un problème commun:Vous devez redémarrer après un changement de configuration de routageGatewayPour que la demande prenne effet,Intelligent vous pouvez voir le problème en un coup d'oeil:Ce n'est pas approprié pour l'environnement de production!
  • Comment faire en sorte que le routage modifié prenne effet immédiatement,Sans redémarrer l'application?C'est le thème d'aujourd'hui:Routage dynamique

Idées de conception

  • Voici une idée de conception claire à l'avance,Dans l'ensemble, il s'agit de placer la configuration dansnacosAllez.,Écrivez un moniteur pour écouternacosChangement de configuration sur,Mettre à jour la configuration modifiée àGatewayDans le processus appliqué:
  • Les idées ci - dessus se reflètent dans le code dans les trois catégories suivantes:
  1. Encapsule le Code de routage de l'opération dans le nomRouteOperatorDans la classe,Utilisez cette classe pour supprimer et ajouter des routes dans le processus
  2. Faire une classe de configurationRouteOperatorConfig,Vous pouvezRouteOperatorEn tant quebean Inscrit à springDans l'environnement
  3. Écouternacos Profil de routage sur , Obtenez la configuration la plus récente en cas de changement ,Puis appelezRouteOperator Méthode pour mettre à jour le routage dans le processus , Ces écoutes nacos Configurer et appeler RouteOperator Les codes de RouteConfigListenerDans la classe
  • Dans ce combat réel , Il y a trois profils impliqués ,Parmi euxbootstrap.yml + gateway-dynamic-by-nacos C'est une configuration classique que tout le monde connaît ,bootstrap.yml Au niveau local,À l'intérieur.nacosConfiguration de,gateway-dynamic-by-nacosInnaocsAllez., À l'intérieur se trouve la configuration requise pour toute l'application ( Par exemple, le numéro de port de service 、Bases de données, etc), Il y a aussi un profil nacosAllez.,Il s'appellegateway-json-routes,- Oui.JSONFormat, Il y a une configuration de routage à l'intérieur , Pourquoi choisir JSONFormat,Parce queJSONQueyml Le format est plus facile à analyser et à traiter ;

  • Final, L'architecture globale des microservices est illustrée ci - dessous :
    Insérer la description de l'image ici

  • La pensée est claire , Commencez le codage

Téléchargement du code source

  • Le code source complet de cet article est disponible à GitHubTélécharger à,Les adresses et les liens sont indiqués dans le tableau ci - dessous(https://github.com/zq2599/blog_demos):
Nom Liens Remarques
Page d'accueil du projet https://github.com/zq2599/blog_demos Le projetGitHubPage d'accueil sur
gitAdresse de l'entrepôt(https) https://github.com/zq2599/blog_demos.git Adresse de l'entrepôt du code source du projet,httpsAccord
gitAdresse de l'entrepôt(ssh) [email protected]:zq2599/blog_demos.git Adresse de l'entrepôt du code source du projet,sshAccord
  • C'estgitIl y a plus d'un dossier dans le projet,Le code source de cet article estspring-cloud-tutorialsSous le dossier,Comme le montre la boîte rouge ci - dessous:
    Insérer la description de l'image ici
  • spring-cloud-tutorials C'est le projet père , Plusieurs sous - projets subordonnés , Le Code du combat d'aujourd'hui est gateway-dynamic-by-nacos,Comme le montre la figure ci - dessous:
    Insérer la description de l'image ici

Codage

  • Le nouveau nom est gateway-dynamic-by-nacos Le projet de ,Lepom.xmlIl se lit comme suit:, Notez les notes en chinois :
<?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">
<parent>
<artifactId>spring-cloud-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway-dynamic-by-nacos</artifactId>
<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Prends ça.springboot Les points d'arrêt du contenu sont exposés -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Utiliserbootstrap.ymlQuand, Cette dépendance doit avoir -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Utilisation de la politique de routage lb La façon dont , Cette dépendance doit avoir -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--nacos: Centre de configuration -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos:Registre-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Si le projet parent n'est pas springboot, Vous allez utiliser le plug - in de la façon suivante , Pour générer un jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.gateway.GatewayApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
  • Profilbootstrap.yml, Il n'y a que nacos, Voir d'autres informations de configuration de naocs:
spring:
application:
name: gateway-dynamic-by-nacos
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yml
group: DEFAULT_GROUP
  • La classe responsable du traitement des configurations de routage en cours de processus est RouteOperator,Comme suit, Vous pouvez voir que toute la configuration est de type chaîne ,Ça marche.JacksonDeObjectMapper Désérialisation (Attention!, Les fichiers de configuration dans le champ de bataille précédent sont ymlFormat, Mais dans ce cas JSON, Plus tard. nacos Configuration supérieure à utiliser JSONFormat), Ensuite, le traitement de la configuration de routage est principalement RouteDefinitionWriterTypebeanTerminé, Pour que la configuration prenne effet immédiatement , Encore. applicationEventPublisher Publier des messages en cours de processus :
package com.bolingcavalry.gateway.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class RouteOperator {

private ObjectMapper objectMapper;
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List<String> routeList = new ArrayList<>();
public RouteOperator(ObjectMapper objectMapper, RouteDefinitionWriter routeDefinitionWriter, ApplicationEventPublisher applicationEventPublisher) {

this.objectMapper = objectMapper;
this.routeDefinitionWriter = routeDefinitionWriter;
this.applicationEventPublisher = applicationEventPublisher;
}
/** * Nettoyer toutes les routes dans la collection , Et vider la collection */
private void clear() {

// Tous les appels API Nettoyer 
routeList.stream().forEach(id -> routeDefinitionWriter.delete(Mono.just(id)).subscribe());
// Vider la collection
routeList.clear();
}
/** * Nouvelle route * @param routeDefinitions */
private void add(List<RouteDefinition> routeDefinitions) {

try {

routeDefinitions.stream().forEach(routeDefinition -> {

routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
routeList.add(routeDefinition.getId());
});
} catch (Exception exception) {

exception.printStackTrace();
}
}
/** * Publier les notifications en cours de processus , Mettre à jour le routage */
private void publish() {

applicationEventPublisher.publishEvent(new RefreshRoutesEvent(routeDefinitionWriter));
}
/** * Mettre à jour toutes les informations de routage * @param configStr */
public void refreshAll(String configStr) {

log.info("start refreshAll : {}", configStr);
// Chaîne invalide non traitée 
if (!StringUtils.hasText(configStr)) {

log.error("invalid string for route config");
return;
}
// AvecJacksonDésérialisation
List<RouteDefinition> routeDefinitions = null;
try {

routeDefinitions = objectMapper.readValue(configStr, new TypeReference<List<RouteDefinition>>(){
});
} catch (JsonProcessingException e) {

log.error("get route definition from nacos string error", e);
}
// Si égal ànull, Indique que la désrialisation a échoué , Retour immédiat 
if (null==routeDefinitions) {

return;
}
// Effacer toutes les routes actuelles 
clear();
// Ajouter la dernière route 
add(routeDefinitions);
// Publier par message dans l'application 
publish();
log.info("finish refreshAll");
}
}
  • Faire une classe de configurationRouteOperatorConfig.java, Après l'Instanciation RouteOperatorInscrivez - vous àspringDans l'environnement:
package com.bolingcavalry.gateway.config;
import com.bolingcavalry.gateway.service.RouteOperator;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RouteOperatorConfig {

@Bean
public RouteOperator routeOperator(ObjectMapper objectMapper,
RouteDefinitionWriter routeDefinitionWriter,
ApplicationEventPublisher applicationEventPublisher) {

return new RouteOperator(objectMapper,
routeDefinitionWriter,
applicationEventPublisher);
}
}
  • Et enfin,nacos Classe d'écoute pour RouteConfigListener, Le point technique clé visible est ConfigService.addListener, Pour ajouter un moniteur , À l'intérieur se trouve la logique de mise à jour du routage après un changement de configuration , Il y a une autre étape importante : Appelez maintenant getConfig Méthode pour obtenir la configuration actuelle , Rafraîchir la configuration de routage du processus actuel :
package com.bolingcavalry.gateway.service;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executor;
@Component
@Slf4j
public class RouteConfigListener {

private String dataId = "gateway-json-routes";
private String group = "DEFAULT_GROUP";
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Autowired
RouteOperator routeOperator;
@PostConstruct
public void dynamicRouteByNacosListener() throws NacosException {

ConfigService configService = NacosFactory.createConfigService(serverAddr);
// Ajouter un moniteur ,nacos Un changement de configuration sur le sera effectué 
configService.addListener(dataId, group, new Listener() {

public void receiveConfigInfo(String configInfo) {

// L'analyse et le traitement sont confiés à RouteOperatorTerminé.
routeOperator.refreshAll(configInfo);
}
public Executor getExecutor() {

return null;
}
});
// Obtenir la configuration actuelle 
String initConfig = configService.getConfig(dataId, group, 5000);
// Mise à jour maintenant 
routeOperator.refreshAll(initConfig);
}
}
  • RouteConfigListener.java Il y en a un autre à noter ,C'estdataIdValeur de la variablegateway-json-routes,C'estnacos Nom du profil sur , Plus tard, on sera nacos Utilisé lors de la configuration ci - dessus
  • Et enfin, la classe de démarrage :
package com.bolingcavalry.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class,args);
}
}
  • Le codage est terminé ,La prochaine foisnacos Ajouter deux configurations ;
  • La première configuration s'appelle gateway-dynamic-by-nacos,Il se lit comme suit::
server:
port: 8086
# Paramètres d'exposition
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
  • La deuxième configuration est nommée gateway-json-routes, Format à sélectionner JSON, Il n'y a qu'une seule route visible (IP+ Le port ), L'autre utilise le nom du service comme URL Le routage ne mérite pas d'être , Utilisé plus tard pour vérifier si l'augmentation dynamique peut prendre effet immédiatement :
[
{

"id": "path_route_addr",
"uri": "http://127.0.0.1:8082",
"predicates":[
{

"name": "Path",
"args": {

"pattern": "/hello/**"
}
}
]
}
]
  • Jusqu'ici., Nous avons terminé le développement , Ensuite, vérifiez que le routage dynamique fonctionne comme prévu , L'outil client que j'utilise ici est postman

Validation

  • Assurez - vous quenacos、provider-hello、gateway-dynamic-by-nacos Attendez que tous les services commencent :
    Insérer la description de l'image ici
  • AvecpostmanAccès àhttp://127.0.0.1:8086/hello/str, Accès normal à ,PreuveGateway L'application est passée de nacos Le routage a été téléchargé en douceur :
    Insérer la description de l'image ici
  • Si vous utilisez l'accès http://127.0.0.1:8086/lbtest/str Ça devrait échouer ,Parce quenacos Ce n'est pas encore configuré path Le routage de ,Comme le montre la figure ci - dessous:, J'ai échoué :
    Insérer la description de l'image ici
  • Innacos Modifier l'élément de configuration sur gateway-json-routesLe contenu de, Ajouter le nom path_route_lb Configuration du routage pour , La configuration complète modifiée est la suivante :
[
{

"id": "path_route_addr",
"uri": "http://127.0.0.1:8082",
"predicates":[
{

"name": "Path",
"args": {

"pattern": "/hello/**"
}
}
]
}
,
{

"id": "path_route_lb",
"uri": "lb://provider-hello",
"predicates":[
{

"name": "Path",
"args": {

"pattern": "/lbtest/**"
}
}
]
}
]
  • Cliquez en bas à droite PublicationAprès le bouton,gateway-dynamic-by-nacos La console appliquée affiche immédiatement ce qui suit , L'écoute visible est déjà en vigueur :
2021-08-15 19:39:45.883 INFO 18736 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [polling-resp] config changed. dataId=gateway-json-routes, group=DEFAULT_GROUP
2021-08-15 19:39:45.883 INFO 18736 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : get changedGroupKeys:[gateway-json-routes+DEFAULT_GROUP]
2021-08-15 19:39:45.890 INFO 18736 --- [-127.0.0.1_8848] c.a.n.client.config.impl.ClientWorker : [fixed-127.0.0.1_8848] [data-received] dataId=gateway-json-routes, group=DEFAULT_GROUP, tenant=null, md5=54fb76dcad838917818d0160ce2bd72f, content=[
{

"id": "path_route_addr",
"uri": "http://127.0.0.1:8082",
"predicates..., type=json 2021-08-15 19:39:45.891 INFO 18736 --- [-127.0.0.1_8848] c.b.gateway.service.RouteOperator : start refreshAll : [ { "id": "path_route_addr", "uri": "http://127.0.0.1:8082", "predicates":[ { "name": "Path", "args": { "pattern": "/hello/**" } } ] } , { "id": "path_route_lb", "uri": "lb://provider-hello", "predicates":[ { "name": "Path", "args": { "pattern": "/lbtest/**"
}
}
]
}
]
2021-08-15 19:39:45.894 INFO 18736 --- [-127.0.0.1_8848] c.b.gateway.service.RouteOperator : finish refreshAll
2021-08-15 19:39:45.894 INFO 18736 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-ok] dataId=gateway-json-routes, group=DEFAULT_GROUP, md5=54fb76dcad838917818d0160ce2bd72f, listener=com.bolingcavalry.gateway.service.RouteConfigListener$1@123ae1f6
2021-08-15 19:39:45.894 INFO 18736 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-listener] time cost=3ms in ClientWorker, dataId=gateway-json-routes, group=DEFAULT_GROUP, md5=54fb76dcad838917818d0160ce2bd72f, listener=com.bolingcavalry.gateway.service.RouteConfigListener$1@123ae1f6
  • Réutiliserpostman Envoyez la même demande , Cette fois, ça a marché , Le routage dynamique visible a réussi :
    Insérer la description de l'image ici
  • En raison de la dépendance spring-boot-starter-actuatorBibliothèque, Et la configuration pertinente a été ajoutée au fichier de configuration , On peut aussi voir SpringBoot Appliquer la configuration interne , Accès par navigateur http://localhost:8086/actuator/gateway/routes, Voir les dernières configurations ,Comme le montre la figure ci - dessous::
    Insérer la description de l'image ici
  • Jusqu'ici., Le développement et la validation du routage dynamique sont terminés , J'espère que cette fonctionnalité pratique vous donnera quelques références , Développer un service de passerelle plus flexible et plus pratique ;

Tu n'es pas seule.,Xinchen original tout au long du chemin

  1. JavaSérie
  2. SpringSérie
  3. DockerSérie
  4. kubernetesSérie
  5. Base de données+Série Middleware
  6. DevOpsSérie
Copyright:Cet article est[Programmeur xinchen]Établi,Veuillez apporter le lien original pour réimprimer,remercier。 https://fra.fheadline.com/2021/08/20210819230146934U.html