Spring Cloud Zuul----详解一

过滤器

在这里插入图片描述

pre过滤器

过滤器 描述 解释
ServletDetectionFilter 它的执行顺序为-3,是最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行,还是通过ZuulServlet来处理运行的。它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判断它以实现做不同的处理。 如果不是以/zuul/**开头,则先走DispatcherServlet,获取具体的Controller,在Controller调用ZuulServlet.service()处理;当请求中以/zuul/**开头,请求直接走ZuulServlet,不走DispatcherServlet。主要用来应对处理大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数来进行修改。
Servlet30WrapperFilter 它的执行顺序为-2,是第二个执行的过滤器。目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。 当请求是/zuul时,不会被包装成Servlet30RequestWrapper对象,一遍FormBodyWrapperFilter使用
FormBodyWrapperFilter 它的执行顺序为-1,是第三个执行的过滤器。 该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
DebugFilter 它的执行顺序为1,是第四个执行的过滤器。 该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。而它的具体操作内容则是将当前的请求上下文中的debugRouting和debugRequest参数设置为true。由于在同一个请求的不同生命周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题,另外,对于请求参数中的debug参数,我们也可以通过zuul.debug.parameter来进行自定义。
PreDecorationFilter 它的执行顺序为5,是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-Proto。我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作 1、请求信息;2、敏感头;3、判断是否是用注册中心还是http;4、代理头

route过滤器

过滤器 描述 解释
RibbonRoutingFilter 它的执行顺序为10,是route阶段第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
SimpleHostRoutingFilter 它的执行顺序为100,是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。
SendForwardFilter 它的执行顺序为500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置。

类结构

项目启动时会加载RibbonCommandFactoryConfiguration和SpringClientFactory类到Spring容器中,在调用接口时,会在RibbonRoutingFilter中利用SpringClientFactory从Spring中加载RibbonClientConfiguration获取RibbonLoadBalancingHttpClient和ILoadBalancer
在这里插入图片描述
RibbonClientConfiguration,如果项目中配置ribbon.eureka.enabled=false,则会走RibbonClientConfiguration的IPing和ServerList,不会走Eureka注册中心,如果没有配置或配置为true则会走EurekaRibbonClientConfiguration,在加载Eureka注册中心
在这里插入图片描述
CommonClientConfigKey:核心配置类

1、通过ZuulProxyAutoConfiguration加载pre过滤器route过滤器(RibbonRoutingFilter)
2、通过ZuulServerAutoConfiguration加载ZuulHandlerMappingZuulServlet

如果请求不是以/zuul/**开头,则先通过DispatcherServlet,通过ZuulHandlerMapping获取具体的Controller,在Controller调用ZuulServlet.service()处理;当请求中以/zuul/**开头,请求直接走ZuulServlet,不走DispatcherServlet。

3、在请求到PreDecorationFilter过滤器时,通过RouteLocator加载Zuul配置信息,通过RouteLocator获取配置文件中的Route配置信息,

  1. 判断url是否以http开头,是则设置;
  2. 判断url是否以判断url是否以forward:开头,设置为转发
  3. 剩下的为服务注册
@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper
				.getPathWithinApplication(ctx.getRequest());
		// 加载配置信息
		Route route = this.routeLocator.getMatchingRoute(requestURI);
		if (route != null) {
			// 获取url
			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());
				}

				// url是否以http开头
				if (location.startsWith(HTTP_SCHEME + ":")
						|| location.startsWith(HTTPS_SCHEME + ":")) {
					ctx.setRouteHost(getUrl(location));
					ctx.addOriginResponseHeader(SERVICE_HEADER, location);
				}
				// 判断url是否以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;
				}
				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 forwardURI = getForwardUri(requestURI);

			ctx.set(FORWARD_TO_KEY, forwardURI);
		}
		return null;
	}

4、当请求发送过来时,在RibbonRoutingFilter过滤器中,通过HttpClientRibbonCommandFactory工厂类,通过SpringClientFactory创建RibbonLoadBalancingHttpClient、IClientConfig 及ILoadBalancer,SpringClientFactory会为每个服务创建一个Spring上下文

注:RibbonLoadBalancingHttpClient、IClientConfig 及ILoadBalancer是通过RibbonClientConfiguration加载;IClientConfig 主要保存Ribbon配置信息,在创建RibbonLoadBalancingHttpClient会一次加载这个配置信息;IClientConfig 配置如下图所示

在这里插入图片描述

public class RibbonRoutingFilter extends ZuulFilter {
	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		this.helper.addIgnoredHeaders();
		try {
			RibbonCommandContext commandContext = buildCommandContext(context);
			// 通过HttpClientRibbonCommandFactory工厂类,通过SpringClientFactory创建RibbonLoadBalancingHttpClient 及ILoadBalancer
			ClientHttpResponse response = forward(commandContext);
			setResponse(response);
			return response;
		}
		catch (ZuulException ex) {
			throw new ZuulRuntimeException(ex);
		}
		catch (Exception ex) {
			throw new ZuulRuntimeException(ex);
		}
	}
	protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
		Map<String, Object> info = this.helper.debug(context.getMethod(),
				context.getUri(), context.getHeaders(), context.getParams(),
				context.getRequestEntity());

	// 通过HttpClientRibbonCommandFactory工厂类,通过SpringClientFactory创建RibbonLoadBalancingHttpClient 及ILoadBalancer
		RibbonCommand command = this.ribbonCommandFactory.create(context);
		try {
			ClientHttpResponse response = command.execute();
			this.helper.appendDebug(info, response.getRawStatusCode(),
					response.getHeaders());
			return response;
		}
		catch (HystrixRuntimeException ex) {
			return handleException(info, ex);
		}

	}
}
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
	
	private final SpringClientFactory clientFactory;

	private final ZuulProperties zuulProperties;

	public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory,
			ZuulProperties zuulProperties, Set<FallbackProvider> fallbackProviders) {
		super(fallbackProviders);
		this.clientFactory = clientFactory;
		this.zuulProperties = zuulProperties;
	}
	@Override
	public HttpClientRibbonCommand create(final RibbonCommandContext context) {
		FallbackProvider zuulFallbackProvider = getFallbackProvider(
				context.getServiceId());
		final String serviceId = context.getServiceId();
		// 通过SpringClientFactory创建RibbonLoadBalancingHttpClient 及ILoadBalancer
		final RibbonLoadBalancingHttpClient client = this.clientFactory
				.getClient(serviceId, RibbonLoadBalancingHttpClient.class);
		client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));

		return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties,
				zuulFallbackProvider, clientFactory.getClientConfig(serviceId));
	}
}

4、通过SpringClientFactory创建RibbonLoadBalancingHttpClient 及ILoadBalancer,SpringClientFactory会为每个服务创建一个Spring上下文。

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
	public SpringClientFactory() {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}
	// 加载ILoadBalancer
	public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
	}
	@Override
	public <C> C getInstance(String name, Class<C> type) {
		// 调用子类加载ILoadBalancer
		C instance = super.getInstance(name, type);
		if (instance != null) {
			return instance;
		}
		IClientConfig config = getInstance(name, IClientConfig.class);
		return instantiateWithConfig(getContext(name), type, config);
	}
}
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
		
		// 加载ILoadBalancer
		public <T> T getInstance(String name, Class<T> type) {
			AnnotationConfigApplicationContext context = getContext(name);
			if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
					type).length > 0) {
				return context.getBean(type);
			}
			return null;
		}
		protected AnnotationConfigApplicationContext getContext(String name) {
		 if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					// 创建上下文
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}
	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
		// 其中configurations的值为default.org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,具体在RibbonAutoConfiguration类中,加载SpringClientFactory的时候
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		//defaultConfigType为RibbonClientConfiguration,具体在SpringClientFactory 构造函数中加载
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}
}

用法

注册中心配置

zuul:
  routes:
    tmp-test1:
      path: /redis/**
      url: tmp  ## 和serviceId效果一样(PreDecorationFilter)
      stripPrefix: false   ## 默认为true,如果为false,表示ip:port/redis/hello将请求到ip:port/redis/hello;如果为true,表示ip:port/redis/hello将请求到ip:port/hello(PreDecorationFilter)
  ignored-services: '*'   ## zuul有默认的映射机制,‘*’表示忽略所有的映射机制
  add-proxy-headers: false  ## 是否在头中增加代理信息(PreDecorationFilter)
  sensitive-headers:  ## 请求发送到网关后,需要移除的头信息
ribbon:  
  ReadTimeout: 20000 ## 针对所有服务,与下边tmp.ribbon.ReadTimeout作用一样
  ConnectTimeout: 30000
#tmp:  ### 服务级,优先级比ribbon.ReadTimeout高
  #ribbon:
      #ReadTimeout: 20000
      #ConnectTimeout: 30000

http请求配置(单实例)

走SimpleHostRoutingFilter过滤器

zuul:
  routes:
    tmp-test1:
      path: /redis/**
      url: http://localhost:8088/ ## 和serviceId效果一样(PreDecorationFilter)
      stripPrefix: false   ## 默认为true,如果为false,表示ip:port/redis/hello将请求到ip:port/redis/hello;如果为true,表示ip:port/redis/hello将请求到ip:port/hello(PreDecorationFilter)
    test:
      path: /test/**
      url: forward:/hello ## 转发,需要再网关层定义controller处理,例如请求/test/test,则最终会跳转到/hello/test中,具体请看PreDecorationFilter类中run方法
  ignored-services: '*'   ## zuul有默认的映射机制,‘*’表示忽略所有的映射机制
  add-proxy-headers: false  ## 是否在头中增加代理信息(PreDecorationFilter)
  sensitive-headers:  ## 请求发送到网关后,需要移除的头信息
  host:
    connect-timeout-millis: 2000  ## 当使用http时,创建httpClient,请求连接时间(SimpleHostRoutingFilter.initialize())
    socket-timeout-millis: 6000  ## 当使用http时,创建httpClient,请求连接时间(SimpleHostRoutingFilter.initialize())

http请求配置(多实例,负载均衡)

走RibbonRoutingFilter 过滤器

zuul:
  routes:
    tmp-test1:
      path: /redis/**
      url: tmp  ## 和serviceId效果一样(PreDecorationFilter)
      stripPrefix: false   ## 默认为true,如果为false,表示ip:port/redis/hello将请求到ip:port/redis/hello;如果为true,表示ip:port/redis/hello将请求到ip:port/hello(PreDecorationFilter)
  ignored-services: '*'   ## zuul有默认的映射机制,‘*’表示忽略所有的映射机制
  add-proxy-headers: false  ## 是否在头中增加代理信息(PreDecorationFilter)
  sensitive-headers:  ## 请求发送到网关后,需要移除的头信息
ribbon:
  ReadTimeout: 20000  ## 针对所有服务,与下边tmp.ribbon.ReadTimeout作用一样
  ConnectTimeout: 30000
  eureka:
    enabled: false ## 如果引入了eureka包,想使用负载均衡,zuul.routes.*.url配置的不是http,此时需要将该值设置成false
tmp:  ### 服务级,优先级比ribbon.ReadTimeout高
  ribbon:
    #ReadTimeout: 20000
    #ConnectTimeout: 30000
    listOfServers: http://localhost:8088/,http://localhost:8081  ## ribbon负载均衡加载的url

超时重试

1、 在pom.xml中增加依赖项

    <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
       </dependency>

2、配置文件

zuul:
  ignored-services: '*'
  routes:
    task:
      path: /task/**
      serviceId: tmp
      customSensitiveHeaders: true
  retryable: true  ## *****开启超时重试开关(必须增加否则不起作用)*****
ribbon:
  ReadTimeout: 10000   ## 请求处理的超时时间
  ConnectTimeout: 5000  ## 请求连接的超时时间
  MaxAutoRetries: 1  ## 对当前实例的重试次数(此处为1+1,重试2次,默认0)
  MaxAutoRetriesNextServer: 2 ## 切换实例的重试次数(2*2及(MaxAutoRetries+1)*MaxAutoRetriesNextServer,默认1)
  retryableStatusCodes: 500 ## *****处理错误状态码*****
  OkToRetryOnAllOperations: false ## *****对所有操作请求都进行重试(默认GET请求)*****
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 151000
tmp:
  ribbon:  ### 服务级,优先级比ribbon.ReadTimeout高
    #ReadTimeout: 20000
    #ConnectTimeout: 30000
    listOfServers: http://localhost:8080/,http://localhost:8081/

熔断机制(Hystrix)

@Component
public class UserServiceFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return "tmp";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

退避策略(Ribbon)

参考资料

@Configuration
public class MyConfiguration {
    @Bean
    LoadBalancedRetryFactory retryFactory() {
        return new LoadBalancedRetryFactory() {
            @Override
            public BackOffPolicy createBackOffPolicy(String service) {
                return new ExponentialBackOffPolicy();
            }
        };
    }
}

Filter工作原理

Zuul中的Filter

Zuul是围绕一系列Filter展开的,这些Filter在整个HTTP请求过程中执行一连串的操作。
Zuul Filter有以下几个特征:

  • Type:用以表示路由过程中的阶段(内置包含PRE、ROUTING、POST和ERROR)
  • Execution Order:表示相同Type的Filter的执行顺序
  • Criteria:执行条件
  • Action:执行体

Filter Types

以下提供四种标准的Filter类型及其在请求生命周期中所处的位置:

  • PRE Filter:在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录。
  • ROUTING Filter:处理目标请求。这里使用Apache HttpClient或Netflix Ribbon构造对目标的HTTP请求。
  • POST Filter:在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
  • ERROR Filter:整个流程某块出错时执行。

自定义一个Filter

@Component
public class PreLogFilter extends ZuulFilter{
 
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return 0;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    @Override
    public Object run() {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        System.out.print(String.format("send %s request to %s",request.getMethod(),request.getRequestURL()));
        return null;
    }

Filter的启用与禁用

我们自己写的Filter可以通过修改shouldFilter()启用或禁用。如果第三方的Filter怎样控制其启用及禁用呢?

很简单,通过配置文件就可以做到:

zuul:
  SendErrorFilter: #自定义Filter类名
    error: #Type
      disable: true
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章