2019-08-26 10:38:53 +08:00

13 KiB
Raw Blame History

Spring-Cloud-Zuul

一、简介
        1.1 API 网关
        1.2 Spring Cloud Zuul
二、项目结构
三、Zuul 网关
        3.1 引入依赖
        3.2 添加注解
        3.3 项目配置
        3.4 启动服务
四、错误熔断
        4.1 默认依赖
        4.2 服务降级
五、Zuul 过滤器
六、负载均衡
七、常见异常

一、简介

1.1 API 网关

API 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能:

1.2 Spring Cloud Zuul

Spring Cloud 基于 Net Flix Zuul 实现了网关组件,这就是 Spring Cloud Zuul。它除了实现负载均衡、错误熔断、路由转发等功能还能与 Spring 的其他组件无缝配合使用。

二、项目结构

spring-cloud-feign 用例已经通过 Feign 实现服务间的调用,且提供了两个服务单元 (consumer、producer),可以方便直观地测试 Zuul 的路由、负载均衡、和错误熔断等功能,所以本用例在其基础上进行整合。

  • common:公共的接口和实体类;
  • consumer:服务的消费者,采用 Feign 调用产品服务;
  • producer:服务的提供者;
  • eureka:注册中心;
  • zuulAPI 网关。

聚合项目目录如下:

Zuul 项目目录如下:

三、Zuul 网关

3.1 引入依赖

主要的依赖是 spring-cloud-starter-netflix-zuul

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.8.RELEASE</version>
        <relativePath/>
    </parent>

    <artifactId>zuul</artifactId>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--zuul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

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


    <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>

</project>

3.2 添加注解

在启动类上添加 @EnableZuulProxy和 @EnableDiscoveryClient 注解,@EnableZuulProxy 会自动设置 Zuul 服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器:

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulApplication {

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

}

3.3 项目配置

在配置文件中指定注册中心的地址并配置网关的路由规则。Zuul 需要指定注册中心的地址Zuul 会从 Eureka 获取其他微服务的实例信息,然后按照指定的路由规则进行请求转发:

server:
  port: 8090
# 指定服务命名
spring:
  application:
    name: zuul
# 指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8010/eureka/
# 网关的路由
zuul:
  routes:
    xxxx: #这个地方的值是可以任意的字符串
      path: /producer/**
      serviceId: producer
    consumer:
      path: /consumer/**
      serviceId: consumer

3.4 启动服务

启动 eureka、producer、consumer、zuul 四个服务,访问 localhost:8090/consumer/sell/product

四、错误熔断

4.1 默认依赖

Zuul 默认整合了 Hystrix ,不用导入其他额外依赖:

4.2 服务降级

创建 CustomZuulFallbackProvider 并实现 FallbackProvider 接口,同时用 @Component 声明为 Spring 组件,即可实现熔断时候的回退服务:

/**
 * @author : heibaiying
 * @description : zuul 的熔断器
 */
@Component
public class CustomZuulFallbackProvider implements FallbackProvider {

    /*
     * 定义熔断将用于哪些路由的服务
     */
    @Override
    public String getRoute() {
        return "consumer";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {

            /**
             * 返回响应的 HTTP 状态代码
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            /**
             * 返回 HTTP 状态代码
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE.value();
            }

            /**
             * 返回响应的 HTTP 状态文本
             */
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            /**
             * 将消息正文作为输入流返回
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("商城崩溃了,请稍后重试!".getBytes());
            }

            /**
             * 将消息正文作为输入流返回
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
                return httpHeaders;
            }
        };
    }
}

正确返回了内容、同时返回的 Http 状态码也和我们设置的一样。

五、Zuul 过滤器

创建自定义过滤器继承自 CustomZuulFilter当我们访问网关的时候如果判断 Session 中没有对应的 code则跳转到我们自定义的登录页面

/**
 * @author : heibaiying
 * @description : 自定义 filter 过滤器
 */

@Component
public class CustomZuulFilter extends ZuulFilter {

    /**
     * 返回过滤器的类型
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 返回过滤器的优先级顺序
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 从此方法返回“true”意味着应该调用下面的 run方法
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * ZuulFilter 的核心校验方法
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String code = (String)request.getSession().getAttribute("code");
        if (StringUtils.isEmpty(code)){
            // 设置值为 false 不将请求转发到对应的服务上
            currentContext.setSendZuulResponse(false);
            // 设置返回的状态码
            currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
            HttpServletResponse response = currentContext.getResponse();
            try {
                // 跳转到登录页面
                response.sendRedirect("/index");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

index.ftl

<!doctype html>
<html lang="en">
<head>
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <input name="username" type="text">
    <button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>

六、负载均衡

Zuul 默认集成了 Ribbon 并实现了负载均衡,只要启动多个实例即可查看到负载均衡的效果:

这里我们直接在idea 中启动多个实例来测试:

负载均衡测试结果:

七、常见异常

如果出现以下错误导致启动失败,是 Spring Boot 版本不兼容导致的错误Finchley SR2 版本的 Zuul 组件和 Spring Boot 2.1.x 存在不兼容的情况。如果出现这个问题,则将 Spring Boot 版本降至 2.0.x 即可,本用例中采用的是 2.0.8 。

APPLICATION FAILED TO START

---

Description:

The bean 'counterFactory', defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

在实际的开发中应该严格遵循 Spring 官方的版本依赖说明:

Release Train Boot Version
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

更多组件的版本说明可以在 spring cloud overview 页面上查看。