0xZzzz 2019-11-09
在之前的 zuul 我们讲了。怎么去动态的获取路由。作为Spring Cloud 亲儿子的存在 gateway 不可能不支持动态路由。今天我们初探一下gateway 的动态路由。
思考:
配置中心刷新routes配置信息。路由信息刷新改变。利用事件发布,利用配置中心完成动态刷新路由。本次改造我们使用自定义存储方式达到手动触发动态路由 InMemoryRouteDefinitionRepository 默认使用。这个类就是把握们当前的所有的路由routes 存储在内存当中。当服务重启或者刷新,内存就不复存在。ps: 因为本项目是用nacos 注册中心也是配置中心。可以存储在nacos 配置中心里面。 RouteDefinitionRepository 接口是InMemoryRouteDefinitionRepository 是它的接口类 继承了 RouteDefinitionLocator 、RouteDefinitionWriter俩个接口。
RouteDefinitionWriter 用来实现路由的添加与删除。
基于这个原则我们在动态添加或者删除路由的时候,就可以根据这个接口实现去满足我们动态的控制路由规则。
RouteDefinitionRepository 这个接口成为了关键点我们来重新动态路由其实也是基于这个接口来实现
实体类
package com.xian.cloud.model; import lombok.Data; /** * @Author: xlr * @Date: Created in 5:10 PM 2019/9/29 */ @Data public class GatewayRoutesEntity { private Long id; private String serviceId; private String uri; private String predicates; private String filters; }
GatewayRoutesService 接口
package com.xian.cloud.service; import com.xian.cloud.model.GatewayRoutesEntity; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import java.util.List; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 16:08 */ public interface GatewayRoutesService { List<GatewayRoutesEntity> findAll() throws Exception; String loadRouteDefinition() throws Exception; GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception; void deleteById(String id) throws Exception; boolean existsById(String id)throws Exception; List<PredicateDefinition> getPredicateDefinition(String predicates) ; List<FilterDefinition> getFilterDefinition(String filters) ; }
GatewayRoutesService 实现类
package com.xian.cloud.service.impl; import com.alibaba.fastjson.JSON; import com.xian.cloud.model.GatewayRoutesEntity; import com.xian.cloud.service.GatewayRoutesService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import java.net.URI; import java.util.List; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 16:09 */ @Service @Slf4j public class GatewayRoutesServiceImpl implements GatewayRoutesService { public static final String GATEWAY_DEFINE_LIST_KEY = "gateway_routes_list_key"; @Autowired private RedisTemplate redisTemplate; @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; @Override public List <GatewayRoutesEntity> findAll() throws Exception { Long size = redisTemplate.opsForList().size( GATEWAY_DEFINE_LIST_KEY ); List<GatewayRoutesEntity> list = redisTemplate.opsForList().range( GATEWAY_DEFINE_LIST_KEY, 0, size ); return list; } @Override public String loadRouteDefinition() { try { List <GatewayRoutesEntity> gatewayDefineServiceAll = findAll(); if (gatewayDefineServiceAll == null) { return "none route defined"; } for (GatewayRoutesEntity gatewayDefine : gatewayDefineServiceAll) { RouteDefinition definition = new RouteDefinition(); definition.setId( gatewayDefine.getServiceId() ); definition.setUri( new URI( gatewayDefine.getUri() ) ); List <PredicateDefinition> predicateDefinitions = getPredicateDefinition(gatewayDefine.getPredicates()); if (predicateDefinitions != null) { definition.setPredicates( predicateDefinitions ); } List <FilterDefinition> filterDefinitions = getFilterDefinition(gatewayDefine.getFilters()); if (filterDefinitions != null) { definition.setFilters( filterDefinitions ); } routeDefinitionWriter.save( Mono.just( definition ) ).subscribe(); publisher.publishEvent( new RefreshRoutesEvent( this ) ); } return "success"; } catch (Exception e) { e.printStackTrace(); return "failure"; } } /** * 获取所有的 自定义路由规则 * @param gatewayDefine * @return * @throws Exception */ @Override public GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception { log.info( "save RouteDefinition : {}",gatewayDefine ); redisTemplate.opsForList().rightPush( GATEWAY_DEFINE_LIST_KEY, gatewayDefine ); return gatewayDefine; } @Override public void deleteById(String id) throws Exception { List <GatewayRoutesEntity> all = findAll(); for (GatewayRoutesEntity gatewayDefine : all) { if(gatewayDefine.getServiceId().equals( id )){ redisTemplate.opsForList().remove( GATEWAY_DEFINE_LIST_KEY,0, gatewayDefine); } } } @Override public boolean existsById(String id) throws Exception { List <GatewayRoutesEntity> all = findAll(); for (GatewayRoutesEntity gatewayDefine : all) { if(gatewayDefine.getServiceId().equals( id )){ return true; } } return false; } @Override public List<PredicateDefinition> getPredicateDefinition(String predicates) { if ( StringUtils.isNotBlank( predicates )) { List<PredicateDefinition> predicateDefinitionList = JSON.parseArray(predicates, PredicateDefinition.class); return predicateDefinitionList; } else { return null; } } @Override public List<FilterDefinition> getFilterDefinition(String filters) { if (StringUtils.isNotBlank( filters )) { List<FilterDefinition> filterDefinitionList = JSON.parseArray(filters, FilterDefinition.class); return filterDefinitionList; } else { return null; } } }
然后我们在进行 RouteDefinitionLocator 、RouteDefinitionWriter 俩个接口的声明 在配置文件中
package com.xian.cloud.config; import com.xian.cloud.repository.GatewayRoutesRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.route.RouteDefinitionLocator; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; /** * <Description> 动态更新路由 * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 17:12 */ @Configuration @Slf4j public class GatewayRoutesDefinitionConfig { @Bean RouteDefinitionLocator routeDefinitionLocator(){ return new GatewayRoutesRepository(); } @Bean @Primary RouteDefinitionWriter routeDefinitionWriter(){ return new GatewayRoutesRepository(); } }
RefreshRoutesEvent 事件发布刷新routes事件,通知网关。 这个事件是gateway 的事件。
package com.xian.cloud.event; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Component; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 17:20 */ @Component @Slf4j public class RefreshRouteService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } /** * 刷新路由表 */ public void refreshRoutes() { publisher.publishEvent(new RefreshRoutesEvent(this)); } }
然后我们还差一个手动触发的接口,创建GatewayRoutesController
package com.xian.cloud.controller; import com.xian.cloud.event.RefreshRouteService; import com.xian.cloud.model.RestResult; import com.xian.cloud.model.RestResultBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 17:18 */ @RestController @RequestMapping("/gateway") public class GatewayRoutesController { @Autowired private RefreshRouteService refreshRouteService; @GetMapping("/refreshRoutes") public RestResult refreshRoutes(){ refreshRouteService.refreshRoutes(); return RestResultBuilder.builder().success().build(); } }
到此为止,就完成了所有的代码。
启动服务
其实还有一种很简单直接的写法。参考重新定义 Spring Cloud 实战中的写法。
创建事件增加类DynamicRouteServiceImpl
package com.xian.cloud.event; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/09 10:40 */ @Slf4j @Service public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware { @Qualifier("routeDefinitionRepositor") @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; /** * 添加路由实体类 * @param definition * @return */ public boolean add(RouteDefinition definition){ routeDefinitionWriter.save((Mono<RouteDefinition>) Mono.just(definition).subscribe()); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return true; } /** * * @param definition 路由实体类 * @return */ public boolean update(RouteDefinition definition){ try { routeDefinitionWriter.delete(Mono.just(definition.getId())); }catch (Exception e){ log.error("update 失败。没有找到对应的路由ID :{}",definition.getId()); } routeDefinitionWriter.save((Mono<RouteDefinition>) (Mono.just(definition)).subscribe()); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return true; } /** * serviceId * @param id * @return */ public boolean del(String id){ routeDefinitionWriter.delete(Mono.just(id)); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return true; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } }
修改controller 添加三个方法
package com.xian.cloud.controller; import com.xian.cloud.event.DynamicRouteServiceImpl; import com.xian.cloud.event.RefreshRouteService; import com.xian.cloud.model.RestResult; import com.xian.cloud.model.RestResultBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.web.bind.annotation.*; /** * <Description> * * @author [email protected] * @version 1.0 * @createDate 2019/11/08 17:18 */ @RestController @RequestMapping("/gateway") public class GatewayRoutesController { @Autowired private RefreshRouteService refreshRouteService; @Autowired private DynamicRouteServiceImpl dynamicRouteService; @GetMapping("/refreshRoutes") public RestResult refreshRoutes(){ refreshRouteService.refreshRoutes(); return RestResultBuilder.builder().success().build(); } /** * * @param definition * @return */ @RequestMapping(value = "routes/add",method = RequestMethod.POST) public RestResult add(@RequestBody RouteDefinition definition){ boolean flag = dynamicRouteService.add(definition); if(flag){ return RestResultBuilder.builder().success().build(); } return RestResultBuilder.builder().failure().build(); } /** * * @param definition * @return */ @RequestMapping(value = "routes/update",method = RequestMethod.POST) public RestResult update(@RequestBody RouteDefinition definition){ boolean flag = dynamicRouteService.add(definition); if(flag){ return RestResultBuilder.builder().success().build(); } return RestResultBuilder.builder().failure().build(); } /** * * @param serviceId * @return */ @RequestMapping(value = "routes/del",method = RequestMethod.POST) public RestResult update(@RequestParam("serviceId") String serviceId){ boolean flag = dynamicRouteService.del(serviceId); if(flag){ return RestResultBuilder.builder().success().build(); } return RestResultBuilder.builder().failure().build(); } }
增删改。三个接口对外暴露。
以上就是gateway 的动态刷新路由。
动态刷新能满足我们在服务运维上的管理方便。但是因为这个整体使用的nacos 配置中心做的。这样做的成本考虑。动态刷新是不是真的有必要这么做。完全可以在配置中心配置然后刷新。就能触发路由的整体刷新。如果需要自己搭建运维管理中心。有自己的管理体系可以实现动态路由。
摘自参考 spring cloud 官方文档
服务器nacos 地址 http://47.99.209.72:8848/nacos
往期地址 spring cloud 文章地址
Spring Cloud Alibaba (nacos 注册中心搭建)
Spring Cloud Alibaba 使用nacos 注册中心
Spring Cloud Alibaba nacos 配置中心使用
Spring Cloud alibaba网关 sentinel zuul 四 限流熔断
Spring Cloud gateway 网关服务二 断言、过滤器
Spring Cloud gateway 三 自定义过滤器GatewayFilter
如何喜欢可以关注分享本公众号。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码