Zuul路由转发源码解析及动态路由实现

本文介绍Zuul路由的源码以及实现动态路由的几种方式,可以先阅读Zuul1.x核心源码解析了解路由源码流程

本文基于spring-cloud-starter-zuul(SpringCloud版本为Edgware.SR3)

1、源码解析

1)、路由转发

route类型的SimpleHostRoutingFilterRibbonRoutingFilter负责实际的请求,核心代码如下:

public class SimpleHostRoutingFilter extends ZuulFilter {
    
    

	@Override
	public boolean shouldFilter() {
    
    
		return RequestContext.getCurrentContext().getRouteHost() != null
				&& RequestContext.getCurrentContext().sendZuulResponse();
	}
  
	@Override
	public Object run() {
    
    
		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();
		MultiValueMap<String, String> headers = this.helper
				.buildZuulRequestHeaders(request);
		MultiValueMap<String, String> params = this.helper
				.buildZuulRequestQueryParams(request);
		String verb = getVerb(request);
		InputStream requestEntity = getRequestBody(request);
		if (request.getContentLength() < 0) {
    
    
			context.setChunkedRequestBody();
		}

		String uri = this.helper.buildZuulRequestURI(request);
		this.helper.addIgnoredHeaders();

		try {
    
    
			CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
					headers, params, requestEntity);
			setResponse(response);
		}
		catch (Exception ex) {
    
    
			throw new ZuulRuntimeException(ex);
		}
		return null;
	}  
public class RibbonRoutingFilter extends ZuulFilter {
    
    
  
	@Override
	public boolean shouldFilter() {
    
    
		RequestContext ctx = RequestContext.getCurrentContext();
		return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
				&& ctx.sendZuulResponse());
	}  
  
	@Override
	public Object run() {
    
    
		RequestContext context = RequestContext.getCurrentContext();
		this.helper.addIgnoredHeaders();
		try {
    
    
			RibbonCommandContext commandContext = buildCommandContext(context);
			ClientHttpResponse response = forward(commandContext);
			setResponse(response);
			return response;
		}
		catch (ZuulException ex) {
    
    
			throw new ZuulRuntimeException(ex);
		}
		catch (Exception ex) {
    
    
			throw new ZuulRuntimeException(ex);
		}
	}  

两个Filter都需要满足sendZuulResponse(),即需要将Response反馈给客户端

不同点在于SimpleHostRoutingFilter需要RouteHost不为空,通过HttpClient来转发请求;RibbonRoutingFilter需要serviceId不为空而且RouteHost为空,通过Ribbon、Hystrix完成客户端负载均衡

配置示例如下

#单实例配置 SimpleHostRoutingFilter
zuul.routes.service1.path=/service1/**
zuul.routes.service1.url=http://localhost:8081/

对符合/service1/**规则的请求路由转发到http://localhost:8081地址的路由规则,该路由规则由SimpleHostRoutingFilter实现路由转发

#多实例配置 RibbonRoutingFilter
zuul.routes.service2.path=/service2/**
zuul.routes.service2.service-id=service2
service2.ribbon.listOfServers=http://localhost:8082/,http://localhost:8083/

对符合/service2/**规则的请求路由转发到http://localhost:8082和http://localhost:8083两个实例地址的路由规则,该路由规则由RibbonRoutingFilter实现路由转发

2)、预处理

pre类型ZuulFilter中,PreDecorationFilter会根据路由信息进行预处理,其处理结果决定了使用哪个route类型ZuulFilter来实际处理请求

public class PreDecorationFilter extends ZuulFilter {
    
    
  
	@Override
	public Object run() {
    
    
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    	//根据requestURI获取路由信息
		Route route = this.routeLocator.getMatchingRoute(requestURI);
    	//路由信息存在
		if (route != null) {
    
    
			String location = route.getLocation();
			if (location != null) {
    
    
				ctx.put(REQUEST_URI_KEY, route.getPath());
				ctx.put(PROXY_KEY, route.getId());
				if (!route.isCustomSensitiveHeaders()) {
    
    
					this.proxyRequestHelper
							.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
				}
				else {
    
    
					this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
				}

				if (route.getRetryable() != null) {
    
    
					ctx.put(RETRYABLE_KEY, route.getRetryable());
				}

        		//以https或http开头,设置RouteHost
				if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
    
    
					ctx.setRouteHost(getUrl(location));
					ctx.addOriginResponseHeader(SERVICE_HEADER, location);
				}
        		//以forward:开头
				else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
    
    
					ctx.set(FORWARD_TO_KEY,
							StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
					ctx.setRouteHost(null);
					return null;
				}
        		//设置serviceId,RouteHost置空
				else {
    
    
					// set serviceId for use in filters.route.RibbonRequest
					ctx.set(SERVICE_ID_KEY, location);
					ctx.setRouteHost(null);
					ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
				}
				if (this.properties.isAddProxyHeaders()) {
    
    
					addProxyHeaders(ctx, route);
					String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
					String remoteAddr = ctx.getRequest().getRemoteAddr();
					if (xforwardedfor == null) {
    
    
						xforwardedfor = remoteAddr;
					}
					else if (!xforwardedfor.contains(remoteAddr)) {
    
     // Prevent duplicates
						xforwardedfor += ", " + remoteAddr;
					}
					ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
				}
				if (this.properties.isAddHostHeader()) {
    
    
					ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
				}
			}
		}
    	//路由信息不存在
		else {
    
    
			log.warn("No route found for uri: " + requestURI);

			String fallBackUri = requestURI;
			String fallbackPrefix = this.dispatcherServletPath; // default fallback
																// servlet is
																// DispatcherServlet

			if (RequestUtils.isZuulServletRequest()) {
    
    
				// remove the Zuul servletPath from the requestUri
				log.debug("zuulServletPath=" + this.properties.getServletPath());
				fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
				log.debug("Replaced Zuul servlet path:" + fallBackUri);
			}
			else {
    
    
				// remove the DispatcherServlet servletPath from the requestUri
				log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
				fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
				log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
			}
			if (!fallBackUri.startsWith("/")) {
    
    
				fallBackUri = "/" + fallBackUri;
			}
			String forwardURI = fallbackPrefix + fallBackUri;
			forwardURI = forwardURI.replaceAll("//", "/");
			ctx.set(FORWARD_TO_KEY, forwardURI);
		}
		return null;
	}  

routeLocator.getMatchingRoute()根据请求URL获取Route,再根据Route的location是否匹配http:、https:、forward:前缀来设置属性

使用上面的/service1/**/service2/**规则,访问http://localhost:8080/service1/helloworldhttp://localhost:8080/service2/helloworld获取的Route信息如下:

Route{id='service1', fullPath='/service1/helloworld', path='/helloworld', location='http://localhost:8081/', prefix='/service1', retryable=false, sensitiveHeaders=[], customSensitiveHeaders=false, prefixStripped=true}
Route{id='service2', fullPath='/service2/helloworld', path='/helloworld', location='service2', prefix='/service2', retryable=false, sensitiveHeaders=[], customSensitiveHeaders=false, prefixStripped=true}

3)、路由定位

PreDecorationFilter中通过RouteLocator根据URL获取Route

public interface RouteLocator {
    
    

	Collection<String> getIgnoredPaths();

	List<Route> getRoutes();

	Route getMatchingRoute(String path);

}

RouteLocator主要功能如下:

  • 根据path获取Route
  • 获取所有Route

RouteLocator继承关系图如下:

在这里插入图片描述

1)SimpleRouteLocator

简单路由定位器,路由信息来自ZuulProperties,locateRoutes()是定位路由的核心,从ZuulProperties中加载了路由数据

public class SimpleRouteLocator implements RouteLocator, Ordered {
    
    
  
  	//routes存储路由信息
	private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
  
  	//获取路由信息
	protected Map<String, ZuulRoute> locateRoutes() {
    
    
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
    	//提取ZuulProperties中的ZuulRoute
		for (ZuulRoute route : this.properties.getRoutes().values()) {
    
    
			routesMap.put(route.getPath(), route);
		}
		return routesMap;
	}
  
  	//刷新路由缓存
	protected void doRefresh() {
    
    
		this.routes.set(locateRoutes());
	}  
2)DiscoveryClientRouteLocator

DiscoveryClientRouteLocator基于DiscoveryClient,路由数据来自properties中的静态配置和DiscoveryClient从注册中心获取的数据

DiscoveryClientRouteLocator主要功能如下:

  • 动态添加Route
  • 刷新路由
  • 从DiscoveryClient获取路由信息
public interface RefreshableRouteLocator extends RouteLocator {
    
    

	void refresh();

}
public class DiscoveryClientRouteLocator extends SimpleRouteLocator
		implements RefreshableRouteLocator {
    
    
  
  	//动态添加路由,会同步把路由信息添加到ZuulProperties,参数也可以是ZuulRoute
	public void addRoute(String path, String location) {
    
    
		this.properties.getRoutes().put(path, new ZuulRoute(path, location));
		refresh();
	}  
  
  @Override
	protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
    
    
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
    	//通过父类SimpleRouteLocator获取静态路由信息
		routesMap.putAll(super.locateRoutes());
    	//通过DiscoveryClient获取路由信息
		if (this.discovery != null) {
    
    
			Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
			for (ZuulRoute route : routesMap.values()) {
    
    
				String serviceId = route.getServiceId();
				if (serviceId == null) {
    
    
					serviceId = route.getId();
				}
				if (serviceId != null) {
    
    
					staticServices.put(serviceId, route);
				}
			}
			// Add routes for discovery services by default
			List<String> services = this.discovery.getServices();
			String[] ignored = this.properties.getIgnoredServices()
					.toArray(new String[0]);
			for (String serviceId : services) {
    
    
				// Ignore specifically ignored services and those that were manually
				// configured
				String key = "/" + mapRouteToService(serviceId) + "/**";
				if (staticServices.containsKey(serviceId)
						&& staticServices.get(serviceId).getUrl() == null) {
    
    
					// Explicitly configured with no URL, cannot be ignored
					// all static routes are already in routesMap
					// Update location using serviceId if location is null
					ZuulRoute staticRoute = staticServices.get(serviceId);
					if (!StringUtils.hasText(staticRoute.getLocation())) {
    
    
						staticRoute.setLocation(serviceId);
					}
				}
				if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
						&& !routesMap.containsKey(key)) {
    
    
					// Not ignored
					routesMap.put(key, new ZuulRoute(key, serviceId));
				}
			}
		}
		if (routesMap.get(DEFAULT_ROUTE) != null) {
    
    
			ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
			// Move the defaultServiceId to the end
			routesMap.remove(DEFAULT_ROUTE);
			routesMap.put(DEFAULT_ROUTE, defaultRoute);
		}
		LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
		for (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;
	}
  
	@Override
	public void refresh() {
    
    
    	//调用SimpleRouteLocator的doRefresh()方法
		doRefresh();
	}  
3)CompositeRouteLocator

CompositeRouteLocator用于组合多个RouteLocator,用Collection存储多个RouteLocator,调用getRoutes()getMatchingRoute()refresh()时都会逐一调用每个RouteLocator相应的方法

public class CompositeRouteLocator implements RefreshableRouteLocator {
    
    

  private final Collection<? extends RouteLocator> routeLocators;
	private ArrayList<RouteLocator> rl;
  
	@Override
	public List<Route> getRoutes() {
    
    
		List<Route> route = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
    
    
			route.addAll(locator.getRoutes());
		}
		return route;
	}

	@Override
	public Route getMatchingRoute(String path) {
    
    
		for (RouteLocator locator : routeLocators) {
    
    
			Route route = locator.getMatchingRoute(path);
			if (route != null) {
    
    
				return route;
			}
		}
		return null;
	}

	@Override
	public void refresh() {
    
    
		for (RouteLocator locator : routeLocators) {
    
    
			if (locator instanceof RefreshableRouteLocator) {
    
    
				((RefreshableRouteLocator) locator).refresh();
			}
		}
	}  

2、动态路由实现

1)、刷新路由的方式

Zuul提供了ZuulRefreshListener,监听到RoutesRefreshedEvent事件后,会调用ZuulHandlerMapping的setDirty()方法,进而调用RouteLocator的refresh()方法

	private static class ZuulRefreshListener
			implements ApplicationListener<ApplicationEvent> {
    
    

		@Autowired
		private ZuulHandlerMapping zuulHandlerMapping;

		private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

		@Override
		public void onApplicationEvent(ApplicationEvent event) {
    
    
			if (event instanceof ContextRefreshedEvent
					|| event instanceof RefreshScopeRefreshedEvent
					|| event instanceof RoutesRefreshedEvent) {
    
    
        		//设置为脏,下一次匹配到路径时,如果发现为脏,则会去刷新路由信息
				this.zuulHandlerMapping.setDirty(true);
			}
			else if (event instanceof HeartbeatEvent) {
    
    
				if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
    
    
					this.zuulHandlerMapping.setDirty(true);
				}
			}
		}

	}
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    
    	

	public void setDirty(boolean dirty) {
    
    
		this.dirty = dirty;
		if (this.routeLocator instanceof RefreshableRouteLocator) {
    
    
			((RefreshableRouteLocator) this.routeLocator).refresh();
		}
	}

2)、实现动态路由加载

#多实例配置
zuul.routes.service2.path=/service2/**
zuul.routes.service2.service-id=service2
service2.ribbon.listOfServers=http://localhost:8082/,http://localhost:8083/

多实例配置中的配置包含两部分:Zuul的路由配置和Ribbon service-id对应的后端路径,zuul.routes.service2.service-id的值和<service-id>.ribbon.listOfServerskey的前缀相匹配,所以动态路由的实现也包含刷新Zuul的路由信息和Ribbon service-id对应的后端路径两部分

@Data
@NoArgsConstructor
public class DynamicRouteEntity {
    
    
    private String serviceId;

    private String path;

    private String serverListStr;
}

DynamicRouteEntity为自定义的动态路由实体,和多实例配置中的key一一对应

public class CustomServerList implements ServerList<Server>, IClientConfigAware {
    
    
    private IClientConfig clientConfig;

    @Override
    public List<Server> getInitialListOfServers() {
    
    
        return getUpdatedListOfServers();
    }

    @Override
    public List<Server> getUpdatedListOfServers() {
    
    
        return ClientServerListCacheControl.getInstance()
                .getServerListCacheByClientName(clientConfig.getClientName());
    }

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
    
        this.clientConfig = iClientConfig;
    }
}

CustomServerList为自定义ServerList类,等价于重写listOfServer配置,通过getUpdatedListOfServers()来维护service-id对应的后端路径。Ribbon中的PollingServerListUpdater会定时调用getUpdatedListOfServers()方法,这就是实现动态加载Ribbon service-id对应后端路径的关键

public class ClientServerListCacheControl {
    
    
    private static final ClientServerListCacheControl CLIENT_SERVER_LIST_CACHE_CONTROL =
            new ClientServerListCacheControl();

    /**
     * key和zuulRoute中serviceId相对应 value为转发的后端地址 多个使用,分隔
     */
    private Map<String, String> clientServerListCache = new ConcurrentHashMap<>();

    private ClientServerListCacheControl() {
    
    

    }

    public static ClientServerListCacheControl getInstance() {
    
    
        return CLIENT_SERVER_LIST_CACHE_CONTROL;
    }

    public List<Server> getServerListCacheByClientName(String clientName) {
    
    
        List<Server> serverList = Lists.newArrayList();
        String serverListStr = clientServerListCache.get(clientName);
        if (!Strings.isNullOrEmpty(serverListStr)) {
    
    
            for (String s : serverListStr.split(",")) {
    
    
                serverList.add(new Server(s.trim()));
            }
        }
        return serverList;
    }

    public void setClientServerListCache(String clientName, String serverList) {
    
    
        clientServerListCache.put(clientName, serverList);
    }
}

ClientServerListCacheControl主要是用来存储service-id对应的后端路径

public class CustomRibbonClientConfiguration extends RibbonClientConfiguration {
    
    

    @Override
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
    
    
        CustomServerList serverList = new CustomServerList();
        serverList.initWithNiwsConfig(config);
        return serverList;
    }
}
@Configuration
@RibbonClients(defaultConfiguration = {
    
    CustomRibbonClientConfiguration.class})
public class CustomRibbonAutoConfiguration {
    
    
}

CustomRibbonClientConfiguration和CustomRibbonAutoConfiguration都是配置类

#ribbon服务列表刷新频率1s 默认值是30s
ribbon.ServerListRefreshInterval=1000

配置文件中修改Ribbon服务列表刷新频率,也就是Ribbon的PollingServerListUpdater定时任务的频率

@Component
public class DynamicRoutesProcessor implements InitializingBean {
    
    

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private CompositeRouteLocator compositeRouteLocator;

    @Autowired
    private ZuulProperties zuulProperties;

    /**
     * 初始化路由信息
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        Map<String, ZuulProperties.ZuulRoute> routes = zuulProperties.getRoutes();
        ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
        zuulRoute.setId("service3");
        zuulRoute.setPath("/service3/**");
        //location以http:和https:开头,设置的是ZuulRoute中的url属性;否则设置serviceId属性
        //设置serviceId
        zuulRoute.setLocation("service3");
        routes.put("service3", zuulRoute);
        //刷新路由
        applicationContext.publishEvent(new RoutesRefreshedEvent(compositeRouteLocator));

        //设置ClientServerListCache
        //PollingServerListUpdater定时任务更新时,会调用CustomServerList的getUpdatedListOfServers()方法,从ClientServerListCache中拿到最新的后端地址
        ClientServerListCacheControl.getInstance().setClientServerListCache(
                "service3", "http://localhost:8081/,http://localhost:8082/");
    }

    /**
     * 动态刷新
     *
     * @param dynamicRouteEntityList 路由信息和转发的后端地址
     */
    public void refreshRoutes(List<DynamicRouteEntity> dynamicRouteEntityList) {
    
    
        Map<String, ZuulProperties.ZuulRoute> routes = zuulProperties.getRoutes();
        //提取routeList数据并添加到routes中
        for (DynamicRouteEntity dynamicRouteEntity : dynamicRouteEntityList) {
    
    
            if (StringUtils.isEmpty(dynamicRouteEntity.getServiceId())
                    || StringUtils.isEmpty(dynamicRouteEntity.getPath())
                    || StringUtils.isEmpty(dynamicRouteEntity.getServerListStr())) {
    
    
                continue;
            }
            //Zuul中的路由信息
            ZuulProperties.ZuulRoute route = new ZuulProperties.ZuulRoute();
            route.setId(dynamicRouteEntity.getServiceId());
            route.setPath(dynamicRouteEntity.getPath());
            route.setLocation(dynamicRouteEntity.getServiceId());
            routes.put(route.getId(), route);

            //设置ClientServerListCache 从而更新Ribbon service-id对应的后端路径
            ClientServerListCacheControl.getInstance().setClientServerListCache(
                    route.getServiceId(), dynamicRouteEntity.getServerListStr());
        }
        //刷新路由
        applicationContext.publishEvent(new RoutesRefreshedEvent(compositeRouteLocator));
    }
}

DynamicRoutesProcessor是实现Zuul路由动态刷新的关键,分为两部分:初始化路由的afterPropertiesSet()方法和动态刷新路由的refreshRoutes(List<DynamicRouteEntity> dynamicRouteEntityList)方法

刷新路由时,通过触发RoutesRefreshedEvent事件刷新Zuul的路由,同时更新ClientServerListCache中存储的service-id对应的后端对应路径的关系,Ribbon中的PollingServerListUpdater定时1s调用CustomServerList的getUpdatedListOfServers()方法,从而从ClientServerListCache中实现动态刷新Ribbon service-id对应的后端路径

参考

https://chenyongjun.vip/articles/113

https://github.com/wpstan/CustomZuulRibbon

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/113482425