Netflix Zuul 简单实现 API 网关

前面介绍了如何使用 Nginx 实现一个简单 API 网关,下面将使用 Zuul 实现 API 网关的基本步骤和相关要点。

添加依赖

在 maven 项目的 pom.xml 中添加 zuul 的依赖信息,如下:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

完整的 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 https://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.2.11.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.hxstrive</groupId>
  <artifactId>zuul-demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>zuul-demo</name>
  <description>zuul-demo</description>

  <properties>
    <java.version>8</java.version>
    <spring-cloud.version>Hoxton.SR8</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-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>

    <!-- spring cloud 依赖 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.83</version>
    </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>
    <finalName>service-demo</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

添加配置

在项目的 resources 目录中添加 application.yml 文件,文件内容如下:

server:
  port: 9000

spring:
  application:
    name: zuul-demo

## 服务地址
eureka:
  client:
    enabled: true
    service-url:
      # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端,
    # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活
    lease-renewal-interval-in-seconds: 5
    # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后,
    # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除
    lease-expiration-duration-in-seconds: 10

# zuul配置
zuul:
  routes:
    service-order:
      path: /api/order/**
      service-id: service-order
    service-product:
      path: /api/product/**
      service-id: service-product

上述的 zuul 配置块是在使用 Zuul 作为 API 网关时,用于定义请求路由规则的。它明确了不同请求路径对应的后端服务,使得从 API 网关进来的请求能够被准确地转发到相应的微服务上进行处理。

具体路由配置解析:

(1)针对 service-order 的路由配置:

  • 路由名称:配置中的 service-order 是为这条路由自定义的一个名称,主要用于在配置中方便地标识和区分不同的路由规则,本身不会直接影响请求的转发逻辑,但有助于配置的可读性和维护性。

  • 请求路径匹配(path 配置项):path: /api/order/** 表示定义了请求路径的匹配规则。当客户端向 API 网关发起的请求路径是以 /api/order/ 开头的任意路径时(** 表示匹配任意多级路径,比如 /api/order/1、/api/order/1/detail 等这样的路径都会匹配),该请求就会进入这条路由的转发逻辑。

  • 后端服务对应(service-id 配置项):service-id: service-order 指明了请求最终要被转发到的后端微服务的标识。也就是说,符合上述 /api/order/** 路径匹配规则的请求,都会被 Zuul 路由转发到名为 service-order 的微服务上去处理,前提是这个微服务已经在服务注册中心(例如 Eureka 等)进行了注册,并且 Zuul 能够发现并与之通信。

(2)针对 service-product 的路由配置:

  • 路由名称:同样,service-product 是这条路由的自定义名称。

  • 请求路径匹配(path 配置项):path: /api/product/** 设定了请求路径的匹配条件,即只要客户端发起的请求路径是以 /api/product/ 开头的任意路径(比如 /api/product/1、/api/product/1/details 等类似路径),就会命中此路由规则。

  • 后端服务对应(service-id 配置项):service-id: service-product 确定了满足路径匹配的请求将被转发至名为 service-product 的微服务上。和前面一样,这个微服务也需要事先在相应的服务注册中心注册登记,以便 Zuul 能找到并把请求正确传递过去。

通过这样的配置,Zuul 就能依据不同的请求路径,将请求精准地分发到对应的后端微服务中,从而实现了 API 网关对多个微服务的请求路由管理功能,有助于构建和管理基于微服务架构的应用系统,让整个系统的服务调用更加清晰、有序。

启动类

下面是 zuul 示例的启动类,代码如下:

package com.hxstrive.hystrix_demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 入口类
 * @author hxstrive.com
 */
@RestController
@SpringBootApplication
@EnableZuulProxy
public class ZuulDemoApplication {

    @Value("${spring.application.name}")
    private String appName;

    @Value("${server.port}")
    private String appPort;

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

    @GetMapping("/")
    public String index() {
        return "appName=" + appName + ", appPort=" + appPort;
    }

}

运行启动类,启动 Zuul 示例,启动成功后服务信息如下:

567cd76f577e3bbc5da6c783ef9bab59_1733064314958-22d4c20f-5710-4c1f-a659-cebe1f02fd8a.png

从上图可知,Zuul 示例运行在 9000 端口上,并且还启动了一个 Eureka 服务,一个订单服务,一个产品服务。

应用验证

使用浏览器访问 http://localhost:9000/api/product/product/1 地址,效果如下图:

7c2d6e2796dc73b6f131f752c4c7f147_1733063574182-1fb22cfb-59f6-43fb-8044-51d23c7b7e6f.png

上述地址 http://localhost:9000/api/product/product/1 将转发到 http://localhost:8091/product/1 地址,因为 service-product 路由配置将 /api/product 开头的请求全部转发到 service-order 服务,而 service-order 服务的访问地址为 http://localhost:8091,因而实际访问地址为 http://localhost:8091/product/1。

继续访问 http://localhost:9000/api/order/order/1 地址,效果如下图:

c4444748fbed90bd80a3281b9ffd3ec4_1733063592216-504300d2-73e2-4ce4-89d6-a9d6e19db5eb.png

到这里,使用 Zuul 搭建简单 API 网关就完成了。

其他知识

上面的 zuul 配置中,使用 service-id 指定一个服务名,服务名来自注册中心,这样就可以实现负载均衡。因为一个服务名可以对应多个服务地址。配置如下:

# zuul配置
zuul:
  routes:
    service-order:
      path: /api/order/**
      # service-order 是服务名,来自注册中心
      service-id: service-order
    service-product:
      path: /api/product/**
      # service-product 是服务名,来自注册中心
      service-id: service-product

指定具体后端地址

zuul 路由配置除了指定服务名外,还可以直接指定一个具体的 URL 地址,如下:

zuul:
  routes:
    # 路由名称
    service-order:
      # 请求路径匹配
      path: /api/order/**
      # 后端服务对应实际访问地址
      url: http://localhost:8092/
    service-product:
      path: /api/product/**
      # 后端服务对应实际访问地址
      url: http://localhost:8091/

注意,如果要实现负载均衡,可以使用逗号分隔多个地址,如下:

zuul:
  routes:
    service-order:
      path: /api/order/**
      url: http://localhost:8092/
    service-product:
      path: /api/product/**
      # 负载均衡
      url: http://localhost:8091/,http://localhost:18091/

效果如下图:

3d283be368d40ab75f76a5f54a262be3_1733144448728-6dc64c1e-eb8f-49b3-b66c-2568a2109080.gif

最简化配置

最简化的配置,即不配置任何 zuul 相关的路由信息。将上述配 Zuul  的配置注释掉,如下:

#zuul:
#  routes:
#    service-order:
#      path: /api/order/**
#      url: http://localhost:8092/
#    service-product:
#      path: /api/product/**
#      url: http://localhost:8091/

重启服务,访问如下地址也能正常访问,如下图:

597e2b9979a2508150f7545154b83f6c_1733065337442-850454e1-286e-493b-afaf-e01224cb9d52.png

22f73ce2555ea1c635615f2ff43f994f_1733065357779-1bcf54d5-be15-4eb5-b2db-d2f1acc73b1c.png

为什么会造成这种效果呢?这是因为 Zuul 使用了注册中心,默认情况下 Zuul 会创建类似如下的配置:

# 最简单的配置,不配置任何路由信息
zuul:
  routes:
    # 路由名称和服务名称一致
    service-order:
      # 匹配 “/服务名” 开头的服务地址
      path: /service-order/**
      # 指定目标服务名
      service-id: service-order
    service-product:
      path: /service-product/**
      service-id: service-product

甚至更简单,直接省略 service-id 配置:

zuul:
  routes:
    # 此时需要保证路由名和服务名一致,如下
    service-order:
      path: /service-order/**
    service-product:
      path: /service-product/**

关于更多 Zuul 的知识,继续阅读后续章节……

点击下载/查看本教程相关资料或者源代码。
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号