01 Spring Cloud Zuul

Wu Jun 2019-12-25 15:59:03
10 微服务 > 3 API网关

Spring Cloud Zuul是提供负载均衡、反向代理、权限认证的一个API gateway。

简单路由

  1. 添加依赖
    引入 spring-cloud-starter-zuul 包
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
  1. 配置文件
spring.application.name=gateway-service-zuul
server.port=9000

#这里的配置表示,访问/yyy/** 直接重定向到http://www.xxx.com/**
zuul.routes.api-name.path=/yyy/**
zuul.routes.api-name.url=http://www.xxx.com/

api-name为路由的名字,可自定,但是一组映射关系的path和url要相同 3. 启动类
启动类添加 @EnableZuulProxy ,支持网关路由。

启动后访问http://localhost:9000/yyy/zzz会跳转到http://www.xxx.com/zzz

服务路由

通过url映射转发有局限性,服务变化配置就得变。将Zuul服务化可以实现对serviceId的映射。

  1. 添加eureka依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
  1. 配置文件
    配置修改为:
spring.application.name=gateway-service-zuul
server.port=9000

zuul.routes.api-name.path=/producer/**
zuul.routes.api-name.serviceId=eureka-producer

eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

启动后访问http://localhost:9000/producer/xx会跳转到eureka上serviceId=eureka-producer的服务/xx

默认路由规则

不用每个服务都配置,默认情况下,Zuul会代理所有注册到Eureka Server的微服务,并且Zuul的路由规则如下: http://ZUUL_HOST:ZUUL_PORT/serviceId/** 会被转发到serviceId对应的微服务。

服务过滤

在Zuul中定义过滤器只需要继承ZuulFilter抽象类,实现其定义的四个抽象函数,就可对请求进行拦截与过滤,实现对外服务的安全控制。

自定义Filter

实现自定义Filter,需要继承ZuulFilter的类,并覆盖其中的4个方法:

public class AccessFilter extends ZuulFilter  {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null) {
            //令zuul过滤该请求,不对其进行路由
            ctx.setSendZuulResponse(false);
            //设置了其返回的错误码  
            ctx.setResponseStatusCode(401);
            //对返回body内容进行编辑
            ctx.setResponseBody("");
            return null;
        }
        log.info("access token ok");
        return null;
    }

}

实现了自定义过滤器之后,还需要在主类实例化该过滤器才能生效:

	@Bean
	public AccessFilter accessFilter() {
		return new AccessFilter();
	}

filterType生命周期

核心过滤器

Spring Cloud Zuul默认地实现了一批核心过滤器,它们会在API网关服务启动的时候被自动地加载和启用。

类型 顺序 过滤器 功能
pre -3 ServletDetectionFilter 标记处理Servlet的类型
pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
pre -1 FormBodyWrapperFilter 包装请求体
route 1 DebugFilter 标记调试标志
route 5 PreDecorationFilter 处理请求上下文供后续使用
route 10 RibbonRoutingFilter serviceId请求转发
route 100 SimpleHostRoutingFilter url请求转发
route 500 SendForwardFilter forward请求转发
post 0 SendErrorFilter 处理有错误的请求响应
post 1000 SendResponseFilter 处理正常的请求响应
禁用指定的Filter

可以在application.yml中配置需要禁用的filter,格式:

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

全局异常处理

添加一个类型为”error”的filter,将错误信息写入RequestContext,这样SendErrorFilter就可以获取错误信息了。

class ErrorFilter extends ZuulFilter {
	@Override
	String filterType() {
		return FilterConstants.ERROR_TYPE;
	}
		
	@Override
	int filterOrder() {
		return 10;
	}
		
	@Override
	boolean shouldFilter() {
		return true;
	}
		
	@Override
	Object run() {
		RequestContext context = getRequestContext();
		Throwable throwable = context.getThrowable();
		ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		ctx.set("error.exception", throwable.getCause());
		return null;
	}
}

修改后处理逻辑

路由熔断

Zuul提供熔断支持。当某个服务出现异常时,直接返回预设的信息。 目前只支持服务级别的熔断,不支持具体到某个URL进行熔断。

自定义fallback

通过继承FallbackProvider接口,自定义fallback,并指定给某个route,实现该route的熔断处理。

public interface FallbackProvider {

    //指明拦截哪个服务
	public String getRoute();

    //定制返回内容
	public ClientHttpResponse fallbackResponse();

    //异常信息
    public ClientHttpResponse fallbackResponse(Throwable cause);
}

路由重试

Zuul结合Spring Retry实现了路由重试。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。

  1. 添加Spring Retry依赖
<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>
  1. 配置文件中开启Zuul Retry
#是否开启重试功能
zuul.retryable=true
#对当前服务的重试次数
ribbon.MaxAutoRetries=2
#切换相同Server的次数
ribbon.MaxAutoRetriesNextServer=0

Cookie和头信息

配置文件中设置sensitiveHeaders,添加需要忽略的信息到请求上下文中,供后续ROUTE阶段的过滤器使用 默认情况下,Zuul在请求路由时,会过滤HTTP请求头信息中的一些敏感信息,默认的敏感头信息通过zuul.sensitiveHeaders定义,包括Cookie、Set-Cookie、Authorization。

zuul.sensitiveHeaders= # 使用空来覆盖默认值
zuul.routes.<routeName>.sensitive-headers= # 对指定路由的敏感头设置为空
zuul.routes.<routeName>.custom-sensitive-headers=true # 对指定路由开启自定义敏感头

头信息

通过配置属性zuul.add-host-header=true正确处理头信息,比如重定向

性能优化参考

  1. 在application.yml文件中配置线程数、缓冲大小
server:
	tomcat:
		max-threads: 128 # 最大worker线程
		min-spare-threads: 64 # 最小worker线程
	undertow:
		io-threads: 8 # IO线程数,默认为CPU核心数,最小为2
		worker-threads: 40 # 阻塞任务线程池,值设置取决于系统的负载,默认为io-threads * 8
		buffer-size: 512 # 每块buffer的空间大小
		buffers-per-region: 10 # 每个区分配的buffer数量
		direct-buffers: 512 # 是否分配的直接内存
  1. 在application.yml文件中配置zuul和ribbon
zuul:
	host:
		max-total-connections: 500 # 每个服务的http客户端连接池最大连接,默认值是200
		max-per-route-connections: 50 # 每个route可用的最大连接数,默认值是20
	ribbon-isolation-strategy: THREAD # 可选:SEMAPHORE THREAD
  1. 在application.yml文件中配置hystrix
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 设置thread的默认超时时间,默认值是10000。

hystrix.command.[CommandKey].execution.isolation.thread.timeoutInMilliseconds 设置不同微服务的超时时间。