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