目录

一、概述

1. 手动添加 route 节点

二、Redis 动态加载的配置

1. RouteDefinitionLocator

2. RouteDefinitionVo 对 RouteDefinition 进行增强

3. RedisRouteDefinitionWriter 对路由节点进行增删改查

4. RedisUtils 工具类

5. RouteController

6. 在配置文件中添加

三、 测试结果

1. 启动服务步骤

2. 动态添加路由节点信息

3. 删除路由节点


Spring Cloud Gateway 学习专栏

1. Spring Cloud : Gateway 服务网关认识(一)

2. Spring Cloud :整合Gateway 学习 (二)

3. Spring Cloud:Gateway 路由定义定位器 RouteDefinitionLocator (三)

4. Spring Cloud : Gateway 网关过滤器 GatewayFilter(四)

5. Spring Cloud : Gateway 网关限流(五)

6. Spring Cloud:Gateway 集成 Sentinel (六)

7. Spring Cloud : Gateway Redis动态路由 (七)

8. Spring Cloud : Gateway 整合Swagger (八)

 

如果发现本文有错误的地方,请大家毫不吝啬,多多指教,欢迎大家评论,谢谢!


一、概述

Gateway配置路由主要有三种方式

  1. 用 yml 配置文件
  2. 手动添加 route 节点
  3. 写在分布式配置中心,修改配置文件可以动态加载

无论是代码配置还是写在yml文件中,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关,怎样代价就比较高。下面讲解是利用Redis装载我们 route 配置文件信息,当我们新增、删除、修改路由信息,会自动刷新自动配置配置信息,不需要重新启动服务。

1. 手动添加 route 节点

@Configuration
public class GatewayRotesConfig {


    @Bean
    protected RouteLocator routeLocator(RouteLocatorBuilder builder) {

        return builder.routes().route(
                // 请求字服务路径以product/**
                r -> r.path("/product/**","/catgory/**")
                .uri("lb://api-product")
                .id("product")
                .filter(new CustomX001Filter())
        ).build();

    }
}

 

二、Redis 动态加载的配置

1. RouteDefinitionLocator

可以查看上一篇写文章 路由定义定位器 RouteDefinitionLocator

主要通过 RouteDefinitionLocator 入口接口,RouteDefinitionRepository 接口中的方法用来对RouteDefinition进行增、删、查操作。

RouteDefinition 作为 GatewayProperties中的属性,在网关启动的时候读取配置文件中的相关配置信息

2. RouteDefinitionVo 对 RouteDefinition 进行增强


/**
 * 扩展此类支持序列化-RouteDefinition
 * @author Zou.LiPing
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class RouteDefinitionVo extends RouteDefinition implements Serializable {

	private static final long serialVersionUID = -4046906501288976257L;
	/**
	 * 路由名称
	 */
	private String routeName;
}

3. RedisRouteDefinitionWriter 对路由节点进行增删改查

ApplicationEventPublisherAware 作用事件发布通知

当我们修改 route 节点时,可用 ApplicationEventPublisher 通过发布事件,在利用监听,重新加载 route 配置节点信息,达到自动刷新配置文件信息

/**
 * redis 保存路由信息,优先级比配置文件高
 * @author Zou.LiPing
 */
@Component
@RequiredArgsConstructor
@Slf4j(topic = "RedisRouteDefinitionWriter")
public class RedisRouteDefinitionWriter implements RouteDefinitionRepository, ApplicationEventPublisherAware {

	private final RedisUtils redisUtils;
	private ApplicationEventPublisher publisher;

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			RouteDefinitionVo vo = new RouteDefinitionVo();
			BeanUtils.copyProperties(r, vo);
			log.info("保存路由信息{}", vo);
			redisUtils.hPut(CacheConstants.ROUTE_KEY,r.getId(), JSON.toJSONString(vo));
			refreshRoutes();
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		routeId.subscribe(id -> {
			log.info("删除路由信息={}", id);
			redisUtils.hDelete(CacheConstants.ROUTE_KEY, id);
			refreshRoutes();
		});
		return Mono.empty();
	}

	private void refreshRoutes() {
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
	}

	/**
	 * 动态路由入口
	 * @return Flux<RouteDefinition>
	 */
	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {

		List<RouteDefinition> definitionList = new ArrayList<>();
		Map<Object, Object> objectMap = redisUtils.hGetAll(CacheConstants.ROUTE_KEY);
		if (Objects.nonNull(objectMap)) {
			for (Map.Entry<Object, Object> objectObjectEntry : objectMap.entrySet()) {
				RouteDefinition routeDefinition = JSON.parseObject(objectObjectEntry.getValue().toString(),RouteDefinition.class);
				definitionList.add(routeDefinition);
			}
		}
		log.info("redis 中路由定义条数: {}, definitionList={}", definitionList.size(), definitionList);
		return Flux.fromIterable(definitionList);
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.publisher = applicationEventPublisher;
	}

}

4. RedisUtils 工具类

@Service
public class RedisUtils {

    @Autowired
    private StringRedisTemplate redisTemplate;

/** -------------------hash相关操作------------------------- */

    /**
     * 获取存储在哈希表中指定字段的值
     *
     * @param key
     * @param field
     * @return
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @param fields
     * @return
     */
    public List<Object> hMultiGet(String key, Collection<Object> fields) {
        return redisTemplate.opsForHash().multiGet(key, fields);
    }

    public void hPut(String key, String hashKey, String value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public void hPutAll(String key, Map<String, String> maps) {
        redisTemplate.opsForHash().putAll(key, maps);
    }

    /**
     * 仅当hashKey不存在时才设置
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 删除一个或多个哈希表字段
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hDelete(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 查看哈希表 key 中,指定的字段是否存在
     *
     * @param key
     * @param field
     * @return
     */
    public boolean hExists(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public Long hIncrBy(String key, Object field, long increment) {
        return redisTemplate.opsForHash().increment(key, field, increment);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public Double hIncrByFloat(String key, Object field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 获取所有哈希表中的字段
     *
     * @param key
     * @return
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取哈希表中字段的数量
     *
     * @param key
     * @return
     */
    public Long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 获取哈希表中所有值
     *
     * @param key
     * @return
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 迭代哈希表中的键值对
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }
}

5. RouteController


/**
 * route 接口控制
 * @date: 2021/4/20 14:52
 * @author Zou.LiPing
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/route")
public class RouteController {

    private final RedisRouteDefinitionWriter redisRouteDefinitionWriter;

    @GetMapping("save")
    public void save(RouteDefinition route) {
        Mono<RouteDefinition> routeDefinition = Mono.just(route);
        redisRouteDefinitionWriter.save(routeDefinition);
    }

    @GetMapping("delete")
    public void delete(String routeId) {
        Mono<String> just = Mono.just(routeId);
        redisRouteDefinitionWriter.delete(just);
    }

    @GetMapping("testSave")
    public void testSave(RouteDefinition route) {
        bulidRouteDefinitionParam(route);
       redisRouteDefinitionWriter.save(Mono.just(route));
    }


    private RouteDefinition bulidRouteDefinitionParam(RouteDefinition definition) {

        definition.setId("user");
        URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8200").build().toUri();
        definition.setUri(uri);

        //定义第一个断言
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        predicate.setName("Path");
        predicateParams.put("pattern", "/user/**");
        predicate.setArgs(predicateParams);

        //定义Filter
        Map<String, String> filterParams = new HashMap<>(8);
        FilterDefinition filter = new FilterDefinition();
        filter.setName("RequestRateLimiter");
        //该_genkey_前缀是固定的,见@link org.springframework.cloud.gateway.support.NameUtils类
        filterParams.put("redis-rate-limiter.replenishRate", "1");
        filterParams.put("redis-rate-limiter.burstCapacity", "2");
        filterParams.put("key-resolver", "#{@remoteAddrKeyResolver}");
        filter.setArgs(filterParams);

        Map<String, String> filter1Params = new HashMap<>(8);
        FilterDefinition RewritePath = new FilterDefinition();
        RewritePath.setName("RewritePath");
        // /user/(?<segment>.*),/$\{segment}
        filter1Params.put("regexp", "/user/(?<segment>.*)");
        filter1Params.put("replacement", "/$\\{segment}");
        RewritePath.setArgs(filter1Params);

        definition.setFilters(Arrays.asList(filter, RewritePath));
        definition.setPredicates(Arrays.asList(predicate));
        log.info("definition:{}",JSON.toJSONString(definition));
        return definition;
    }

}

 

6. 在配置文件中添加

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
server:
  port: 9000
service-url:
  user-service: http://localhost:8200

spring:
  application:
    name: mall-gateway


  main:
    allow-bean-definition-overriding: true
  ## redis配置
  redis:
    database: 0
    host: 47.103.20.21
    password: zlp123456
    port: 6379
    timeout: 7000

  cloud:
  ## nacos注册中心
    nacos:
      discovery:
        server-addr: 47.103.20.21:8848

    ## 整合sentinel
    sentinel:
      transport:
        dashboard: 47.103.20.21:8000
        port: 8080
      # 服务启动直接建立心跳连接
      eager: true
    



# 暴露 route 端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

三、 测试结果

1. 启动服务步骤

  1. 先启动 user-service 服务
  2. 启动 user-product 服务
  3. 启动 api-gateway 服务

 

启动项目,查看网关路由信息,访问 http://localhost:9000/actuator/gateway/routes ,因没有配置路由信息,因此返回结果为空数组

2. 动态添加路由节点信息

{
	"filters": [{
		"args": {
			"_genkey_0": "/product/(?<segment>.*)",
			"_genkey_1": "/$\\{segment}"
		},
		"name": "RewritePath"
	}],
	"id": "product",
	"order": 1,
	"predicates": [{
		"args": {
			"_genkey_0": "/product/**"
		},
		"name": "Path"
	}],
	"uri": "lb://api-product"
}

postman 添加路由节点

SpringCloud Gateway 中在这个类提供了 AbstractGatewayControllerEndpoint 提供了添加方法

http://127.0.0.1:9000/actuator/gateway/routes/product

 http://localhost:9000/actuator/gateway/routes

GatewayControllerEndpoint

3. 删除路由节点

http://127.0.0.1:9000/actuator/gateway/routes/product

 


源码地址

mall-gateway 这个项目

https://gitee.com/gaibianzlp/zlp-mall-demo.git


参考链接

1. SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由

2. 动态路由配置,使用Mysql,存储路由,实现集群gateway动态路由

3. Spring Cloud 系列之 Gateway 服务网关(三)

4. SpringCloud 微服务网关Gateway 动态路由配置

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐