Spring Cloud实战系列(五) - 服务网关Zuul

前言

ZuulNetflix 开源的一个 API Gateway 服务器, 本质上是一个基于 ServletWeb 应用。在微服务框架 Spring Cloud 中,Zuul 被作为 服务的网关,负责对 请求 进行一些 预处理,比如:安全验证动态路由负载分配 等等。

正文

1. 路由网关

在前面几篇的基础上,新建一个 service-zuul 的项目模块,配置 pom.xml 如下:

<?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>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>io.github.ostenant.springcloud</groupId>
    <artifactId>service-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-zuul</name>
    <description>Demo project for Spring Boot</description>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
</project>

在应用程序启动类上,使用 注解 @EnableZuulProxy 开启 路由网关

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ServiceZuulApplication {

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

application.yml 文件中配置 URL 前缀和 服务 的映射关系。首先指定 服务注册中心 的地址为 http://localhost:8761/eureka/,服务的 端口号8769服务名service-zuul,以 /api-a/前缀 的请求都转发给 service-feign 服务,以 /api-b/前缀 的请求都转发给 service-ribbon 服务。

server:
  port: 8769
spring:
  application:
    name: service-zuul
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
zuul:
  routes:
    api-a:
      path: /api-a/**
      serviceId: service-feign
    api-b:
      path: /api-b/**
      serviceId: service-ribbon

按顺序依次运行 eureka-serverservice-hiservice-ribbonservice-feignservice-zuul 几个应用,其中在 87628763 端口 启动两个 service-hi 服务实例。

访问 http://localhost:8769/api-a/hi?name=zuul 两次,服务端返回数据如下:

Hi zuul, I am from port: 8763

Hi zuul, I am from port: 8762

这说明 Zuul 起到了 路由 的作用。如果某个 服务 存在 多个实例Zuul 会结合 Ribbon负载均衡,将请求 均分并路由 到不同的 服务实例

2. 配置过滤器

Zuul 不仅只作为 路由,并且提供 过滤功能,包括一些 安全验证。继续改造项目,增加一个 Zuul 提供的过滤器。

@Component
public class MyFilter extends ZuulFilter {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }


    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的具体逻辑
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        logger.info(String.format("%s >> %s", request.getMethod(), 
                request.getRequestURL().toString()));

        Object accessToken = request.getParameter("token");
        if (accessToken == null) {
            log.warn("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
        }
        return null;
    }
}

filterType() 方法表示 过滤器 的类型:

  • pre:路由发生之前;

  • routing:路由发生时;

  • post:路由发生之后;

  • error:发送错误调用时。

访问 http://localhost:8769/api-b/hi?name=zuul,服务端返回的响应数据如下:

token is empty

访问 http://localhost:8769/api-b/hi?name=zuul&token=123 两次,服务端返回的响应数据如下:

Hi zuul, I am from port: 8763

Hi zuul, I am from port: 8762

3. 配置API前戳版本号

在上面 Zuul 工程 application.yml 的基础上增加一条配置 zuul.prefix: /v1,然后重启应用,访问 http://localhost:8769/v1/api-b/hi?name=mark&token=123 即可。

4. 配置熔断器

Zuul 的基础上实现 熔断功能 很简单,类似实现 过滤器 的步骤,只需要实现 ZuulFallbackProvider 接口即可,代码如下:

@Component
public class MyFallbackProvider implements ZuulFallbackProvider {

    @Override
    public String getRoute() {
        return "service-feign";
    }

    @Override
    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("Fallback method is invoked!".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
                return httpHeaders;
            }
        };
    }
}
  • getRoute():指定 熔断器 作用于哪些 服务

  • fallbackResponse():指定 熔断 时返回的响应数据。

重新启动 service-zuul 应用,并关闭所有的 service-hi服务实例,在浏览器上访问 http://localhost:8769/v1/api-a/hi?name=mark&token=123,服务端返回的响应数据如下:

Fallback method is invoked!

如果需要所有的 路由服务 都加上 熔断功能,只需要在 getRoute() 方法上返回 “*”通配符 即可。

5. Zuul的常见使用方式

Zuul 采用的是 同步步阻塞模型,性能比 Nginx 差,由于 Zuul 和其他 Netflix 组件 相互配合无缝集成Zuul 很容易就能实现 负载均衡智能路由熔断器 等功能。在大多数情况下,Zuul 都是以 集群 的形式部署。

  • 对不同的 终端用户,使用不同的 Zuul 来进行 路由,例如 移动端 共用一个 Zuul 网关实例Web 端用另一个 Zuul 网关实例,其他的 客户端 用另一个 Zuul 实例进行路由。

  • 另外一种常见的 集群部署方式 就是通过 NginxZuul 相互结合来做 负载均衡

参考

  • 方志朋《深入理解Spring Cloud与微服务构建》

欢迎关注技术公众号: 零壹技术栈

12738336-e788ede74d7625e2
零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值