跟我学SpringCloud | 第十篇:服务网关Zuul高级篇
springcloud系列教程 | 第十篇:服务网关zuul高级篇
springboot: 2.1.6.release
springcloud: greenwich.sr1
如无特殊说明,本系列教程全采用以上版本
上一篇我们主要聊到了zuul的使用方式,以及自动转发机制,其实zuul还有更多的使用姿势,比如:鉴权、流量转发、请求统计等。
1. zuul的核心
zuul的核心是filter,用来实现对外服务的控制。分别是“pre”、“routing”、“post”、“error”,整个生命周期可以用下图来表示。
zuul大部分功能都是通过过滤器来实现的。zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
routing: 这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用apache httpclient或netfilx ribbon请求微服务。
ost: 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的http header、收集统计信息和指标、将响应从微服务发送给客户端等。
error: 在其他阶段发生错误时执行该过滤器。
2. zuul中默认实现的filter
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
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 | 处理正常的请求响应 |
2.1 禁用指定的filter
可以在application.yml中配置需要禁用的filter,格式:
zuul: formbodywrapperfilter: pre: disable: true
3. 自定义filter
实现自定义filter,需要继承zuulfilter的类,并覆盖其中的4个方法。
package com.springcloud.zuulsimple.filter; import com.netflix.zuul.zuulfilter; import com.netflix.zuul.exception.zuulexception; /** * created with intellij idea. * * @user: weishiyao * @date: 2019/7/6 * @time: 16:10 * @email: inwsy@hotmail.com * description: */ public class myfilter extends zuulfilter { @override public string filtertype() { return null; } @override public int filterorder() { return 0; } @override public boolean shouldfilter() { return false; } @override public object run() throws zuulexception { return null; } }
4. 自定义filter示例
我们假设有这样一个场景,因为服务网关应对的是外部的所有请求,为了避免产生安全隐患,我们需要对请求做一定的限制,比如请求中含有token便让请求继续往下走,如果请求不带token就直接返回并给出提示。
4.1 zuul-simple修改
首先,将上一篇的zuul-simple copy到一个新的文件夹中,自定义一个filter,在run()方法中验证参数是否含有token。
package com.springcloud.zuulsimple.filter; import com.netflix.zuul.zuulfilter; import com.netflix.zuul.context.requestcontext; import com.netflix.zuul.exception.zuulexception; import org.apache.commons.lang.stringutils; import org.slf4j.logger; import org.slf4j.loggerfactory; import javax.servlet.http.httpservletrequest; /** * created with intellij idea. * * @user: weishiyao * @date: 2019/7/6 * @time: 16:11 * @email: inwsy@hotmail.com * description: */ public class tokenfilter extends zuulfilter { private final logger logger = loggerfactory.getlogger(tokenfilter.class); @override public string filtertype() { return "pre"; // 可以在请求被路由之前调用 } @override public int filterorder() { return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低 } @override public boolean shouldfilter() { return true;// 是否执行该过滤器,此处为true,说明需要过滤 } @override public object run() throws zuulexception { requestcontext ctx = requestcontext.getcurrentcontext(); httpservletrequest request = ctx.getrequest(); logger.info("--->>> tokenfilter {},{}", request.getmethod(), request.getrequesturl().tostring()); string token = request.getparameter("token");// 获取请求的参数 if (stringutils.isnotblank(token)) { ctx.setsendzuulresponse(true); //对请求进行路由 ctx.setresponsestatuscode(200); ctx.set("issuccess", true); return null; } else { ctx.setsendzuulresponse(false); //不对其进行路由 ctx.setresponsestatuscode(400); ctx.setresponsebody("token is empty"); ctx.set("issuccess", false); return null; } } }
将tokenfilter加入到请求拦截队列,在启动类中添加以下代码:
@bean public tokenfilter tokenfilter() { return new tokenfilter(); }
这样就将我们自定义好的filter加入到了请求拦截中。
4.2 测试
将上一篇的eureka和producer都cv到新的文件夹下面,依次启动。
打开浏览器,我们访问:http://localhost:8080/spring-cloud-producer/hello?name=spring, 返回:token is empty ,请求被拦截返回。
访问地址:http://localhost:8080/spring-cloud-producer/hello?name=spring&token=123,返回:hello spring,producer is ready,说明请求正常响应。
通过上面这例子我们可以看出,我们可以使用“pre”类型的filter做很多的验证工作,在实际使用中我们可以结合shiro、oauth2.0等技术去做鉴权、验证。
5. 路由熔断
当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行降级处理。zuul给我们提供了这样的支持。当某个服务出现异常时,直接返回我们预设的信息。
我们通过自定义的fallback方法,并且将其指定给某个route来实现该route访问出问题的熔断处理。主要继承fallbackprovider接口来实现,fallbackprovider默认有两个方法,一个用来指明熔断拦截哪个服务,一个定制返回内容。
/* * copyright 2013-2019 the original author or authors. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ package org.springframework.cloud.netflix.zuul.filters.route; import org.springframework.http.client.clienthttpresponse; /** * provides fallback when a failure occurs on a route. * * @author ryan baxter * @author dominik mostek */ public interface fallbackprovider { /** * the route this fallback will be used for. * @return the route the fallback will be used for. */ string getroute(); /** * provides a fallback response based on the cause of the failed execution. * @param route the route the fallback is for * @param cause cause of the main method failure, may be <code>null</code> * @return the fallback response */ clienthttpresponse fallbackresponse(string route, throwable cause); }
实现类通过实现getroute方法,告诉zuul它是负责哪个route定义的熔断。而fallbackresponse方法则是告诉 zuul 断路出现时,它会提供一个什么返回值来处理请求。
我们以上面的spring-cloud-producer服务为例,定制它的熔断返回内容。
package com.springcloud.zuulsimple.component; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cloud.netflix.zuul.filters.route.fallbackprovider; import org.springframework.http.httpheaders; import org.springframework.http.httpstatus; import org.springframework.http.mediatype; import org.springframework.http.client.clienthttpresponse; import java.io.bytearrayinputstream; import java.io.ioexception; import java.io.inputstream; /** * created with intellij idea. * * @user: weishiyao * @date: 2019/7/6 * @time: 16:25 * @email: inwsy@hotmail.com * description: */ @component public class producerfallback implements fallbackprovider { private final logger logger = loggerfactory.getlogger(fallbackprovider.class); //指定要处理的 service。 @override public string getroute() { return "spring-cloud-producer"; } public clienthttpresponse fallbackresponse() { 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("the service is unavailable.".getbytes()); } @override public httpheaders getheaders() { httpheaders headers = new httpheaders(); headers.setcontenttype(mediatype.application_json); return headers; } }; } @override public clienthttpresponse fallbackresponse(string route, throwable cause) { if (cause != null && cause.getcause() != null) { string reason = cause.getcause().getmessage(); logger.info("excption {}",reason); } return fallbackresponse(); } }
当服务出现异常时,打印相关异常信息,并返回”the service is unavailable.”。
需要注意点,这里我们需要将eureka的配置文件修改一下:
server: port: 8761 spring: application: name: eureka-serve eureka: # server: # enable-self-preservation: false client: register-with-eureka: false service-url: defaultzone: http://localhost:8761/eureka/
将eureka的自我保护模式打开,如果这里不开启自我保护模式,producer一停止服务,这个服务直接在eureka下线,zuul会直接报错找不到对应的producer服务。
我们顺次启动这三个服务。
现在打开浏览器,访问链接:http://localhost:8080/spring-cloud-producer/hello?name=spring&token=123, 可以看到页面正常返回:hello spring,producer is ready,现在我们把producer这个服务停下,再刷新下页面,可以看到页面返回:the service is unavailable.。这样我们熔断也测试成功。
6. zuul高可用
我们实际使用zuul的方式如上图,不同的客户端使用不同的负载将请求分发到后端的zuul,zuul在通过eureka调用后端服务,最后对外输出。因此为了保证zuul的高可用性,前端可以同时启动多个zuul实例进行负载,在zuul的前端使用nginx或者f5进行负载转发以达到高可用性。
参考:
http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html