SpringCloud之Zuul简单实例(springboot2.2.2RELEASE)

一. API网关简介

网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。

在这里插入图片描述

二. Zuul简介

Zuul是Spring Cloud全家桶中的微服务API网关。

所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 认证和安全:识别每一个资源的验证要求,并拒绝那些不符的请求

  • 性能监测:在服务边界追踪并统计数据,提供精确的生产视图

  • 动态路由:动态将请求路由到不同后端集群

  • 压力测试:逐渐增加指向集群的流量,以了解性能

  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求

  • 静态响应处理:边缘位置进行响应,避免转发到内部集群

  • 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化

Spring Cloud对Zuul进行了整合和增强。目前,Zuul使用的默认是Apache的HTTP Client,也可以使用Rest Client,可以设置ribbon.restclient.enabled=true.

2.1 高可用

对于网关来说所面对的请求大部分来自于外部(客户端未注册到Eureka Server),要实现网关的高可用可以在在网关前面再架一个前置代理(如Nginx)。如果客户端注册在Eureka Server上那么客户端可以从Eureka Server处获取Zuul网关地址实现客户端负载均衡。

客户端未注册到Eureka Server上(比如手机App端等):
在这里插入图片描述
客户端注册在Eureka Server上(比如:MVC App, SPA 等):
在这里插入图片描述

2.2 Zuul聚合微服务

一个外部请求,可能会通过Zuul查询多个微服务,如果让其一个个请求,就算Zuul转发,网络开销、流量耗费、时长都不是很理想。

我们可以使用Zuul聚合微服务请求,即应用系统只发送一个请求给Zuul,由Zuul请求不同的微服务,并把数据返给应用系统。

三. 示例

3.1 代码地址

eurekaclientzuul

3.2 pom.xml

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.3 启动类

@SpringBootApplication
@EnableZuulProxy
public class CloudZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudZuulApplication.class, args);
    }
}

3.4 配置文件


spring.application.name=cloud-zuul

server.port=8040
# logging 配置
logging.config=classpath:log4j2.xml

#根据ip注册实例
eureka.instance.prefer-ip-address=true

#指定注册实例ID(默认是主机名:应用名:应用端口)
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#指定注册实例主机名
#eureka.instance.hostname=127.0.0.1
#eureka.instance.hostname= ${spring.cloud.client.ip-address}

#注册地址 eureka服务端的地址 多节点用,分隔
eureka.client.service-url.defaultZone=http://127.0.0.1:8025/eureka/

3.5 路由设置示例

先启动eureka、client然后启动zuul。其中client启动两个,一个端口为8030一个为8031

1.zuul默认代理所有微服务。在浏览器输入http://localhost:8040/cloud-client/client/api/findById?id=5结果是:

<JSONObject>
<id>5</id>
</JSONObject>

说明配置生效

2.zuul默认代理所有微服务。要忽略指定微服务用下的配置。多个服务用,隔开,要忽略所有微服务用 *。这里忽略cloud-client服务。

zuul.ignored-services=cloud-client

在浏览器输入http://localhost:8040/cloud-client/client/api/findById?id=5结果是是404,说明配置生效。

3.指定微服务(cloud-client)的访问路径。

zuul.routes.cloud-client=/user/**

表示以/user开头,就会请求到服务id为cloud-client的服务上面,请求地址是/**。

在浏览器输入http://localhost:8040/user/client/api/findById?id=5结果是:

<JSONObject>
<id>5</id>
</JSONObject>

说明配置生效

4.为cloud-client服务配置别名(client)和访问路径。

zuul.routes.client.service-id=cloud-client
zuul.routes.client.path=/user/**

表示访问路径是/user开头的会请求到cloud-client的服务上面,请求地址是/**。

在浏览器输入http://localhost:8040/user/client/api/findById?id=5结果是:

<JSONObject>
<id>5</id>
</JSONObject>

说明配置生效

5.指定path和URL。

zuul.routes.client.service-id=cloud-client
zuul.routes.client.service-id=cloud-client
zuul.routes.client.path=/user/**

表示请求是/user开头,就会请求到http://localhost:8030 上面,请求地址是http://localhost:8030/** 该配置无法使用负载均衡功能。

在浏览器输入http://localhost:8040/user/client/api/findById?id=5结果是:

<JSONObject>
<id>5</id>
</JSONObject>

其中端口为8030的client有日志INFO findById:5,而端口为8031的client没有日志。说明配置生效

6.指定path和URL,并保留Zuul的Hystrix、Ribbon特性

zuul.routes.client.service-id=cloud-client
zuul.routes.client.path=/user/**
#为Ribbon禁用Eureka
ribbon.eureka.enabled=false
cloud-client.ribbon.listOfServers=localhost:8030,localhost:8031

表示请求是/user开头,就会请求到http://localhost:8030、localhost:8031上面,请求地址是http://localhost:8030/** 或者http://localhost:8031/** 该配置可以使用zuul负载均衡功能。

在浏览器输入http://localhost:8040/user/client/api/findById?id=5,多访问几次,结果都是:

<JSONObject>
<id>5</id>
</JSONObject>

其中端口为8030及8031的client都有日志INFO findById:5。说明配置生效

7.设置全局路由前缀,并重写指定微服务(这里是cloud-client)的访问路径。

#设置一个全局的前缀,即所有请求必须添加client
zuul.prefix=/client
#是否将这个代理前缀去掉
zuul.strip-prefix=true
zuul.routes.cloud-client=/user/**

其中zuul.strip-prefix=false则访问路径以/client/user开头时,就会请求到服务id为cloud-client的服务上面,请求地址是/client/**

zuul.strip-prefix=true则访问路径以/client/user开头时,就会请求到服务id为cloud-client的服务上面,请求地址是/**。

zuul.strip-prefix是true还是false,浏览器输入http://localhost:8040/user/client/api/findById?id=5则结果都为404

当zuul.strip-prefix=false时,浏览器输入http://localhost:8040/client/user/api/findById?id=5则结果为:

<JSONObject>
<id>5</id>
</JSONObject>

当zuul.strip-prefix=false时,浏览器输入http://localhost:8040/client/user/client/api/findById?id=5则结果为404

当zuul.strip-prefix=true时,浏览器输入http://localhost:8040/client/user/api/findById?id=5则结果为404

当zuul.strip-prefix=true时,浏览器输入http://localhost:8040/client/user/client/api/findById?id=5则结果为:

<JSONObject>
<id>5</id>
</JSONObject>

证明配置生效

8.设置局部路由前缀,并重写指定微服务(这里是cloud-client)的访问路径。

zuul.prefix=/client
zuul.routes.client.service-id=cloud-client
#是否将这个代理前缀去掉 
zuul.routes.client.strip-prefix=true
zuul.routes.client.path=/user/**

其中zuul.routes.client.strip-prefix=false 不能生效
zuul.routes.client.strip-prefix=true生效,以/client/user开头,就会请求到服务id为cloud-client的服务上面,请求地址是/client/**

当zuul.routes.client.strip-prefix=true时浏览器输入http://localhost:8040/client/user/client/api/findById?id=5则结果为:

<JSONObject>
<id>5</id>
</JSONObject>

当zuul.routes.client.strip-prefix=true时浏览器输入http://localhost:8040/client/user/api/findById?id=5则结果为404

证明配置生效

9.忽略某些微服务中的某些路径

zuul.ignoredPatterns=/**/findById
zuul.routes.cloud-client=/user/**

浏览器输入http://localhost:8040/user/client/api/findById?id=5则结果为404

浏览器输入http://localhost:8040/user/client/api/=5则结果为:

<JSONObject>
<findByPathId>5</findByPathId>
</JSONObject>

10.本地转发
新建一个类

@RestController
@RequestMapping("api")
@Log4j2
public class Controller {

    @GetMapping("findById")
    public boolean findById(@RequestParam(name = "id") Long id) {
        log.info("Zuul-->findById:"+id);
        return true;
    }
}

配置如下:

zuul.routes.forward.path=/user/**
zuul.routes.forward.url=forward:/api

浏览器输入http://localhost:8040/user/findById?id=5会被本地转发成http://localhost:8040/api/findById?id=5结果为:

<Boolean>true</Boolean>

3.6 Zuul上传文件示例

如果要通过Zuul来上传文件则要加路径前面加/zuul,比如:curl -F "[email protected]" http://localhost:8040/zuul/user/client/api/upload 如果不加就会报错

对于有大文件上传则还要设置超时时间:

hystrix.command.default.execution.isolation.thread.timeoutInMillseconds=60000
ribbon.ConnectTimeout=90000
ribbon.ReadTimeout=90000

如果设置路由前缀则zuul在前缀前面如: curl -F "[email protected]" http://localhost:8040/zuul/client/user/client/api/upload

3.7 Header设置

1.过滤客户端请求中的headers

#全局
zuul.sensitive-headers=Cookie,Set-Cookie,Authorization
#局部
zuul.routes.client.custom-sensitive-headers=true
zuul.routes.client.sensitive-headers=Cookie,Set-Cookie,Authorization

如果客户端在发请求是带了Cookie,那么Cookie不会传递给下游服务。

2.过滤服务之间通信附带的headers

zuul.ignored-headers=Cookie,Set-Cookie,Authorization

如果客户端在发请求是带了Cookie,那么Cookie依然会传递给下游服务。但是如果下游服务再转发就会被过滤。

zuul.ignored-headers的作用与上面zuul.sensitive-headers差不多,事实上sensitive-headers会被添加到ignored-headers中。

还有一种情况就是客户端带了Cookie,在ZUUL的Filter中又addZuulRequestHeader(“Cookie”, “new”),
那么客户端的Cookie将会被覆盖,此时不需要sensitive-headers。

如果设置了sensitiveHeaders: Cookie,那么Filter中设置的Cookie依然不会被过滤。

3.8 过滤器示例

3.8.1 过滤器特征

  • Type:用以表示路由过程中的阶段(包含pre、routing、post、error)

  • Execution Order:表示相同Type的Filter的执行顺序

  • Criteria:执行条件

  • Action:执行体

Zuul提供了动态读取、编译和执行Filter的框架。各个Filter间没有直接联系,但是都通过RequestContext共享一些状态数据。

Zuul支持任何基于JVM的语言,但是过滤器目前是用Groovy编写的。

3.8.2 过滤器类型

  • pre:在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录

  • routing:将请求路由到微服务,用于构建发送给微服务的请求,并使用Apache Http Client或者Netflix Ribbon请求微服务。指定serviceId时使用Netflix Ribbon否则使用Apache Http Client

  • post:在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。

  • error:出错时执行

3.8.3 过滤器生命周期图

在这里插入图片描述

1.修改启动类

@SpringBootApplication
@EnableZuulProxy
public class CloudZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudZuulApplication.class, args);
    }
    @Bean
    public PreRequestLogFilter preRequestLogFilter(){
        return new PreRequestLogFilter();
    }
}

2.添加过滤器类

@Log4j2
public class PreRequestLogFilter extends ZuulFilter {
    // 过滤器类型
    @Override
    public String filterType() {
       return "pre";//在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录
//        return "routing";//将请求路由到微服务,用于构建发送给微服务的请求,并使用Apache Http Client或者Netflix Ribbon请求微服务
//        return "post";//在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
//        return "error";//出错时执行
    }

    // 优先级,越大越靠后执行
    @Override
    public int filterOrder() {
        return 0;
    }

    // 是否需要过滤
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        log.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL())+System.currentTimeMillis());
        return null;
    }
}

3.自定义的Filter可以通过shouldFilter方法返回false来禁用,第三方的可以通过下面方式来禁用

#PreRequestLogFilter是过滤器名,pre是类型
zuul.PreRequestLogFilter.pre.disable=true

3.9 容错和回退示例

@Component
public class DefaultFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        // 表明是为哪个微服务提供回退 *表示匹配所有
        return "*";
    }



    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //当fallback时返回给调用者的状态码
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                //状态码的文本形式
                return getStatusCode().getReasonPhrase();

            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //响应体
                return new ByteArrayInputStream("cloud-client Service is not avaiable, please try again later."
                        .getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //设定headers
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.TEXT_HTML);
                return headers;
            }
        };
    }
}

关闭client浏览器输入http://localhost:8040/user/client/api/findById?id=5页面会输出cloud-client Service is not available, please try again later.

参考文章:Spring Cloud 微服务架构学习笔记与示例ZUUL-API网关使用Zuul构建API Gateway

发布了92 篇原创文章 · 获赞 4 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/DingKG/article/details/103696219