SpringCloud Zuul+Mysql implements dynamic gateway

Introduction to Zuul

Zuul is an open source API Gateway server of netflix. It is essentially a web servlet application. It is also one of the core components of spring cloud. Zuul framework mainly provides dynamic routing, monitoring, elasticity and security services

problem

Generally speaking, there are two types of Zuul routing
1, Static configuration is written in the configuration file. As shown in the figure, all requests under USM user will be intercepted and forwarded to user

2, Dynamic configuration, which can be controlled in real time.
One day, I suddenly feel that the static configuration is very unfriendly. If you add a new service, you need to modify the configuration file and then repackage and publish it. This is very cumbersome and unpleasant. Even if Zuul doesn't do clustering, the problem will be serious at this time. Therefore, I want to try dynamic routing. Generally speaking, it is the dynamic configuration of routing through the database.

principle

By overriding simpleroutelocator Locateroutes() method, read the configuration from the database, and then refresh the routing configuration when refresh() is called. As for the overall process, interested friends can consult the source code by themselves

Core code

Only part of the core code will be released here. For detailed source code and database sql, please see here: Source code entry

//Inject into spring
@Configuration
public class DynamicRouteConfig {


    /**
     * Use a custom routing policy instead of the default routing policy
     */
    @Bean
    public SimpleRouteLocator routeLocator(ZuulProperties zuulProperties) {
        //DynamicRouteLocator is a custom routing rule class obtained through the database
        return new DynamicRouteLocator( zuulProperties.getPrefix(), zuulProperties);
    }
}

Rewrite method to pull data through the database

/**
 * Dynamic routing implementation
 */
@Slf4j
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {

	@Autowired
	private ZuulRouterMapper zuulRouterMapper;

	private ZuulProperties properties;

	public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
		super(servletPath, properties);
		this.properties = properties;
	}

	/**
	 * Refresh route
	 *
	 */
	@Override
	public void refresh() {
		super.doRefresh();
	}

	/**
	 * Overload routing rules, and automatically execute this method when the RoutesRefreshedEvent event is inserted
	 */
	@Override
	protected Map<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
		//Load routing information from configuration file
		routesMap.putAll(super.locateRoutes());
		//Custom load routing information
		routesMap.putAll(getRouteList());
		//Optimize the configuration
		LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
		for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
			String path = entry.getKey();
			// Prepend with slash if not already present.
			if (!path.startsWith("/")) {
				path = "/" + path;
			}
			if (StringUtils.hasText(this.properties.getPrefix())) {
				path = this.properties.getPrefix() + path;
				if (!path.startsWith("/")) {
					path = "/" + path;
				}
			}
			values.put(path, entry.getValue());
		}
		return values;
	}

	private LinkedHashMap<String, ZuulRoute> getRouteList() {
		LinkedHashMap<String, ZuulRoute> zuulRoutes = new LinkedHashMap<>();
		List<ZuulRouteEntity> sysZuulRoutes = null;
		//Read redis. Redis does not read the database again
		List<ZuulRouteEntity> routesString = (List<ZuulRouteEntity>)RedisUtils.get("zuul:router:list");
		if(routesString!=null){
			sysZuulRoutes = routesString;
		}else {
			//Get database data
			sysZuulRoutes = zuulRouterMapper.selectAll();
		}
		for (ZuulRouteEntity route: sysZuulRoutes) {
			// Empty skip
			if (Strings.isNullOrEmpty(route.getPath()) && Strings.isNullOrEmpty(route.getUrl())) {
				continue;
			}
			ZuulRoute zuulRoute = new ZuulRoute();
			try {
				zuulRoute.setId(route.getServiceId());
				zuulRoute.setPath(route.getPath());
				zuulRoute.setServiceId(route.getServiceId());
				zuulRoute.setRetryable(Boolean.valueOf(route.getRetryable()));
				zuulRoute.setStripPrefix(Boolean.valueOf( route.getStripPrefix()) );
				zuulRoute.setUrl(route.getUrl());
				if(!StringUtils.isEmpty(route.getSensitiveheadersList())) {
					List<String> sensitiveHeadersList = Arrays.asList(route.getSensitiveheadersList().split(","));
					if (sensitiveHeadersList != null) {
						Set<String> sensitiveHeaderSet = Sets.newHashSet();
						sensitiveHeadersList.forEach(sensitiveHeader -> sensitiveHeaderSet.add(sensitiveHeader));
						zuulRoute.setSensitiveHeaders(sensitiveHeaderSet);
						zuulRoute.setCustomSensitiveHeaders(true);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				log.error(e.getMessage(), e);
			}
			log.info("Custom routing configuration,path: {},serviceId:{}", zuulRoute.getPath(), zuulRoute.getServiceId());
			zuulRoutes.put(zuulRoute.getPath(), zuulRoute);
		}
		RedisUtils.set("zuul:router:list",sysZuulRoutes);
		return zuulRoutes;
	}
}

After modifying the database routing configuration, you need to trigger it manually and call refreshRoute()

@Component
public class ZuulRouterHandle {

    @Autowired
    private DynamicRouteLocator dynamicRouteLocator;

    @Autowired
    private ApplicationEventPublisher publisher;

    /**
     * The dynamic routing implementation calls refreshRoute() to publish the refresh route event
     */
    public void refreshRoute() {
        RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(dynamicRouteLocator);
        publisher.publishEvent(routesRefreshedEvent);
    }
}

Final effect

After the test service is started, access it through the postman port

Then look at the routing forwarding effect

The forwarding effect is the same after adding data to the database. Refresh the route first, and then call the interface


Zuul+Mysql realizes dynamic routing. Basically, there are so many places that need to be adjusted according to their actual situation. There are several places that need attention
1, Eureka client. The default time of registry fetch interval seconds is 30s, which causes refresh() to be called once in 30s
2, There is a small bug temporarily. refresh() will be called twice. The author handles it by caching redis. It will be updated after being solved later, or if friends know, please comment and leave a message
3, When using the source code, some configurations need to be modified
Ps: the above is only obtained from personal study. Please indicate the source for reprint. Friends with questions are welcome to comment and leave messages

Keywords: Java Spring

Added by russ8 on Fri, 11 Feb 2022 18:39:07 +0200