该内容已被发布者删除 该内容被自由微信恢复
文章于 2021年9月5日 被检测为删除。
查看原文
被用户删除
其他

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

点击关注 👉 鸭哥聊Java 2021-09-05
大家好,我是你们的鸭哥
记得点击关注下方公众号,领取 编程资
作者:冷冷zz
来源:https://my.oschina.net/giegie/blog/2874300


# 动态路由背景


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

例如:
# zuul 的配置形式routes:pig-auth:path: /auth/**serviceId: pig-authstripPrefix: true # gateway 的配置形式routes:- id: pigx-authuri: lb://pigx-authpredicates:- Path=/auth/**filters:- ValidateCodeGatewayFilter

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

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

# Spring Cloud Gateway 路由加载源码


1.DispatcherHandler 接管用户请求

2.RoutePredicateHandlerMapping 路由匹配
  • 根据RouteLocator获取 RouteDefinitionLocator

  •  返回多个RouteDefinitionLocator.getRouteDefinitions()的路由定义信息


3.FilteringWebHandler执行路由定义中的filter 最后路由到具体的业务服务中


# Spring Cloud Gateway 默认动态路由实现


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

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

# 扩展基于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 */@Overridepublic List<SysRouteConf> routes() { SysRouteConf condition = new SysRouteConf(); condition.setDelFlag(CommonConstant.STATUS_NORMAL);return baseMapper.selectList(new EntityWrapper<>(condition)); }
/** * 更新路由信息 * * @param routes 路由信息 * @return */@Overridepublic 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;
Object id = map.get("routeId");if (id != null) { vo.setId(String.valueOf(id)); }
Object predicates = map.get("predicates");if (predicates != null) { JSONArray predicatesArray = (JSONArray) predicates; List<PredicateDefinition> predicateDefinitionList = predicatesArray.toList(PredicateDefinition.class); vo.setPredicates(predicateDefinitionList); }
Object filters = map.get("filters");if (filters != null) { JSONArray filtersArray = (JSONArray) filters; List<FilterDefinition> filterDefinitionList = filtersArray.toList(FilterDefinition.class); vo.setFilters(filterDefinitionList); }
Object uri = map.get("uri");if (uri != null) { vo.setUri(URI.create(String.valueOf(uri))); }
Object order = map.get("order");if (order != null) { vo.setOrder(Integer.parseInt(String.valueOf(order))); }
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); redisTemplate.opsForHash().put(CommonConstant.ROUTE_KEY, vo.getId(), vo); 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

/** * @author lengleng * @date 2018/10/31 * <p> * redis 保存路由信息,优先级比配置文件高 */@Slf4j@Component@AllArgsConstructorpublic class RedisRouteDefinitionWriter implements RouteDefinitionRepository {private final RedisTemplate redisTemplate;
@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return route.flatMap(r -> { RouteDefinitionVo vo = new RouteDefinitionVo(); BeanUtils.copyProperties(r, vo); log.info("保存路由信息{}", vo); redisTemplate.opsForHash().put(CommonConstant.ROUTE_KEY, r.getId(), vo);return Mono.empty(); }); }@Overridepublic Mono<Void> delete(Mono<String> routeId) { routeId.subscribe(id -> { log.info("删除路由信息{}", id); redisTemplate.opsForHash().delete(CommonConstant.ROUTE_KEY, id); });return Mono.empty(); }
/** * 动态路由入口 * * @return */@Overridepublic Flux<RouteDefinition> getRouteDefinitions() { redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CommonConstant.ROUTE_KEY); List<RouteDefinition> definitionList = new ArrayList<>(); values.forEach(vo -> { RouteDefinition routeDefinition = new RouteDefinition(); BeanUtils.copyProperties(vo, routeDefinition); definitionList.add(vo); }); log.debug("redis 中路由定义条数:{}, {}", definitionList.size(), definitionList);return Flux.fromIterable(definitionList); }}

3.库表定义



热门推荐:

扫描关注,永不迷路
点分享点点赞点在看
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

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

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