Zuul是Netflix开源的微服务网关,可以和Eureka、Ribbon、Hystrix等组件配合使用,Spring Cloud对Zuul进行了整合与增强, Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/service-a/login转发到到service-a服务。zuul默认和Ribbon结合实现了负载均衡的功能。
zuul的核心是一系列的filters。下面开始简单的源码分析。
我们知道一般在使用zuul的时候,需要在application启用应用上添加@EnableZuulProxy注解。
@SpringBootApplication
@EnableZuulProxy
public class EurekaZuulApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaZuulApplication.class, args);
}
}
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
@EnableZuulProxy归属于spring-cloud-netflix-core模块。查看它的spring.factories文件。
看一下它的自动注入类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\
org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\
org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.netflix.feign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\
org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\
org.springframework.cloud.netflix.rx.RxJavaAutoConfiguration,\
org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
其中ZuulProxyAutoConfiguration和ZuulServerAutoConfiguration会被自动注入。先看一下ZuulProxyAutoConfiguration类。
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
..................
}
可以看到ZuulProxyAutoConfiguration加载的条件是需要通过@EnableZuulProxy注入ZuulProxyMarkerConfiguration.Marker的Bean类。而且它还继承了ZuulServerAutoConfiguration类。先看下ZuulProxyAutoConfiguration装载的比较重要的几个Bean。
@Bean
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
public DiscoveryClientRouteLocator discoveryRouteLocator() {
return new DiscoveryClientRouteLocator(this.server.getServletPrefix(),
this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);
}
// pre filters
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
this.zuulProperties, proxyRequestHelper);
}
// route filters
@Bean
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
DiscoveryClientRouteLocator类主要作用是发现路由配置等,并组装成Map<String, ZuulRoute>形式。
PreDecorationFilter类主要是执行preFilter。filter类型是pre。
RibbonRoutingFilter类执行routeFilter中。filter类型是route。
filter类型有pre、route、post等,后面会具体讲。
下面看ZuulServerAutoConfiguration装载了哪些Bean。
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
@Autowired
protected ZuulProperties zuulProperties;
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
}
ZuulProperties加载application.yml中对应的配置文件。
zuul:
prefix: /api-a
routes:
routes1:
path: /**/**
service-id: ribbontest
strip-prefix: true
CompositeRouteLocator主要是混合路由配置器。主要处理实现了RouteLocator接口的Bean,包含SimpleRouteLocator和DiscoveryClientRouteLocator。
ZuulController是负责请求过来处理请求的Controller。
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
}
ZuulHandlerMapping主要是处理DispatcherServlet请求访问过来的时候,需要AbstractHandlerMapping处理getHandlerInternal方法寻找handler时用的。也就是zuulproperties配置的路径走ZuulController。
AbstractUrlHandlerMapping类getHandlerInternal方法。
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
..................
}
ZuulHandlerMapping的lookupHandler方法。
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
return super.lookupHandler(urlPath, request);
}
继续进入registerHandlers方法。
private void registerHandlers() {
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
registerHandler(route.getFullPath(), this.zuul);
}
}
}
可以看到this.zuul就是zuulController。
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
private final RouteLocator routeLocator;
private final ZuulController zuul;
............
}
我们继续看Collection<Route> routes = this.routeLocator.getRoutes();方法。该方法会进入SimpleRouteLocator类中。
@Override
public List<Route> getRoutes() {
List<Route> values = new ArrayList<>();
//继续进入getRoutesMap方法
for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
ZuulRoute route = entry.getValue();
String path = route.getPath();
values.add(getRoute(route, path));
}
return values;
}
protected Map<String, ZuulRoute> getRoutesMap() {
if (this.routes.get() == null) {
//进入locateRoutes方法
this.routes.set(locateRoutes());
}
return this.routes.get();
}
protected Map<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
for (ZuulRoute route : this.properties.getRoutes().values()) {
routesMap.put(route.getPath(), route);
}
return routesMap;
}
可以看到是从ZuulProperties的Bean中获取的,也就是我们application.yml中配置的信息。这里可以看到map中是以配置文件自定义的路由名称为key,内容为value。
加载和route映射基本完毕了。
正常spring的请求都是由DispatcherServlet处理,这里也不意外。只不过经过DispatcherServlet的getHandler方法,找到了ZuulControllor。ZuulControllor在处理请求后,最终会调用ZuulServlet的service方法。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
//之前的PreDecorationFilter
this.preRoute();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
//之前装载的RibbonRoutingFilter
this.route();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
现在看看preRoute方法。
void preRoute() throws ZuulException {
this.zuulRunner.preRoute();
}
public void preRoute() throws ZuulException {
try {
this.runFilters("pre");
} catch (ZuulException var2) {
throw var2;
} catch (Throwable var3) {
throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
}
}
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean)result).booleanValue();
}
}
}
return bResult;
}
查找所有类型为pre的Filter。也就是之前的PreDecorationFilter类。然后执行run方法。
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
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());
...................
}
可以看到Route route = this.routeLocator.getMatchingRoute(requestURI);这行代码是找到对应的route。
protected Route getSimpleMatchingRoute(final String path) {
if (log.isDebugEnabled()) {
log.debug("Finding route for path: " + path);
}
// This is called for the initialization done in getRoutesMap()
getRoutesMap();
if (log.isDebugEnabled()) {
log.debug("servletPath=" + this.dispatcherServletPath);
log.debug("zuulServletPath=" + this.zuulServletPath);
log.debug("RequestUtils.isDispatcherServletRequest()="
+ RequestUtils.isDispatcherServletRequest());
log.debug("RequestUtils.isZuulServletRequest()="
+ RequestUtils.isZuulServletRequest());
}
String adjustedPath = adjustPath(path);
ZuulRoute route = getZuulRoute(adjustedPath);
return getRoute(route, adjustedPath);
会进入SimpleRouteLocator类的getSimpleMatchingRoute方法进行匹配。getZuulRoute方法通过请求的url,找到之前匹配好的route。
protected ZuulRoute getZuulRoute(String adjustedPath) {
if (!matchesIgnoredPatterns(adjustedPath)) {
for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
String pattern = entry.getKey();
log.debug("Matching pattern:" + pattern);
if (this.pathMatcher.match(pattern, adjustedPath)) {
return entry.getValue();
}
}
}
return null;
}
这样获取的route还不能直接使用,需要进一步加工,进入最后一行return getRoute(route, adjustedPath)方法。
protected Route getRoute(ZuulRoute route, String path) {
if (route == null) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("route matched=" + route);
}
String targetPath = path;
String prefix = this.properties.getPrefix();
if(prefix.endsWith("/")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
targetPath = path.substring(prefix.length());
}
if (route.isStripPrefix()) {
int index = route.getPath().indexOf("*") - 1;
if (index > 0) {
String routePrefix = route.getPath().substring(0, index);
targetPath = targetPath.replaceFirst(routePrefix, "");
prefix = prefix + routePrefix;
}
}
Boolean retryable = this.properties.getRetryable();
if (route.getRetryable() != null) {
retryable = route.getRetryable();
}
return new Route(route.getId(), targetPath, route.getLocation(), prefix,
retryable,
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null,
route.isStripPrefix());
}
该方法我们可以看到包括对prefix处理,如果开启strip-prefix ,zuul 会默认去除第一个 * 号位置前面的内容 /d*/**/**会变成 d*/**/**。还有是否需要替换strip-prefix等参数进行设置。之后返回的route就是可以直接访问的地址了。
本文只是简单分析了下加载过程和处理情况。还有很多设置没有写,各位见谅。另外使用Zuul肯定是动态加载的,毕竟在微服务配置了许多路由关系后,我们不可能频繁重启Zuul服务。所以一般结合Zuul+Config或者Apollo等。动态加载可以在ZuulApplication启动类中加个Bean就行了。如下:
@Bean
@RefreshScope
@ConfigurationProperties("zuul")
@Primary
public ZuulProperties zuulProperties(){
return new ZuulProperties();
}