Spring Cloud Gateway 数据库存储路由信息的扩展方案

动态路由背景

​ 无论你在使用Zuul还是Spring Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件配置的方式

例如:

1 # zuul 的配置形式 2 routes: 3 pig-auth: 4 path: /auth/** 5 serviceId: pig-auth 6 stripPrefix: true 7 # gateway 的配置形式 8 routes: 9 - id: pigx-auth 10 uri: lb://pigx-auth 11 predicates: 12 - Path=/auth/** 13 filters: 14 - ValidateCodeGatewayFilter 15

配置更改需要重启服务,不能满足实际生产过程中的动态刷新、实时变更的业务需求。

​ 基于以上分析 pig已经提供了基于Zuul版本的动态路由功能,附Git 地址传送门,效果如下图可以实时配置修改刷新。

Spring Cloud Gateway 路由加载源码

  1. DispatcherHandler 接管用户请求
  2. RoutePredicateHandlerMapping 路由匹配
    1. 根据RouteLocator获取 RouteDefinitionLocator
    2. 返回多个RouteDefinitionLocator.getRouteDefinitions()的路由定义信息
  3. FilteringWebHandler执行路由定义中的filter 最后路由到具体的业务服务中

Spring Cloud Gateway 默认动态路由实现

GatewayControllerEndpoint 基于actuate端点的默认实现,支持JVM 级别的动态路由,不能序列化存储

1// 上图动态路由的信息保存的默认实现是基于内存的实现 2public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { 3 private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>()); 4 @Override 5 public Mono<Void> save(Mono<RouteDefinition> route){} 6 @Override 7 public Mono<Void> delete(Mono<String> routeId){} 8 9 @Override 10 public Flux<RouteDefinition> getRouteDefinitions(){} 11}

扩展基于Mysql + Redis存储分布式动态组件

为什么使用Mysql的同时,又要使用Redis?

  1. spring cloud gateway 基于webflux 背压,暂时不支持mysql 数据库
  2. redis-reactive 支持 spring cloud gateway 的背压,同时还可以实现分布式,高性能

扩展思路

  1. 增加一个路由管理模块,参考GatewayControllerEndpoint实现,启动时加载数据库中配置文件到Redis
  2. 网关模块重写RouteDefinitionRepository,getRouteDefinitions()取Redis中读取即可实现
  3. 前端配合 json-view 类似插件,直接修改展示。

具体实现

  1. 路由管理模块核心处理逻辑,获取路由和更新路由

    /**

    • @author lengleng

    • @date 2018年11月06日10:27:55

    • <p>
    • 动态路由处理类 */ @Slf4j @AllArgsConstructor @Service("sysRouteConfService") public class SysRouteConfServiceImpl extends ServiceImpl<SysRouteConfMapper, SysRouteConf> implements SysRouteConfService { private final RedisTemplate redisTemplate; private final ApplicationEventPublisher applicationEventPublisher;

      /**

      • 获取全部路由
      • <p>
      • RedisRouteDefinitionWriter.java
      • PropertiesRouteDefinitionLocator.java
      • @return */ @Override public List<SysRouteConf> routes() { SysRouteConf condition = new SysRouteConf(); condition.setDelFlag(CommonConstant.STATUS_NORMAL); return baseMapper.selectList(new EntityWrapper<>(condition)); }

      /**

      • 更新路由信息

      • @param routes 路由信息

      • @return */ @Override public Mono<Void> editRoutes(JSONArray routes) { // 清空Redis 缓存 Boolean result = redisTemplate.delete(CommonConstant.ROUTE_KEY); log.info("清空网关路由 {} ", result);

        // 遍历修改的routes,保存到Redis List<RouteDefinitionVo> routeDefinitionVoList = new ArrayList<>(); routes.forEach(value -> { log.info("更新路由 ->{}", value); RouteDefinitionVo vo = new RouteDefinitionVo(); Map<String, Object> map = (Map) value;

        1 Object id = map.get("routeId"); 2 if (id != null) { 3 vo.setId(String.valueOf(id)); 4 } 5 6 Object predicates = map.get("predicates"); 7 if (predicates != null) { 8 JSONArray predicatesArray = (JSONArray) predicates; 9 List<PredicateDefinition> predicateDefinitionList = 10 predicatesArray.toList(PredicateDefinition.class); 11 vo.setPredicates(predicateDefinitionList); 12 } 13 14 Object filters = map.get("filters"); 15 if (filters != null) { 16 JSONArray filtersArray = (JSONArray) filters; 17 List<FilterDefinition> filterDefinitionList 18 = filtersArray.toList(FilterDefinition.class); 19 vo.setFilters(filterDefinitionList); 20 } 21 22 Object uri = map.get("uri"); 23 if (uri != null) { 24 vo.setUri(URI.create(String.valueOf(uri))); 25 } 26 27 Object order = map.get("order"); 28 if (order != null) { 29 vo.setOrder(Integer.parseInt(String.valueOf(order))); 30 } 31 32 redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); 33 redisTemplate.opsForHash().put(CommonConstant.ROUTE_KEY, vo.getId(), vo); 34 routeDefinitionVoList.add(vo);

        });

        // 逻辑删除全部 SysRouteConf condition = new SysRouteConf(); condition.setDelFlag(CommonConstant.STATUS_NORMAL); this.delete(new EntityWrapper<>(condition));

        //插入生效路由 List<SysRouteConf> routeConfList = routeDefinitionVoList.stream().map(vo -> { SysRouteConf routeConf = new SysRouteConf(); routeConf.setRouteId(vo.getId()); routeConf.setFilters(JSONUtil.toJsonStr(vo.getFilters())); routeConf.setPredicates(JSONUtil.toJsonStr(vo.getPredicates())); routeConf.setOrder(vo.getOrder()); routeConf.setUri(vo.getUri().toString()); return routeConf; }).collect(Collectors.toList()); this.insertBatch(routeConfList); log.debug("更新网关路由结束 ");

        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this)); return Mono.empty(); } }

  2. 网关自定义RedisRouteDefinitionRepository

    1/** 2 * @author lengleng 3 * @date 2018/10/31 4 * <p> 5 * redis 保存路由信息,优先级比配置文件高 6 */ 7@Slf4j 8@Component 9@AllArgsConstructor 10public class RedisRouteDefinitionWriter implements RouteDefinitionRepository { 11 private final RedisTemplate redisTemplate; 12 13 @Override 14 public Mono<Void> save(Mono<RouteDefinition> route) { 15 return route.flatMap(r -> { 16 RouteDefinitionVo vo = new RouteDefinitionVo(); 17 BeanUtils.copyProperties(r, vo); 18 log.info("保存路由信息{}", vo); 19 redisTemplate.opsForHash().put(CommonConstant.ROUTE_KEY, r.getId(), vo); 20 return Mono.empty(); 21 }); 22 } 23 @Override 24 public Mono<Void> delete(Mono<String> routeId) { 25 routeId.subscribe(id -> { 26 log.info("删除路由信息{}", id); 27 redisTemplate.opsForHash().delete(CommonConstant.ROUTE_KEY, id); 28 }); 29 return Mono.empty(); 30 } 31 32 /** 33 * 动态路由入口 34 * 35 * @return 36 */ 37 @Override 38 public Flux<RouteDefinition> getRouteDefinitions() { 39 redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); 40 List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CommonConstant.ROUTE_KEY); 41 List<RouteDefinition> definitionList = new ArrayList<>(); 42 values.forEach(vo -> { 43 RouteDefinition routeDefinition = new RouteDefinition(); 44 BeanUtils.copyProperties(vo, routeDefinition); 45 definitionList.add(vo); 46 }); 47 log.debug("redis 中路由定义条数: {}, {}", definitionList.size(), definitionList); 48 return Flux.fromIterable(definitionList); 49 } 50}

3.库表定义

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )