查看原文
其他

【SFA官方翻译】:基于Zuul、Redis和REST API实现动态路由的持久化及容错能力

The following article is from 生活点亮技术 Author JackieTang

原文链接:https://dzone.com/articles/persistent-and-fault-tolerant-dynamic-routes-using

作者: Vikas Anand

译者:helloworldtang

学习如何使用REST API在Zuul服务器上注册动态路由,并借助Redis使您的动态路由具备容错能力。

目标

我们将使用Zuul、Spring boot Actuator、Redis创建一个应用程序,它提供REST API来创建动态路由,查看动态路由,删除不需要的路由,从缓存和数据库中恢复以前创建的所有动态路由的功能。虽然这个应用程序展示了更多关于动态路由的信息,但是它也展示了使用spring boot-starer-redis与Redis进行交互的方式。假设Redis服务器在本地的6379端口上运行。此外,它还展示了Spring boot actuator公开的对这个应用程序很有帮助的一些URL。

步骤

我们将创建一个基于maven的Spring Boot项目。

项目结构:

项目中的文件如下所示:

  • pom.xml

  1. <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">

  2.    <modelVersion>4.0.0</modelVersion>

  3.    <groupId>com.cfeindia.examples.zuul</groupId>

  4.    <artifactId>zuul-route-redis</artifactId>

  5.    <version>0.0.1-SNAPSHOT</version>

  6.    <parent>

  7.        <groupId>org.springframework.boot</groupId>

  8.        <artifactId>spring-boot-starter-parent</artifactId>

  9.        <version>1.5.13.RELEASE</version>

  10.        <relativePath />

  11.        <!-- lookup parent from repository -->

  12.    </parent>

  13.    <properties>

  14.        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  15.        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

  16.        <java.version>1.8</java.version>

  17.        <spring-cloud.version>Edgware.SR3</spring-cloud.version>

  18.        <maven.compiler.source>1.8</maven.compiler.source>

  19.        <maven.compiler.target>1.8</maven.compiler.target>

  20.    </properties>

  21.    <repositories>

  22.        <repository>

  23.            <id>spy</id>

  24.            <name>Spy Repository</name>

  25.            <layout>default</layout>

  26.            <url>http://files.couchbase.com/maven2/</url>

  27.            <snapshots>

  28.                <enabled>false</enabled>

  29.            </snapshots>

  30.        </repository>

  31.    </repositories>

  32.    <dependencyManagement>

  33.        <dependencies>

  34.            <dependency>

  35.                <groupId>org.springframework.cloud</groupId>

  36.                <artifactId>spring-cloud-dependencies</artifactId>

  37.                <version>${spring-cloud.version}</version>

  38.                <type>pom</type>

  39.                <scope>import</scope>

  40.            </dependency>

  41.        </dependencies>

  42.    </dependencyManagement>

  43.    <dependencies>

  44.        <dependency>

  45.            <groupId>org.springframework.cloud</groupId>

  46.            <artifactId>spring-cloud-starter-zuul</artifactId>

  47.        </dependency>

  48.        <dependency>

  49.            <artifactId>spring-boot-starter-actuator</artifactId>

  50.            <exclusions>

  51.                <exclusion>

  52.                    <groupId>org.springframework.boot</groupId>

  53.                    <artifactId>spring-boot-starter-logging</artifactId>

  54.                </exclusion>

  55.            </exclusions>

  56.        </dependency>

  57.        <dependency>

  58.            <groupId>org.springframework.boot</groupId>

  59.            <artifactId>spring-boot-starter-log4j2</artifactId>

  60.        </dependency>

  61.        <dependency>

  62.            <groupId>org.springframework.boot</groupId>

  63.            <artifactId>spring-boot-starter-data-redis</artifactId>

  64.        </dependency>

  65.        <dependency>

  66.            <groupId>org.springframework.boot</groupId>

  67.            <artifactId>spring-boot-starter-web</artifactId>

  68.        </dependency>

  69.    </dependencies>

  70.    <build>

  71.        <plugins>

  72.            <plugin>

  73.                <artifactId>maven-compiler-plugin</artifactId>

  74.                <configuration>

  75.                    <source>1.8</source>

  76.                    <target>1.8</target>

  77.                </configuration>

  78.            </plugin>

  79.            <plugin>

  80.                <artifactId>maven-shade-plugin</artifactId>

  81.                <executions>

  82.                    <execution>

  83.                        <phase>package</phase>

  84.                        <goals>

  85.                            <goal>shade</goal>

  86.                        </goals>

  87.                        <configuration>

  88. <finalName>${project.artifactId}-${project.version}</finalName>

  89.                            <transformers>

  90.                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">

  91.                                    <resource>META-INF/spring.handlers</resource>

  92.                                </transformer>

  93.                                <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">

  94.                                    <resource>META-INF/spring.factories</resource>

  95.                                </transformer>

  96.                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">

  97.                                    <resource>META-INF/spring.schemas</resource>

  98.                                </transformer>

  99.                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />

  100.                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">

  101.                                    <mainClass>com.mettl.gatewayservice.application.SpringBootWebApplication</mainClass>

  102.                                </transformer>

  103.                                <transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer" />

  104.                            </transformers>

  105.                        </configuration>

  106.                    </execution>

  107.                </executions>

  108.                <dependencies>

  109.                    <dependency>

  110.                        <groupId>com.github.edwgiz</groupId>

  111.                        <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>

  112.                        <version>2.6.1</version>

  113.                    </dependency>

  114.                </dependencies>

  115.            </plugin>

  116.        </plugins>

  117.    </build>

  118. </project>

  • Spring Boot应用程序启动类,添加了Zuul、Redis等特性必需的注解:

  1. package com.cfeindia.examples.zuul.application;

  2. import org.springframework.boot.autoconfigure.SpringBootApplication;

  3. import org.springframework.boot.SpringApplication;

  4. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

  5. import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;

  6. import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

  7. import org.springframework.context.annotation.ComponentScan;

  8. import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;

  9. @SpringBootApplication

  10. @EnableAutoConfiguration(exclude = {

  11. RabbitAutoConfiguration.class

  12. })

  13. @EnableZuulProxy

  14. @EnableRedisRepositories("com.cfeindia.examples.zuul")

  15. @ComponentScan("com.cfeindia.examples.zuul")

  16. public class SpringBootWebApplication {

  17. public static void main(String[] args) {

  18.  SpringApplication.run(SpringBootWebApplication.class, args);

  19. }

  20. }

  • 应用程序配置类,定义bean

  1. package com.cfeindia.examples.zuul.config;

  2. import org.springframework.context.annotation.Bean;

  3. import org.springframework.context.annotation.Configuration;

  4. import org.springframework.data.redis.connection.RedisConnectionFactory;

  5. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

  6. import org.springframework.data.redis.core.RedisTemplate;

  7. import com.cfeindia.examples.zuul.model.DynamicRoute;

  8. import redis.clients.jedis.JedisPoolConfig;

  9. @Configuration

  10. public class AppConfig {

  11. private static final int REDIS_PORT = 6379;

  12. private static final String REDIS_HOST_NAME = "localhost";

  13. @Bean

  14. public RedisConnectionFactory redisConnectionFactory() {

  15.  JedisPoolConfig poolConfig = new JedisPoolConfig();

  16.  //Change the configuration as per requirement

  17.  poolConfig.setMaxTotal(128);

  18.  poolConfig.setTestOnBorrow(true);

  19.  poolConfig.setTestOnReturn(true);

  20.  poolConfig.setMinIdle(5);

  21.  poolConfig.setMaxIdle(128);

  22.  JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);

  23.  connectionFactory.setUsePool(true);

  24.  connectionFactory.setHostName(REDIS_HOST_NAME);

  25.  connectionFactory.setPort(REDIS_PORT);

  26.  return connectionFactory;

  27. }

  28. @Bean

  29. public RedisTemplate < String, DynamicRoute > redisTemplate() {

  30.  RedisTemplate < String, DynamicRoute > redisTemplate = new RedisTemplate < > ();

  31.  redisTemplate.setConnectionFactory(redisConnectionFactory());

  32.  redisTemplate.setEnableTransactionSupport(false);

  33.  return redisTemplate;

  34. }

  35. }

  • controller类,提供了动态路由注册和删除功能的API:

  1. package com.cfeindia.examples.zuul.controller;

  2. import org.slf4j.Logger;

  3. import org.slf4j.LoggerFactory;

  4. import org.springframework.beans.factory.annotation.Autowired;

  5. import org.springframework.web.bind.annotation.RequestBody;

  6. import org.springframework.web.bind.annotation.RequestMapping;

  7. import org.springframework.web.bind.annotation.RequestMethod;

  8. import org.springframework.web.bind.annotation.ResponseBody;

  9. import org.springframework.web.bind.annotation.RestController;

  10. import com.cfeindia.examples.zuul.model.DeleteRouteRequest;

  11. import com.cfeindia.examples.zuul.model.DynamicRoute;

  12. import com.cfeindia.examples.zuul.model.DynamicRouteResponse;

  13. import com.cfeindia.examples.zuul.service.ZuulDynamicRoutingService;

  14. @RestController

  15. public class DynamicRouteController {

  16. private static final Logger logger = LoggerFactory.getLogger(DynamicRouteController.class);

  17. @Autowired

  18. ZuulDynamicRoutingService zuulDynamicRoutingService;

  19. @RequestMapping(value = "/proxyurl", method = RequestMethod.POST)

  20. public @ResponseBody DynamicRouteResponse getProxyURL(@RequestBody DynamicRoute dynamicRoute) {

  21.  logger.debug("request received to add {}", dynamicRoute);

  22.  DynamicRouteResponse dynamicRouteResponse = zuulDynamicRoutingService.addDynamicRoute(dynamicRoute);

  23.  logger.debug("response sent {}", dynamicRouteResponse);

  24.  return dynamicRouteResponse;

  25. }

  26. @RequestMapping(value = "/proxyurl", method = RequestMethod.DELETE)

  27. public @ResponseBody Boolean deleteProxyURL(@RequestBody DeleteRouteRequest deleteRouteRequest) {

  28.  logger.debug("request received to delete {}", deleteRouteRequest);

  29.  Boolean response = zuulDynamicRoutingService.removeDynamicRoute(deleteRouteRequest.getRequestURIUniqueKey());

  30.  logger.debug("response sent for delete {}", response);

  31.  return response;

  32. }

  33. }

  • Rest API请求和响应需要的POJO

DynamicRoute.java:

存储到Redis时用到的动态路由请求类。检查@RedisHash和@Id注解,这是保存、检索和删除动态路由所必需的。 它也被用于Rest API请求中,用来将传入的Json转换成动态路由对象。

  1. package com.cfeindia.examples.zuul.model;

  2. import java.io.Serializable;

  3. import org.springframework.data.annotation.Id;

  4. import org.springframework.data.redis.core.RedisHash;

  5. /**

  6. * Dynamic route request object saved in Redis

  7. * It is also used in Rest API request to convert Json in Object

  8. * @author vikasanand

  9. *

  10. */

  11. @RedisHash("Gateway_Service_Dynamic_Route")

  12. public class DynamicRoute implements Serializable {

  13. private static final long serialVersionUID = -6719150427066586659 L;

  14. /**

  15.  * This can be a unique key different for each route registration. It should be different

  16.  * for each requestURI to be forwarded.

  17.  * i.e. asad121-sadas-hjjhhd

  18.  */

  19. @Id

  20. public String requestURIUniqueKey;

  21. /**

  22.  * This can be of format "/api1"

  23.  * It should be sub-path URI which needs to forwarded to different proxy

  24.  */

  25. private String requestURI;

  26. /**

  27.  * Target Host name or IP

  28.  * i.e. https://adomain.com

  29.  */

  30. private String targetURLHost;

  31. /**

  32.  * Target Port to forward

  33.  * i.e. 80

  34.  */

  35. private int targetURLPort;

  36. /**

  37.  * Target URI to forward

  38.  * /proxy-api1

  39.  */

  40. private String targetURIPath;

  41. public String getRequestURIUniqueKey() {

  42.  return requestURIUniqueKey;

  43. }

  44. public void setRequestURIUniqueKey(String requestURIUniqueKey) {

  45.  this.requestURIUniqueKey = requestURIUniqueKey;

  46. }

  47. public String getRequestURI() {

  48.  return requestURI;

  49. }

  50. public void setRequestURI(String requestURI) {

  51.  this.requestURI = requestURI;

  52. }

  53. public String getTargetURLHost() {

  54.  return targetURLHost;

  55. }

  56. public void setTargetURLHost(String targetURLHost) {

  57.  this.targetURLHost = targetURLHost;

  58. }

  59. public int getTargetURLPort() {

  60.  return targetURLPort;

  61. }

  62. public void setTargetURLPort(int targetURLPort) {

  63.  this.targetURLPort = targetURLPort;

  64. }

  65. public String getTargetURIPath() {

  66.  return targetURIPath;

  67. }

  68. public void setTargetURIPath(String targetURIPath) {

  69.  this.targetURIPath = targetURIPath;

  70. }

  71. @Override

  72. public String toString() {

  73.  StringBuilder builder = new StringBuilder();

  74.  builder.append("DynamicRoute [requestURIUniqueKey=");

  75.  builder.append(requestURIUniqueKey);

  76.  builder.append(", requestURI=");

  77.  builder.append(requestURI);

  78.  builder.append(", targetURLHost=");

  79.  builder.append(targetURLHost);

  80.  builder.append(", targetURLPort=");

  81.  builder.append(targetURLPort);

  82.  builder.append(", targetURIPath=");

  83.  builder.append(targetURIPath);

  84.  builder.append("]");

  85.  return builder.toString();

  86. }

  87. }

其它POJO:

  1. package com.cfeindia.examples.zuul.model;

  2. /**

  3. * Response of dynamic route creation and saving

  4. * @author vikasanand

  5. *

  6. */

  7. public class DynamicRouteResponse {

  8. /**

  9.  * statusCode is 0 for success.

  10.  * if statusCode is not 0 then check for errorMessage for details.

  11.  */

  12. private int statusCode;

  13. private String errorMessage;

  14. public DynamicRouteResponse() {

  15.  super();

  16. }

  17. public DynamicRouteResponse(int statusCode, String errorMessage) {

  18.  super();

  19.  this.statusCode = statusCode;

  20.  this.errorMessage = errorMessage;

  21. }

  22. public int getStatusCode() {

  23.  return statusCode;

  24. }

  25. public String getErrorMessage() {

  26.  return errorMessage;

  27. }

  28. @Override

  29. public String toString() {

  30.  StringBuilder builder = new StringBuilder();

  31.  builder.append("DynamicRouteResponse [statusCode=");

  32.  builder.append(statusCode);

  33.  builder.append(", errorMessage=");

  34.  builder.append(errorMessage);

  35.  builder.append("]");

  36.  return builder.toString();

  37. }

  38. }

  1. package com.cfeindia.examples.zuul.model;

  2. public class DeleteRouteRequest {

  3. public String requestURIUniqueKey;

  4. public String getRequestURIUniqueKey() {

  5.  return requestURIUniqueKey;

  6. }

  7. public void setRequestURIUniqueKey(String requestURIUniqueKey) {

  8.  this.requestURIUniqueKey = requestURIUniqueKey;

  9. }

  10. }

  • 与Redis服务器交互的DAO或repository类。使用Spring Data Redis库使得CRUD操作变得非常简单。我们只需要创建一个接口来扩展接口CrudRepository,并添加@Repository注解。请设置DynamicRouteRedisRepository所继承父类CrudRepository的泛型为DynamicRoute和String,第一个是值的类型,第二个是键的类型。

  1. package com.cfeindia.examples.zuul.dao;

  2. import org.springframework.data.repository.CrudRepository;

  3. import org.springframework.stereotype.Repository;

  4. import com.cfeindia.examples.zuul.model.DynamicRoute;

  5. @Repository

  6. public interface DynamicRouteRedisRepository extends CrudRepository < DynamicRoute, String > {}

  • Zuul使用的Filter类。这些类都是默认类。

  1. package com.cfeindia.examples.zuul.filter;

  2. import javax.servlet.http.HttpServletRequest;

  3. import org.slf4j.Logger;

  4. import org.slf4j.LoggerFactory;

  5. import org.springframework.stereotype.Component;

  6. import org.springframework.util.StringUtils;

  7. import org.springframework.web.util.UrlPathHelper;

  8. import com.netflix.zuul.ZuulFilter;

  9. import com.netflix.zuul.context.RequestContext;

  10. import com.netflix.zuul.http.HttpServletRequestWrapper;

  11. @Component

  12. public class PreFilter extends ZuulFilter {

  13. private static Logger log = LoggerFactory.getLogger(PreFilter.class);

  14. private UrlPathHelper urlPathHelper = new UrlPathHelper();

  15. @Override

  16. public String filterType() {

  17.  return "pre";

  18. }

  19. @Override

  20. public int filterOrder() {

  21.  return 1;

  22. }

  23. @Override

  24. public boolean shouldFilter() {

  25.  RequestContext ctx = RequestContext.getCurrentContext();

  26.  String requestURL = ctx.getRequest().getRequestURL().toString();

  27.  return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));

  28. }

  29. @Override

  30. public Object run() {

  31.  RequestContext ctx = RequestContext.getCurrentContext();

  32.  HttpServletRequest request = ctx.getRequest();

  33.  log.info("PreFilter: " +

  34.   String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

  35.  return null;

  36. }

  37. }

  1. package com.cfeindia.examples.zuul.filter;

  2. import javax.servlet.http.HttpServletRequest;

  3. import org.slf4j.Logger;

  4. import org.slf4j.LoggerFactory;

  5. import org.springframework.stereotype.Component;

  6. import com.netflix.zuul.ZuulFilter;

  7. import com.netflix.zuul.context.RequestContext;

  8. @Component

  9. public class RouteFilter extends ZuulFilter {

  10. private static Logger log = LoggerFactory.getLogger(RouteFilter.class);

  11. @Override

  12. public String filterType() {

  13.  return "route";

  14. }

  15. @Override

  16. public int filterOrder() {

  17.  return 1;

  18. }

  19. @Override

  20. public boolean shouldFilter() {

  21.  RequestContext ctx = RequestContext.getCurrentContext();

  22.  String requestURL = ctx.getRequest().getRequestURL().toString();

  23.  return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));

  24. }

  25. @Override

  26. public Object run() {

  27.  RequestContext ctx = RequestContext.getCurrentContext();

  28.  HttpServletRequest request = ctx.getRequest();

  29.  log.info("RouteFilter: " + String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

  30.  return null;

  31. }

  32. }

  1. package com.cfeindia.examples.zuul.filter;

  2. import javax.servlet.http.HttpServletResponse;

  3. import org.slf4j.Logger;

  4. import org.slf4j.LoggerFactory;

  5. import org.springframework.stereotype.Component;

  6. import com.netflix.zuul.ZuulFilter;

  7. import com.netflix.zuul.context.RequestContext;

  8. @Component

  9. public class PostFilter extends ZuulFilter {

  10. private static Logger log = LoggerFactory.getLogger(PostFilter.class);

  11. @Override

  12. public String filterType() {

  13.  return "post";

  14. }

  15. @Override

  16. public int filterOrder() {

  17.  return 1;

  18. }

  19. @Override

  20. public boolean shouldFilter() {

  21.  RequestContext ctx = RequestContext.getCurrentContext();

  22.  String requestURL = ctx.getRequest().getRequestURL().toString();

  23.  return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));

  24. }

  25. @Override

  26. public Object run() {

  27.  HttpServletResponse response = RequestContext.getCurrentContext().getResponse();

  28.  log.info("PostFilter: " + String.format("response is %s", response));

  29.  return null;

  30. }

  31. }

  1. package com.cfeindia.examples.zuul.filter;

  2. import javax.servlet.http.HttpServletResponse;

  3. import org.slf4j.Logger;

  4. import org.slf4j.LoggerFactory;

  5. import org.springframework.stereotype.Component;

  6. import com.netflix.zuul.ZuulFilter;

  7. import com.netflix.zuul.context.RequestContext;

  8. @Component

  9. public class ErrorFilter extends ZuulFilter {

  10. private static Logger log = LoggerFactory.getLogger(PostFilter.class);

  11. @Override

  12. public String filterType() {

  13.  return "error";

  14. }

  15. @Override

  16. public int filterOrder() {

  17.  return 1;

  18. }

  19. @Override

  20. public boolean shouldFilter() {

  21.  RequestContext ctx = RequestContext.getCurrentContext();

  22.  String requestURL = ctx.getRequest().getRequestURL().toString();

  23.  return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));

  24. }

  25. @Override

  26. public Object run() {

  27.  HttpServletResponse response = RequestContext.getCurrentContext().getResponse();

  28.  log.info("ErrorFilter: " + String.format("response is %s", response));

  29.  return null;

  30. }

  31. }

在Spring Boot的application.yml文件中配置Zuul和actuator:

  1. server:

  2.  port: ${appPort:8071}

  3. # Actuator endpoint path (/admin/info, /admin/health, ...)

  4. server.servlet-path: /

  5. management.context-path: /admin

  6. management.security.enabled: false

  7. endpoints.health.sensitive: false

  8. # ribbon.eureka.enabled: false

  9. zuul:

  10.  ignoredPatterns: /**/admin/**, /proxyurl

  11.  routes:

  12.    zuulDemo1:

  13.      path: /**

  14.      url: http://localhost/admin/health

  15.      # stripPrefix set to true if context path is set to /

  16.      stripPrefix: true

现在,Service对于不同的功能有不同的方法。创建ZuulDynamicRoutingService类并引入必要依赖,如下所示:

  1. package com.cfeindia.examples.zuul.service;

  2. import java.util.HashSet;

  3. import javax.annotation.PostConstruct;

  4. import org.slf4j.Logger;

  5. import org.slf4j.LoggerFactory;

  6. import org.springframework.beans.factory.annotation.Autowired;

  7. import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;

  8. import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;

  9. import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;

  10. import org.springframework.stereotype.Service;

  11. import org.springframework.util.StringUtils;

  12. import com.cfeindia.examples.zuul.dao.DynamicRouteRedisRepository;

  13. import com.cfeindia.examples.zuul.model.DynamicRoute;

  14. import com.cfeindia.examples.zuul.model.DynamicRouteResponse;

  15. @Service

  16. public class ZuulDynamicRoutingService {

  17. private static final Logger logger = LoggerFactory.getLogger(ZuulDynamicRoutingService.class);

  18. private static final String HTTP_PROTOCOL = "http://";

  19. private final ZuulProperties zuulProperties;

  20. private final ZuulHandlerMapping zuulHandlerMapping;

  21. private final DynamicRouteRedisRepository dynamicRouteRedisRepository;

  22. @Autowired

  23. public ZuulDynamicRoutingService(final ZuulProperties zuulProperties, final ZuulHandlerMapping zuulHandlerMapping,

  24.   final DynamicRouteRedisRepository dynamicRouteRedisRepository) {

  25.   this.zuulProperties = zuulProperties;

  26.   this.zuulHandlerMapping = zuulHandlerMapping;

  27.   this.dynamicRouteRedisRepository = dynamicRouteRedisRepository;

  28.  }

  29.  .......

添加创建动态路由的方法。下面是请求JSON示例:

  1. {

  2. "requestURIUniqueKey" : "api1UniqueKey",

  3. "requestURI": "/api1",

  4. "targetURLHost": "localhost",

  5. "targetURLPort": "8081",

  6. "targetURIPath": "/proxy-api1"

  7. }

ZuulRoute映射需要一个独一无二的key往map中添加route,因此API客户端应该总是为不同的请求URI和路由信息发送不同的唯一key;否则,新路由将覆盖已经存在的路由。

dynamicRoute.requestURIUniqueKey在每个请求中应该有所不同。它可以是任何字符串。请求URI和目标URI路径应该从“/”开始,以使route正常工作。

在ZuulDynamicRoutingService中添加一条Route的相关操作

在添加一条route时,这几行代码很重要:

  1. zuulProperties.getRoutes().put(dynamicRoute.getRequestURIUniqueKey(),

  2. new ZuulRoute(

  3. dynamicRoute.getRequestURIUniqueKey(),

  4. dynamicRoute.getRequestURI() + "/**",

  5. null, url, true, false, new HashSet<>())

  6. );

  7. zuulHandlerMapping.setDirty(true);

  1. public DynamicRouteResponse addDynamicRoute(DynamicRoute dynamicRoute) {

  2. logger.debug("request received in service to add {}", dynamicRoute);

  3. addDynamicRouteInZuul(dynamicRoute);

  4. logger.debug("going to add in cache {}", dynamicRoute);

  5. addToCache(dynamicRoute);

  6. logger.debug("added in cache {}", dynamicRoute);

  7. zuulHandlerMapping.setDirty(true);

  8. DynamicRouteResponse dynamicRouteResponse = new DynamicRouteResponse();

  9. logger.debug("response sent {}", dynamicRouteResponse);

  10. return dynamicRouteResponse;

  11. }

  12. private void addDynamicRouteInZuul(DynamicRoute dynamicRoute) {

  13. String url = createTargetURL(dynamicRoute);

  14. zuulProperties.getRoutes().put(dynamicRoute.getRequestURIUniqueKey(),

  15.  new ZuulRoute(dynamicRoute.getRequestURIUniqueKey(), dynamicRoute.getRequestURI() + "/**",

  16.   null, url, true, false, new HashSet < > ()));

  17. }

  18. private String createTargetURL(DynamicRoute dynamicRoute) {

  19. StringBuilder sb = new StringBuilder(HTTP_PROTOCOL);

  20. sb.append(dynamicRoute.getTargetURLHost()).append(":").append(dynamicRoute.getTargetURLPort());

  21. if (StringUtils.isEmpty(dynamicRoute.getTargetURIPath())) {

  22.  sb.append("");

  23. } else {

  24.  sb.append(dynamicRoute.getTargetURIPath());

  25. }

  26. return sb.toString();

  27. }

此外,我们还需要在Redis缓存中添加路由信息:

  1. private void addToCache(final DynamicRoute dynamicRoute) {

  2. DynamicRoute dynamicRouteSaved = dynamicRouteRedisRepository.save(dynamicRoute);

  3. logger.debug("Added in cache {}", dynamicRouteSaved);

  4. }

按如下步骤操作,可以从Zuul Mapping和Redis缓存中删除route。

在移除一个route时,这几行代码比较重要。

  1. ZuulRoute zuulRoute = zuulProperties.getRoutes().remove(requestURIUniqueKey);

  2. zuulHandlerMapping.setDirty(true)

完整的代码:

  1. public Boolean removeDynamicRoute(final String requestURIUniqueKey) {

  2. DynamicRoute dynamicRoute = new DynamicRoute();

  3. //Removal from redis will be done from unique key. No need for other params. So create object

  4. //with just unique key

  5. dynamicRoute.setRequestURIUniqueKey(requestURIUniqueKey);

  6. if (zuulProperties.getRoutes().containsKey(requestURIUniqueKey)) {

  7.  ZuulRoute zuulRoute = zuulProperties.getRoutes().remove(requestURIUniqueKey);

  8.  logger.debug("removed the zuul route {}", zuulRoute);

  9.  //Removal from redis will be done from unique key. No need for other params

  10.  removeFromCache(dynamicRoute);

  11.  zuulHandlerMapping.setDirty(true);

  12.  return Boolean.TRUE;

  13. }

  14. return Boolean.FALSE;

  15. }

  16. private void removeFromCache(final DynamicRoute dynamicRoute) {

  17. logger.debug("removing the dynamic route {}", dynamicRoute);

  18. //Removal from redis will be done from unique key. No need for other params

  19. dynamicRouteRedisRepository.delete(dynamicRoute);

  20. }

在服务器启动时,从Redis缓存恢复路由可以这样做:

  1. /**

  2. * Load all routes from redis cache to restore the existing routes while restarting the zuul server

  3. */

  4. @PostConstruct

  5. public void initialize() {

  6. try {

  7.  dynamicRouteRedisRepository.findAll().forEach(dynamicRoute -> {

  8.   addDynamicRouteInZuul(dynamicRoute);

  9.  });

  10.  zuulHandlerMapping.setDirty(true);

  11. } catch (Exception e) {

  12.  logger.error("Exception in loading any previous route while restarting zuul routes.", e);

  13. }

  14. }

Demo

下面展示了不同操作及其对服务器数据影响的快照列表。Spring Boot公开了URL /admin/routes来检查Zuul路由。

本地服务器下的完整URL是 http://localhost:8071/admin/routes

增加路由:

查看已经添加的路由:

增加另一个路由:

再次显示已经添加的路由。查看已经添加的路由数量:

删除一个路由:

在删除一个路由后,再查看一下路由:

待办事项

通过添加一些API来停止Zuul服务器。重新启动服务器并使用 http://localhost:8071/admin/routes查看路由,如果它们是从Redis加载的。

总结

本文解释了在JVM运行时中使用REST API在Zuul服务器上注册动态路由。它在Redis缓存中保存路由信息。

我们展示了如何使它具有容错功能,以及如何在重新启动Zuul服务器时从Redis缓存中恢复以前的路由。

本例包含了使用Redis保存和检索数据的功能,还演示了在Spring boot/Spring MVC项目中如何在服务器启动时加载数据。

此外,像Mongo这样的数据库可以代替Redis,以更好的方式确保路由不丢失。

从GitHub下载这个项目https://github.com/vikasanandgit/zuul-route-redis。

这是另一篇关于使用Zuul的文章 ,其中一个子域的请求被路由到子路径,并且子域到子路径的路由可以动态注册。

招人:数心,造化心数奇;用心等你...

上一篇:【SFA官方翻译】:使用 Spring Boot 2.0、Eureka 和 Spring Cloud 的微服务快速指南

点击阅读原文查看更多

关注公众号


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存