前面介绍了如何使用 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 示例,启动成功后服务信息如下:
从上图可知,Zuul 示例运行在 9000 端口上,并且还启动了一个 Eureka 服务,一个订单服务,一个产品服务。
使用浏览器访问 http://localhost:9000/api/product/product/1 地址,效果如下图:
上述地址 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 地址,效果如下图:
到这里,使用 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/
效果如下图:
最简化的配置,即不配置任何 zuul 相关的路由信息。将上述配 Zuul 的配置注释掉,如下:
#zuul: # routes: # service-order: # path: /api/order/** # url: http://localhost:8092/ # service-product: # path: /api/product/** # url: http://localhost:8091/
重启服务,访问如下地址也能正常访问,如下图:
为什么会造成这种效果呢?这是因为 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 的知识,继续阅读后续章节……
点击下载/查看本教程相关资料或者源代码。