Dynamic Routing background

Whether you are using Zuul or the Spring Cloud Gateway, the official documentation provides solutions based on how the configuration file is configured

Such as:

  # Zuul configuration form
  routes:
    pig-auth:
      path: /auth/**
      serviceId: pig-auth
      stripPrefix: true
  Gateway configuration
  routes:
  - id: pigx-auth
  	uri: lb://pigx-auth
    predicates:
    - Path=/auth/**
    filters:
    - ValidateCodeGatewayFilter

Copy the code

Configuration changes require service restart, which cannot meet the requirements of dynamic refreshes and real-time changes during actual production.

Based on the above analysis, Pig has provided Zuul version of the dynamic routing function, with Git address portal, the effect of the following picture can be real-time configuration modification update.

Spring Cloud Gateway route load source code

  1. DispatcherHandler takes over user requests
  2. RoutePredicateHandlerMapping routing matching
    1. Get RouteDefinitionLocator according to the RouteLocator
    2. Return multiple RouteDefinitionLocator. GetRouteDefinitions route definition information ()
  3. FilteringWebHandler executes the filter in the route definition and routes it to the specific service

Spring Cloud Gateway implements dynamic routing by default

GatewayControllerEndpoint based on actuate the endpoint of the default implementation, support the JVM level of dynamic routing, cannot be serialized storage

// The default implementation of dynamic routing information saving is memory based implementation
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
	private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
	@Override
	public Mono<Void> save(Mono<RouteDefinition> route){}
	@Override
	public Mono<Void> delete(Mono<String> routeId){}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions(a){}}Copy the code

The extension is based on Mysql + Redis to store distributed dynamic components

Why use Mysql and Redis at the same time?

  1. Spring Cloud Gateway is based on WebFlux backpressure and does not support mysql database for the time being
  2. Redis-reactive supports the back pressure of Spring CloudGateway while also achieving distributed, high performance

Extended thinking

  1. Add a routing management module, reference GatewayControllerEndpoint implementation, load startup database in the configuration file to Redis
  2. Rewrite RouteDefinitionRepository gateway module, getRouteDefinitions (), which is read to Redis can be realized
  3. Front-end with JSON-view similar plug-in, directly modify display.

The specific implementation

  1. Route management module core processing logic, obtain routes and update routes
/** * @author lengleng * @date 2018 月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; / * * * to get all routing * < p > * RedisRouteDefinitionWriter in Java. * PropertiesRouteDefinitionLocator Java @ * *return
	 */
	@Override
	public List<SysRouteConf> routes() {
		SysRouteConf condition = new SysRouteConf();
		condition.setDelFlag(CommonConstant.STATUS_NORMAL);
		returnbaseMapper.selectList(new EntityWrapper<>(condition)); } /** * Updates the routing information ** @param routes Routes * @return*/ @override public Mono<Void> editRoutes(JSONArray routes) redisTemplate.delete(CommonConstant.ROUTE_KEY); log.info("Clear gateway route {}", result); Redis List<RouteDefinitionVo> routeDefinitionVoList = new ArrayList<>(); routes.forEach(value -> { log.info("Update route ->{}", 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)); / / insert effective routing List < SysRouteConf > routeConfList = routeDefinitionVoList. Stream (). The 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("Updating gateway route completed");

		this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
		returnMono.empty(); }}Copy the code
  1. The gateway custom RedisRouteDefinitionRepository
  @Slf4j
  @Component
  @AllArgsConstructor
  public class RedisRouteDefinitionWriter implements RouteDefinitionRepository {
  	private final RedisTemplate redisTemplate;
  
  	@Override
  	public Mono<Void> save(Mono<RouteDefinition> route) {
  		return route.flatMap(r -> {
  			RouteDefinitionVo vo = new RouteDefinitionVo();
  			BeanUtils.copyProperties(r, vo);
  			log.info("Save routing information {}", vo);
  			redisTemplate.opsForHash().put(CommonConstant.ROUTE_KEY, r.getId(), vo);
  			return Mono.empty();
  		});
  	}
  	@Override
  	public Mono<Void> delete(Mono<String> routeId) {
  		routeId.subscribe(id -> {
  			log.info("Delete routing information {}", id);
  			redisTemplate.opsForHash().delete(CommonConstant.ROUTE_KEY, id);
  		});
  		return Mono.empty();
  	}
  
  	@Override
  	public 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 routing definitions: {}, {}", definitionList.size(), definitionList);
  		returnFlux.fromIterable(definitionList); }}Copy the code

3. Library table definition