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

目录

一、概述

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 动态路由配置

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本项目示例基于spring boot 最新版本(2.1.9)实现,Spring Boot、Spring Cloud 学习示例,将持续更新…… 在基于Spring Boot、Spring Cloud 分布微服务开发过程中,根据实际项目环境,需要选择、集成符合项目需求的各种组件和积累各种解决方案。基于这样的背景下,我开源了本示例项目,方便大家快速上手Spring Boot、Spring Cloud 。 每个示例都带有详细的介绍文档、作者在使用过程中踩过的坑、解决方案及参考资料,方便快速上手为你提供学习捷径,少绕弯路,提高开发效率。 有需要写关于spring boot、spring cloud示例,可以给我提issue哦 ## 项目介绍 spring boot demo 是一个Spring Boot、Spring Cloud的项目示例,根据市场主流的后端技术,共集成了30+个demo,未来将持续更新。该项目包含helloworld(快速入门)、web(ssh项目快速搭建)、aop(切面编程)、data-redis(redis缓存)、quartz(集群任务实现)、shiro(权限管理)、oauth2(四种认证模式)、shign(接口参数防篡改重放)、encoder(用户密码设计)、actuator(服务监控)、cloud-config(配置中心)、cloud-gateway(服务网关)、email(邮件发送)、cloud-alibaba(微服务全家桶)等模块 ### 开发环境 - JDK1.8 + - Maven 3.5 + - IntelliJ IDEA ULTIMATE 2019.1 - MySql 5.7 + ### Spring Boot 模块 模块名称|主要内容 ---|--- helloworld|[spring mvc,Spring Boot项目创建,单元测试](https://github.com/smltq/spring-boot-demo/blob/master/helloworld/HELP.md) web|[ssh项目,spring mvc,过滤器,拦截器,监视器,thymeleaf,lombok,jquery,bootstrap,mysql](https://github.com/smltq/spring-boot-demo/blob/master/web/HELP.md) aop|[aop,正则,前置通知,后置通知,环绕通知](https://github.com/smltq/spring-boot-demo/blob/master/aop/HELP.md) data-redis|[lettuce,redis,session redis,YAML配置,连接池,对象存储](https://github.com/smltq/spring-boot-demo/blob/master/data-redis/HELP.md) quartz|[Spring Scheduler,Quartz,分布式调度,集群,mysql持久化等](https://github.com/smltq/spring-boot-demo/blob/master/quartz/HELP.md) shiro|[授权、认证、加解密、统一异常处理](https://github.com/smltq/spring-boot-demo/blob/master/shiro/HELP.md) sign|[防篡改、防重放、文档自动生成](https://github.com/smltq/spring-boot-demo/blob/master/sign/HELP.md) security|[授权、认证、加解密、mybatis plus使用](https://github.com/smltq/spring-boot-demo/blob/master/security/HELP.md) mybatis-plus-generator|[基于mybatisplus代码自动生成](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-generator) mybatis-plus-crud|[基于mybatisplus实现数据库增、册、改、查](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-crud) encoder|[主流加密算法介绍、用户加密算法推荐](https://github.com/smltq/spring-boot-demo/blob/master/encoder/HELP.md) actuator|[autuator介绍](https://github.com/smltq/spring-boot-demo/blob/master/actuator/README.md) admin|[可视化服务监控、使用](https://github.com/smltq/spring-boot-demo/blob/master/admin/README.md) security-oauth2-credentials|[oauth2实现密码模式、客户端模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-credentials/README.md) security-oauth2-auth-code|[基于spring boot实现oauth2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-auth-code/README.md) mybatis-multi-datasource|[mybatis、数据库集群、读写分离、读库负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-multi-datasource) template-thymeleaf|[thymeleaf实现应用国际化示例](https://github.com/smltq/spring-boot-demo/blob/master/template-thymeleaf) mq-redis|[redis之mq实现,发布订阅模式](https://github.com/smltq/spring-boot-demo/blob/master/mq-redis) email|[email实现邮件发送](https://github.com/smltq/spring-boot-demo/blob/master/email) jGit|[java调用git命令、jgit使用等](https://github.com/smltq/spring-boot-demo/blob/master/jGit) webmagic|[webmagic实现某电影网站爬虫示例](https://github.com/smltq/spring-boot-demo/blob/master/webmagic) netty|[基于BIO、NIO等tcp服务器搭建介绍](https://github.com/smltq/spring-boot-demo/blob/master/netty) ### Spring Cloud 模块 模块名称|主要内容 ---|--- cloud-oauth2-auth-code|[基于spring cloud实现oath2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/cloud-oauth2-auth-code) cloud-gateway|[API主流网关、gateway快速上手](https://github.com/smltq/spring-boot-demo/blob/master/cloud-gateway) cloud-config|[配置中心(服务端、客户端)示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-config) cloud-feign|[Eureka服务注册中心、负载均衡、声明式服务调用](https://github.com/smltq/spring-boot-demo/blob/master/cloud-feign) cloud-hystrix|[Hystrix服务容错、异常处理、注册中心示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-hystrix) cloud-zuul|[zuul服务网关、过滤器、路由转发、服务降级、负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/cloud-zuul) cloud-alibaba|[nacos服务中心、配置中心、限流等使用(系列示例整理中...)](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba) #### Spring Cloud Alibaba 模块 模块名称|主要内容 ---|--- nacos|[Spring Cloud Alibaba(一)如何使用nacos服务注册和发现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README1.md) config|[Spring Cloud Alibaba(二)配置中心多项目、多配置文件、分目录实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README2.md) Sentinel|[Spring Cloud Alibaba(三)Sentinel之熔断降级](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README3.md) Dubbo|[Spring Cloud Alibaba(四)Spring Cloud与Dubbo的融合](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README4.md) RocketMQ|[Spring Cloud Alibaba(五)RocketMQ 异步通信实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README5.md) ### 其它 模块名称|主要内容 ---|--- leetcode|[力扣题解目录](https://github.com/smltq/spring-boot-demo/blob/master/leetcode) ## Spring Boot 概述 Spring Boot简化了基于Spring的应用开发,通过少量的代码就能创建一个独立的、产品级别的Spring应用。 Spring Boot为Spring平台及第三方库提供开箱即用的设置,这样你就可以有条不紊地开始。多数Spring Boot应用只需要很少的Spring配置。 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Sprin
Spring Cloud Gateway中,你可以使用Redis来实现动态获取Gateway的路由信息。下面是一个基本的实现思路: 1. **配置Redis**:首先,确保你已经正确配置了Redis,并能够通过Spring Boot进行连接和操作。你可以参考Spring Data Redis的官方文档来了解如何配置和使用Redis。 2. **创建自定义的RouteDefinitionLocator**:创建一个自定义的`RouteDefinitionLocator`实现,用于从Redis中获取路由信息。这个实现类需要实现`getRouteDefinitions()`方法,并在该方法中从Redis中获取路由配置信息。 ```java @Component public class RedisRouteDefinitionLocator implements RouteDefinitionLocator { private final RedisTemplate<String, Object> redisTemplate; public RedisRouteDefinitionLocator(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public Flux<RouteDefinition> getRouteDefinitions() { List<RouteDefinition> routeDefinitions = new ArrayList<>(); // 从Redis中获取路由配置信息,并将其转换为RouteDefinition对象 // 将解析后的RouteDefinition对象添加到routeDefinitions列表中 return Flux.fromIterable(routeDefinitions); } } ``` 3. **配置Gateway使用自定义的RouteDefinitionLocator**:在配置类中,将自定义的`RouteDefinitionLocator`注册为Bean,并将其设置为Gateway的`RouteDefinitionLocator`。 ```java @Configuration public class GatewayConfig { @Bean public RedisTemplate<String, Object> redisTemplate() { // 创建和配置RedisTemplate实例 return redisTemplate; } @Bean public RouteDefinitionLocator routeDefinitionLocator(RedisTemplate<String, Object> redisTemplate) { return new RedisRouteDefinitionLocator(redisTemplate); } @Bean public RouteDefinitionLocatorComposite routeDefinitionLocatorComposite(RouteDefinitionLocator... locators) { return new RouteDefinitionLocatorComposite(Arrays.asList(locators)); } } ``` 在上述配置中,`RedisTemplate`是一个用于与Redis进行交互的Spring Data Redis的组件。你可以根据实际情况进行配置和使用。 4. **配置Redis中的路由信息**:在Redis中存储路由信息,可以使用Hash结构来存储每个路由的详细信息,例如路由ID、URI、谓词、过滤器等。 ```shell HSET gateway_routes route1 '{ "id": "route1", "uri": "http://example.com", "predicates": [{ "name": "Path", "args": { "pattern": "/example" } }] }' HSET gateway_routes route2 '{ "id": "route2", "uri": "http://example.org", "predicates": [{ "name": "Path", "args": { "pattern": "/example2" } }] }' ``` 上述示例中,使用Hash结构存储了两个路由的详细信息,每个路由信息以JSON格式表示。 通过以上步骤,你就可以通过Redis动态获取Gateway的路由信息了。每当路由配置信息在Redis中发生变化时,Gateway会自动重新加载路由信息。 需要注意的是,上述示例仅提供了一个基本的思路和示例代码,你可能需要根据实际需求进行适当的调整和扩展。 希望以上内容对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值