Zuul使用与原理

一、使用

1.1、核心依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>2.2.9.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.9.RELEASE</version>
  </dependency>
</dependencies>

入口类,用@EnableZuulProxy进行标注, @EnableZuulProxy功能包含了@EnableZuulServer的功能

@EnableZuulProxy  //启用zuul反向代理功能
@SpringBootApplication
public class ZuulApp {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApp.class, args);
    }
}

1.2、静态路由

静态路由是将路由配置写在配置文件中,最简单的路由配置

server:
  port: 9001

zuul:
  routes:
    users:
      path: /**  # 代表所有请求都重定向到后端服务
      url: http://localhost:8081
  prefix: /api
server:
  port: 9001

zuul:
  routes:
    users:
      path: /hello/**  # 将hello请求转发到后端
      url: http://localhost:8081/hello
  prefix: /api

带有负载均衡功能

server:
  port: 9001

zuul:
  routes:
    users:
      path: /**
      serviceId: backendServices
  prefix: /api

ribbon:
  eureka:
    enabled: false

backendServices:
  ribbon:
    listOfServers: http://localhost:8081,http://localhost:8082

1.2、动态路由

静态路由虽然方便简单,但是不能实现动态扩展,往往不符合我们的需求。为了实现动态化可以通过继承SimpleRouteLocator方式进行扩展,我们在SimpleRouteLocator类中注释内容也得到了确认

	/**
	 * Calculate all the routes and set up a cache for the values. Subclasses can call
	 * this method if they need to implement {@link RefreshableRouteLocator}.
	 */
	protected void doRefresh() {
		this.routes.set(locateRoutes());
	}

	/**
	 * Compute a map of path pattern to route. The default is just a static map from the
	 * {@link ZuulProperties}, but subclasses can add dynamic calculations.
	 * @return map of Zuul routes
	 */
	protected Map<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
		for (ZuulRoute route : this.properties.getRoutes().values()) {
			routesMap.put(route.getPath(), route);
		}
		return routesMap;
	}

具体实现如下:

zuul:
  servlet-path: /zull
  prefix: /api
public class BackendRouteLocator extends SimpleRouteLocator {
    private ZuulProperties properties;
    public BackendRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        this.properties = properties;
    }

    /** 代替下面routes配置
     * zuul:
     *   routes:
     *     users:
     *       path: /user/**
     *       url: http://localhost:8081/user
     *     orders:
     *       path: /order/**
     *       url: http://localhost:8081/order
     *   prefix: /api
     * @return
     */
    @Override
    protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
        Map<String, ZuulProperties.ZuulRoute> routes = new HashMap<>();
        ZuulProperties.ZuulRoute route = new ZuulProperties.ZuulRoute();
        route.setId("users");
        route.setLocation("http://localhost:8081/user");
        route.setPath("/user/**");
        routes.put(properties.getPrefix()+route.getPath(), route); //要与前缀/api进行拼接

        ZuulProperties.ZuulRoute orders = new ZuulProperties.ZuulRoute();
        orders.setId("orders");
        orders.setLocation("http://localhost:8081/order");
        orders.setPath("/order/**");
        routes.put(properties.getPrefix()+orders.getPath(), orders);
        return routes;
    }
}

二、原理剖析

2.1、执行原理

zuul的底层实现是基于springmvc框架实现请求转发的,而在springmvc框架中,要想处理请求必须要有controller控制器,在springmvc中有两种方式可以定义controller控制器:
1)通过注解@Controller,是固定请求,不能动态化。
2)通过实现接口Controller,可以动态化
spring-cloud-netflix-zuul为了实现动态化,继承了Controller接口,uml类图如下:
在这里插入图片描述
zuul处理流程图如下:
在这里插入图片描述
注入ZuulController,后面就可以处理请求了

@Bean
public ZuulController zuulController() {
  return new ZuulController();
}
public class ZuulController extends ServletWrappingController {
	public ZuulController() {
		setServletClass(ZuulServlet.class); //zuul 定义的servlet类,并不会注入到Servlet容器中
		setServletName("zuul");
		setSupportedMethods((String[]) null); // Allow all
	}

  //处理请求的入口,实现接口Controller中方法
	@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); //最后调用ZuulServlet中service方法
		}
		finally {
			// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
			RequestContext.getCurrentContext().unset();
		}
	}

}

2.2、 Filter

zuul中的Filter并非是Servlet中的Filter,但实现的功能都是类似。zuul定义的IZuulFilter接口如下

public interface IZuulFilter {
    /**
     * a "true" return from this method means that the run() method should be invoked
     *
     * @return true if the run() method should be invoked. false will not invoke the run() method
     */
    boolean shouldFilter();

    /**
     * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
     *
     * @return Some arbitrary artifact may be returned. Current implementation ignores it.
	 * @throws ZuulException if an error occurs during execution.
     */
    Object run() throws ZuulException;
}

zuul提供了内置Filter,其中比较重要是SimpleHostRoutingFilter,这个过滤器会将请求转发给backend


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

@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 (getContentLength(request) < 0) {
		context.setChunkedRequestBody();
	}

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

	try {
	    //转发请求给backend
		CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
				headers, params, requestEntity);
		setResponse(response);
	}
	catch (Exception ex) {
		throw new ZuulRuntimeException(handleException(ex));
	}
	return null;
}

我们通过继承抽象类ZuulFilter,来扩展zuul功能,下面是我自定义一个限流器的Filter

public class RateLimiterFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE; //前置过滤器
    }

    @Override
    public int filterOrder() {
        return 1; //过滤器执行顺序
    }

    @Override
    public boolean shouldFilter() {
        return true;  //当前过滤器是否需要执行  true执行
    }

    @Override
    public Object run() throws ZuulException {//强制被限流,默认返回限流错误
        System.out.println("RateLimiterFilter >>>");
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.setResponseBody("{\"data\":\"failed\", \"errCode\":\"rate limiter\"}");
        ctx.getResponse().setContentType("application/json;charset=UTF-8");
        ctx.setSendZuulResponse(false);
        ctx.setResponseStatusCode(429);
        return null;
    }
}

zuul提供一些内置过滤器以及相应的执行顺序,再自定义过滤器时要避免顺序与之冲突

    // FilterConstants.java
	// ORDER constants -----------------------------------
	/**
	 * Filter Order for {@link DebugFilter#filterOrder()}.
	 */
	public static final int DEBUG_FILTER_ORDER = 1;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter#filterOrder()}.
	 */
	public static final int FORM_BODY_WRAPPER_FILTER_ORDER = -1;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter}.
	 */
	public static final int PRE_DECORATION_FILTER_ORDER = 5;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#filterOrder()}.
	 */
	public static final int RIBBON_ROUTING_FILTER_ORDER = 10;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter#filterOrder()}.
	 */
	public static final int SEND_ERROR_FILTER_ORDER = 0;

	/**
	 * Filter Order for {@link SendForwardFilter#filterOrder()}.
	 */
	public static final int SEND_FORWARD_FILTER_ORDER = 500;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#filterOrder()}.
	 */
	public static final int SEND_RESPONSE_FILTER_ORDER = 1000;

	/**
	 * Filter Order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#filterOrder()}.
	 */
	public static final int SIMPLE_HOST_ROUTING_FILTER_ORDER = 100;

	/**
	 * filter order for {@link Servlet30WrapperFilter#filterOrder()}.
	 */
	public static final int SERVLET_30_WRAPPER_FILTER_ORDER = -2;

	/**
	 * filter order for
	 * {@link org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter#filterOrder()}.
	 */
	public static final int SERVLET_DETECTION_FILTER_ORDER = -3;

2.3、Route路由注册

Zuul的路由注册采用懒加载+动态注册功能,可参考ZuulHandlerMapping中的lookupHandlers方法


@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) {//只要将dirty设置成true,则会重新加载路由,可实现动态化路由注册
		synchronized (this) {
			if (this.dirty) {
				registerHandlers();//注册Handler
				this.dirty = false;
			}
		}
	}
	return super.lookupHandler(urlPath, request);
}
private void registerHandlers() {
	Collection<Route> routes = this.routeLocator.getRoutes();//获取路由,这里的路由就是配置中定义的routes或者继承SimpleRouteLocator
	if (routes.isEmpty()) {
		this.logger.warn("No routes found from RouteLocator");
	}
	else {
		for (Route route : routes) {
			registerHandler(route.getFullPath(), this.zuul); //注册handlerMapping,getFullPath=prefix+path, zuul类型是ZuulController
		}
	}
}

三、总结

zuul框架并不复杂,带比较简单,上面是我对zuul理解,欢迎大家一起讨论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值