Spring Cloud—九、服务网关Spring Cloud Zuul

9.1、分析

通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:
微服务架构.png

我们使用Spring Cloud netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?

先来说说这样结构需要做的一些事儿以及存在的不足:
1.首先,破坏了服务无状态特点。

  • a.为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中Rest Api无状态的特点。
  • b.从具体开发测试的角度来说,在工作中出了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。

2.其次,无法直接复用既有接口。

  • a.当我们需对一个既有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

面对类似上面的问题,我们要如何解决呢?答案是:服务网关!

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的负载均衡器->服务网关。

服务网关时微服务结构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

9.2、Zuul的简介

官网:https://github.com/Netflix/zuul

zuul.png

Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。

Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能。

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产试图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加指向集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的边缘更贴近系统的使用者。

Spring Cloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是Apache HTTP Client,也可以使用RestClient或者okhttp3.0kHttpClient。如果想要使用RestClient,可以设置ribbon.restclient.enabled=true;想要使用okhttp3.0kHttpClient,可以设置ribbon.okhttp.enabled=true。

9.3、使用Zuul之后的架构

架构.png

从架构图中可以看出,客户端请求微服务时,先经过Zuul之后再请求,这样就可以将一些类似于校验的业务逻辑放到zuul中完成。

而微服务自身只需要关注自己的业务逻辑即可。

9.4、快速入门

9.4.1、创建工程springcloud-demo-zuul
9.4.2、导入依赖
<!--导入Spring Cloud的依赖管理-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
9.4.3、在启动类上添加注解@EnableZuulProxy
添加注解.png
9.4.4、编写application.properties文件
server.port=8080
spring.application.name=springcloud-demo-zuul
9.4.5、编写路由规则

首先,查看Eureka中的服务:
Eureka.png

可以看到,当前Eureka中有2个商品服务。

接下来,我们编写路由规则:

 #配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
zuul.routes.springcloud-demo-item.url=http://127.0.0.1:18100

9.4.6、启动测试
测试.png

可以看到,已经通过zuul访问到了商品服务。

9.5、面向服务的路由

在快速入门中我们已经通过了URL映射,访问到了商品微服务。这样做会存在一个问题,就是,如果商品微服务的地址发生了改变怎么办?
很自然的能够想到,不应该配置具体的url而是走Eureka注册中心获取地址。

9.5.1、添加Eureka服务的依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </exclusion>
    </exclusions>
</dependency>
9.5.2、修改application.properties配置文件
 server.port=8080
spring.application.name=springcloud-demo-zuul
#配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
#zuul.routes.springcloud-demo-item.url=[http://127.0.0.1:18100](http://127.0.0.1:18100/)
#指定Eureka注册中心的服务id
zuul.routes.springcloud-demo-item.service-id=springcloud-demo-item
#是否将自己注册到Eureka服务中
eureka.client.register-with-eureka=false
#是否从Eureka中获取信息
eureka.client.fetch-registry=true
#Eureka服务端与Eureka客户端交互地址
eureka.client.serviceUrl.defaultZone=http://zuo:123456@127.0.0.1:8888/eureka/
#将自己得ip地址注册到Eureka服务中
eureka.instance.prefer-ip-address=true
9.5.3、在启动类中增加@EnableDiscoveryClient注解
启动类.png
9.5.4、重启测试

查看Eureka注册中心:


eureka.png

发现已经有springcloud-demo-zuul在注册中心了。

接下来测试,功能是否正常:


测试.png

发现一切正常

9.6、zuul配置详解

9.6.1、指定服务id

配置zuul.routes.指定微服务的serviceId=指定路径即可。例如:
zuul.routes.springcloud-demo-item=/item/**
这样设置,springcloud-demo-item微服务就会被映射到/item/**

9.6.2、忽略指定服务

忽略服务非常简单,可以使用zuul.ignored-service配置需要忽略的服务,多个逗号分割。例如:
zuul.ignored-services=springcloud-demo-item,springcloud-demo-a
这样就可让Zuul忽略sptringcloud-demo-item和springcloud-demo-a微服务,只代理其他微服务。

9.6.3、忽略所有服务,只是有路由指定

很多场景下,可能只想要让Zuul代理指定的微服务,此时可以将zuul.ignored-services设为*.
zuul.ignored-services=*
zuul.routes.springcloud-demo-item=/item/**

这样就可以让Zuul只路由springcloud-demo-item微服务

9.6.4、同时配置path和url
#配置请求Url的请求规则
zuul.routes.springcloud-demo-item.path=/springcloud-demo-item/**
#真正的微服务地址
zuul.routes.springcloud-demo-item.url=http://127.0.0.1:18100

其中springcloud-demo-item可以任意起名,只是给路由一个名称。
这样就可以将/springcloud-demo-item/**映射到http://127.0.0.1:18100/ **。

需要注意的是,使用这种方式配置的路由不会作为HystrixCommand执行,同时也不能使用Ribbon来负载均衡多个URL。

9.6.5、使用正则表达式指定路由规则
正则.png
9.6.6、路由前缀
路由前缀.png
9.6.7、忽略某些路径
忽略某些路径.png

9.7、过滤器

过滤器是zuul的重要组件

9.7.1、过滤器ZuulFilter
过滤器.png

zuulFilter是一个抽象类,其实现类需要实现4个方法:
1.shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
2.run:过滤器的具体业务逻辑。
3.filterType:返回字符串代表过滤器的类型
    a) pre:请求在被路由之前执行
    b) routing:在路由请求时调用
    c) post:在routing和error过滤器之后调用
    d) errror:处理请求时发生错误调用
4.filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

9.7.2、执行过程
执行过程.png

从上图中我们可以看到,当外部HTTP请求到达API网关服务的时候,首先它会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型过滤器的主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。在完成了pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post。此时请求将会被post类型的过滤器处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端(对于error过滤器的处理,在Spring Cloud Zuul的过滤链中实际上有一些不同。)

9.8、过滤器实战

需求:通过编写过滤器实现用户是否登陆的检查。
实现:通过判断请求中是否有token,如果有任务就是已经登陆的,如果没有就认为是非法请求,响应401。

9.8.1、编写UserLoginZuulFilter
package cn.zuoqy.springclouddemozuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

/**
* 登陆验证过滤器
* Created by zuoqy on 13:48 2018/10/27.
*/
@Component // 加入到Spring容器
public class UserLoginZuulFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre"; // 设置过滤类型为:pre
    }

    @Override
    public int filterOrder() {
        return 0; // 设置执行顺序
    }

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

    @Override
    public Object run() { // 编写业务逻辑

        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest req = context.getRequest();
        String token = req.getParameter("token");

        if (StringUtils.isEmpty(token)) {
            // 过滤该请求,不对其进行路由
            context.setSendZuulResponse(false);
            // 设置响应状态
            context.setResponseStatusCode(401);
        }
        return null;
    }

}
9.8.2、启动测试
测试1.png

测试2.png

可以看到过滤器已经生效。

Spring Cloud—一、微服务架构
Spring Cloud—二、Spring Cloud简介
Spring Cloud—三、使用Spring Cloud实现微服务
Spring Cloud—四、Spring Cloud快速入门
Spring Cloud—五、注册中心Eureka
Spring Cloud—六、使用Ribbon实现负载均衡
Spring Cloud—七、容错保护:Hystrix
Spring Cloud—八、使用Feign实现声明式的Rest调用
Spring Cloud—九、服务网关Spring Cloud Zuul
Spring Cloud—十、使用Spring Cloud Config统一管理微服务
Spring Cloud—十一、使用Spring Cloud Bus(消息总线)实现自动更新

demo源码

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

推荐阅读更多精彩内容