Spring Cloud(Dalston.SR5)--Zuul 网关
我们使用 Spring Cloud Netflix 中的 Eureka 实现了服务注册中心以及服务注册与发现;而服务间通过 Ribbon 或 Feign 实现服务的消费以及均衡负载;使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。在该架构中,我们的服务集群包含:内部服务,需要注册与订阅服务至 Eureka Server;外部服务通过均衡负载公开至服务调用方。
这样的的架构存在的问题有如下:
- 破坏了服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中 REST API 无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
- 其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
- 让客户端直接与各个微服务通讯,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通讯,那么重构将会很难实施。
- 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。
为了解决上面的问题,我们可以使用 Zuul 微服务网关,使用网关优点:
- 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可在微服务网关上进行认证。然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数。
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供 REST API 的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix 中的 Zuul 就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
Zuul 示例如下
- 创建项目
创建名为 spring-cloud-zuul 的 Spring Cloud 项目,并增加 zuul 依赖,修改 POM.xml 中增加以下依赖项:
<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.lixue.zuul</groupId>
<artifactId>spring-cloud-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-zuul</name>
<description>DemoprojectforSpringBoot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/><!--lookupparentfromrepository-->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</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>
- 启用 Zuul
使用注解 @EnableZuulProxy 标注启动类,开启注解后,在 Spring 容器初始化时,会将 Zuul 的相关配置初始化,其中包含一个 ServletRegisterationBean ,该类主要用于注册 Servlet。Zuul 提供了一个 ZuulServlet 类,在 Servlet 的 service 方法中,执行各种 Zuul 过滤器(ZuulFilter),ZuulServlet 的 service 方法接收到请求后,会执行 pre 阶段的过滤器,再执行 routing 阶段的过滤器,最后执行 post 阶段的过滤器。其中 routing 阶段的过滤器将请求转发到指定的 Url 地址;在执行 pre 和 routing 阶段的过滤器时,如果出现异常,则会执行 error 过滤器。整个过程的 HTTP 请求、HTTP响应、状态等数据,都会被封装到一个 RequestContext 对象中。下图是 HTTP 请求在 ZuulServlet 中的生命周期:
示例代码:
package org.lixue.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class SpringCloudZuulApplication{
public static void main(String[] args){
SpringApplication.run(SpringCloudZuulApplication.class,args);
}
}
- 增加配置
修改项目的 src/main/resources/application.yml 配置文件,增加如下内容:
#配置应用名称
spring:
application:
name:spring-cloud-zuul
#配置服务端口
server:
port:9100
#zuul路由配置
zuul:
routes:
#表示http://localhost:9100/person/speaks地址,路由到http://localhost:8080/speaks
person:
url:http://localhost:8080
- 测试验证
该项目依赖一个 service-provider 服务,首先启动 service-provider 服务,然后启动 spring-cloud-zuul 项目,访问地址 http://localhost:9100/person/speaks?names=123 可以看到其他返回和直接访问 http://localhost:8080/speaks?names=123 的返回信息一致,因为 Zuul 在接收到请求时,对请求进行了转发,返回结果如下:
{"123":"Hello World 123 Port=8080"}