一、使用
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理解,欢迎大家一起讨论