写在前面

通过前面的学习,我们已经对Spring Cloud Netflix下的核心组件了解了一大半。这些组件基本涵盖了微服务架构中最为基础的几个核心设施,利用这些组件我们已经可以构建起一个简单的微服务架构系统。如使用Spring Cloud Eureka实现高可用的服务注册中心以及服务的注册与发现;使用Spring Cloud Ribbon或者Feign来实现服务间负载均衡的接口调用;使用Spring Cloud Hystrix实现线程隔离并加入熔断机制,以避免微服务架构中因个别服务出现异常而引起级联故障蔓延,提高了系统的健壮性。

基于上述思路,截止到目前我们可以设计出类似于下图所示的基础系统架构:

接下来仔细分析一下该架构的特点。在该架构中,我们的服务集群包含内部服务ServiceA和ServiceB,它们都会向Eureka Server集群进行注册与订阅服务,而Open Service是一个对外的RESTful API服务,它通过F5、Nginx等网络设备或工具软件实现对各个微服务的路由与负载均衡,并公开给外部的客户端调用。

接下来我们将重点研究对外服务这一块Edge Service,通常也称为边缘服务。需要说明的是,通过使用上面所绘制的架构实现系统功能是完全没问题的,但是我们可以站在运维或者开发人员角度思考,这样的架构是否存在一定的问题,或者说是不足?

首先从运维角度出发,看看为了实现这些的架构它们需要做一些什么工作。我们知道当客户端应用单击某个功能的时候,往往会发出一些对于微服务获取资源的请求到后端,这些请求通过F5、Nginx等设备的路由和负载均衡分配后,被转发到各个不同的服务实例上。然后为了能够让这些设备正确路由与分发请求,运维人员需要手动维护这些路由规则与服务实例列表,当有实例增减或是IP地址变动等情况发生时,也需要手动去同步修改这些信息,以保持实例信息与中间件配置内容的一致性。在系统规模不太大的时候,维护这些信息的工作还不算复杂;但是当系统规模不断增加,这些看似非常简单的维护工作会变得越来越难,且出现配置错误的概率也会越来越大,所以这样的做法明显是不合理的,需要有一套机制来有效降低维护路由规则与服务实例列表的难度。

谈完了运维角度,接下来再来聊一聊开发人员的难处。同样主要是站在开发人员的角度,看一看这样的架构会产生怎样的问题?在大多数情况下,为了保证对外服务的安全性,我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,如对用户登录机制的校验。同时为了防止客户端在发起请求时被篡改等考虑,还会有一些签名校验的机制存在。但是这里使用了微服务架构,它破坏了应用的完整性,将原本处于一个应用中的多个模块拆分成了多个应用,而这些应用提供的接口都需要这些校验逻辑,这使得我们必须在这些应用中都实现这样的一套校验逻辑。随着微服务规模的不断扩大,这些校验逻辑的冗余变得越来越多。假设某一天校验规则发生了变化,那么我们需要去每一个应用中修改这些逻辑,这无疑是一项工作量非常大的内容,且这也会加重测试人员的负担,所以我们迫切需要一套机制,用于解决微服务架构中,对于微服务接口访问时前置校验冗余的问题。

API网关就是在这样的条件下诞生的,它的定义类似于面向对象设计模式中的外观模式,它的存在就像是整个微服务架构的门面一样,所有的外部客户端请求访问都需要经过它来进行调度进和过滤。它除了要实现请求路由,负载均衡、校验过滤等功能之外,还必须有其他能力,如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列较为高级的功能,这些都是后续学习的重点内容。

Spring Cloud基于Netflix Zuul开发了API网关组件Spring Cloud Zuul,用于解决上述两个较为普遍的问题:“路由规则与服务实例的维护”和“签名校验与登录校验的冗余”。那么问题来了,Spring Cloud Zuul是如何解决上述两个问题的?

对于第一个问题:“路由规则与服务实例的维护”,Spring Cloud Zuul通过与Spring Cloud Eureka整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这种设计充分将服务治理体系中维护的实例信息利用起来,同时将维护服务实例信息的工作交给了服务治理框架来自动完成,不需要人工介入。而对于路由规则的维护,Spring Cloud Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射,大部分情况下,这些默认的设置都可以实现我们路由需求,除了一些特殊情况(如兼容一些老的URL)外,还需要做一些特别的配置。但是这相比于之前架构下运维工作量,使用Spring Cloud Zuul实现API网关以后已经大大减少了很多。

对于第二个问题:“签名校验和登录校验的冗余”。其实从理论上来说,这些校验逻辑在本质上与微服务应用自身的业务并有太多的关系,因此完全可以将其独立成一个单独的服务而存在,只是它们被剥离和独立出来后,并不是像通俗的做法给各个微服务调用,而是在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。Spring Cloud Zuul提供了一套过滤器机制,它可以很友好的支持这样的任务。开发者可以通过使用Spring Cloud Zuul来创建各种校验过滤器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,否则就返回错误提示。通过这样的改造,各个业务层的微服务应用就不再需要非业务性质的校验逻辑了,这使得我们的微服务应用可以更专注于业务逻辑的开发,同时微服务的自动化测试也变得更容易实现。

快速入门

尽管微服务架构可以将我们的开发单元拆分的更为细致,有效的降低了开发难度,但是它所带来各种问题是需要解决的,且如果处理不当就会变成实施过程中的不稳定因素,甚至掩盖了原本实施微服务架构所带来的优势。鉴于此种情况,在微服务架构的实施方案中,API网关几乎都成了必用的组件。

前面说了那么一大堆的理论,光说不练假把式,接下来将通过一个简单的入门demo来学习Spring Cloud Zuul的相关知识,里面涉及到Spring Cloud Zuul的使用方法、属性配置以及一些不足之处和需要进行的思考。

构建网关

在实现各种API网关服务之前,我们需要进行一些准备工作,如构建几个最基本的API网关服务,且搭建几个用于路由和过滤使用的微服务应用。

前面使用的eureka-client(服务名称为hello-service),feign-consumer(服务名称为feign-consumer)其实都是微服务应用,且需要说明的是,尽管此前我们一直将feign-consumer(服务名称为feign-consumer)当做服务消费者,其实在Eureka的服务注册与发现体系中,每个服务既是提供者也是消费者,所以feign-consumer(服务名称为feign-consumer)其本质上也是一个服务提供者。(还记得之前我们访问诸如http://localhost:3001/feign-consumer3等一系列接口,这也能说明它就是一个服务提供者)。

同样为了和其他部分解耦和体现微服务的思想,这里选择新建一个Spring Boot工程,名称为api-gateway,添加相关依赖。

第一步,在Gitte上新建一个分支,名称为api-gateway,并将其pull到本地;

第二步,新建一个普通的SpringBoot工程,名称为api-gateway,注意使用默认的依赖。

第三步,添加项目依赖。Spring Boot工程创建完成后,修改pom.xml文件,添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>Hoxton.SR7</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
</dependencies>

请注意Spring Boot2.x系列中一些组件的依赖名称发生了变化,此处的Zuul所导入的依赖为spring-cloud-starter-netflix-zuul这个和Spring Boot1.x系列中完全不同。同时通过查看spring-cloud-starter-netflix-zuul这个依赖文件,可以发现其中包含以下的依赖:spring-boot-starter-webspring-boot-starter-actuatorspring-cloud-starter-netflix-hystrixspring-cloud-starter-netflix-ribbonspring-cloud-starter-netflix-archaiuszuul-core等。

解释一下上述各依赖的作用:spring-boot-starter-web表示提供Web服务;spring-boot-starter-actuator表示提供常规的微服务管理端点;spring-cloud-starter-netflix-hystrix用于在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,进而影响其他应用的对外服务;spring-cloud-starter-netflix-ribbon用于实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试;spring-cloud-starter-netflix-archaius用于从许多不同来源收集配置属性,并提供对配置信息的快速及线程安全访问;zuul-core它是Netflix Zuul的核心,用于提供API网关服务。同时Spring Cloud Zuul中还特别提供了/routes端点来返回当前的所有路由规则。


当然了也可以采用可视化的方式来创建该项目,创建过程如下所示:

第四步,在api-gateway项目入口类上添加启动注解。接下来在项目的入口类上添加@EnableZuulProxy注解,用于开启Zuul的API网关服务功能,如下所示:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}

第五步,属性配置。在api-gateway项目的application.yml配置文件中设置Zuul应用的基础信息,如应用名、服务端口号等信息,相应的配置为:

1
2
3
4
5
spring:
application:
name: api-gateway
server:
port: 3005

通过这样简单的五步操作,我们就使用Zuul完成了API网关服务的构建工作。