SpringCloud 第十篇:服务网关Zuul高级篇

1. Zuul的核心

Zuul的核心是Filter,用来实现对外服务的控制。分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示。

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

ROUTING: 这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

OST: 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

ERROR: 在其他阶段发生错误时执行该过滤器。(了解源码可+WX:  haiwabbc)

2. Zuul中默认实现的Filter

类型顺序过滤器功能

pre-3ServletDetectionFilter标记处理Servlet的类型

pre-2Servlet30WrapperFilter包装HttpServletRequest请求

pre-1FormBodyWrapperFilter包装请求体

route1DebugFilter标记调试标志

route5PreDecorationFilter处理请求上下文供后续使用

route10RibbonRoutingFilterserviceId请求转发

route100SimpleHostRoutingFilterurl请求转发

route500SendForwardFilterforward请求转发

post0SendErrorFilter处理有错误的请求响应

post1000SendResponseFilter处理正常的请求响应

2.1 禁用指定的Filter

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

zuul:  FormBodyWrapperFilter:    pre:      disable:true

3. 自定义Filter

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

packagecom.springcloud.zuulsimple.filter;importcom.netflix.zuul.ZuulFilter;importcom.netflix.zuul.exception.ZuulException;/** * Created with IntelliJ IDEA. * *@User: weishiyao *@Date: 2019/7/6 *@Time: 16:10 *@email: inwsy@hotmail.com * Description: */publicclassMyFilterextendsZuulFilter{@OverridepublicStringfilterType(){returnnull;    }@OverridepublicintfilterOrder(){return0;    }@OverridepublicbooleanshouldFilter(){returnfalse;    }@OverridepublicObjectrun()throwsZuulException{returnnull;    }}

4. 自定义Filter示例

我们假设有这样一个场景,因为服务网关应对的是外部的所有请求,为了避免产生安全隐患,我们需要对请求做一定的限制,比如请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示。

4.1 zuul-simple修改

首先,将上一篇的zuul-simple copy到一个新的文件夹中,自定义一个Filter,在run()方法中验证参数是否含有Token。

packagecom.springcloud.zuulsimple.filter;importcom.netflix.zuul.ZuulFilter;importcom.netflix.zuul.context.RequestContext;importcom.netflix.zuul.exception.ZuulException;importorg.apache.commons.lang.StringUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjavax.servlet.http.HttpServletRequest;/** * Created with IntelliJ IDEA. * *@User: weishiyao *@Date: 2019/7/6 *@Time: 16:11 *@email: inwsy@hotmail.com * Description: */publicclassTokenFilterextendsZuulFilter{privatefinalLogger logger = LoggerFactory.getLogger(TokenFilter.class);@OverridepublicStringfilterType(){return"pre";// 可以在请求被路由之前调用}@OverridepublicintfilterOrder(){return0;// filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低}@OverridepublicbooleanshouldFilter(){returntrue;// 是否执行该过滤器,此处为true,说明需要过滤}@OverridepublicObjectrun()throwsZuulException{        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);returnnull;        }else{            ctx.setSendZuulResponse(false);//不对其进行路由ctx.setResponseStatusCode(400);            ctx.setResponseBody("token is empty");            ctx.set("isSuccess",false);returnnull;        }    }}

将TokenFilter加入到请求拦截队列,在启动类中添加以下代码:

@BeanpublicTokenFiltertokenFilter(){returnnewTokenFilter();}

这样就将我们自定义好的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.

*/packageorg.springframework.cloud.netflix.zuul.filters.route;importorg.springframework.http.client.ClientHttpResponse;/** * Provides fallback when a failure occurs on a route. * *@authorRyan Baxter *@authorDominik Mostek */publicinterfaceFallbackProvider{/**    * The route this fallback will be used for.    *@returnThe route the fallback will be used for.    */StringgetRoute();/**    * Provides a fallback response based on the cause of the failed execution.    *@paramroute The route the fallback is for    *@paramcause cause of the main method failure, may be null    *@returnthe fallback response    */ClientHttpResponsefallbackResponse(String route, Throwable cause);}

实现类通过实现getRoute方法,告诉Zuul它是负责哪个route定义的熔断。而fallbackResponse方法则是告诉 Zuul 断路出现时,它会提供一个什么返回值来处理请求。

我们以上面的spring-cloud-producer服务为例,定制它的熔断返回内容。

packagecom.springcloud.zuulsimple.component;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.MediaType;importorg.springframework.http.client.ClientHttpResponse;importjava.io.ByteArrayInputStream;importjava.io.IOException;importjava.io.InputStream;/** * Created with IntelliJ IDEA. * *@User: weishiyao *@Date: 2019/7/6 *@Time: 16:25 *@email: inwsy@hotmail.com * Description: */@ComponentpublicclassProducerFallbackimplementsFallbackProvider{privatefinalLogger logger = LoggerFactory.getLogger(FallbackProvider.class);//指定要处理的 service。@OverridepublicStringgetRoute(){return"spring-cloud-producer";    }publicClientHttpResponsefallbackResponse(){returnnewClientHttpResponse() {@OverridepublicHttpStatusgetStatusCode()throwsIOException{returnHttpStatus.OK;            }@OverridepublicintgetRawStatusCode()throwsIOException{return200;            }@OverridepublicStringgetStatusText()throwsIOException{return"OK";            }@Overridepublicvoidclose(){            }@OverridepublicInputStreamgetBody()throwsIOException{returnnewByteArrayInputStream("The service is unavailable.".getBytes());            }@OverridepublicHttpHeadersgetHeaders(){                HttpHeaders headers =newHttpHeaders();                headers.setContentType(MediaType.APPLICATION_JSON);returnheaders;            }        };    }@OverridepublicClientHttpResponsefallbackResponse(String route, Throwable cause){if(cause !=null&& cause.getCause() !=null) {            String reason = cause.getCause().getMessage();            logger.info("Excption {}",reason);        }returnfallbackResponse();    }}

当服务出现异常时,打印相关异常信息,并返回”The service is unavailable.”。

需要注意点,这里我们需要将Eureka的配置文件修改一下:

server:  port:8761spring:  application:    name:eureka-serveeureka:#  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进行负载转发以达到高可用性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354