在没有服务网关时,请求流程图大致如下:
客户端直接与各个微服务通信,会有以下问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,大多数情况下,为了保证对外服务的安全性,我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,比如对用户登录状态的校验等;同时为了防止客户端在发起请求时被篡改等安全方面的考虑,还会有一些签名校验的机制存在。这时候,由于使用了微服务架构的理念,我们将原本处于一个应用中的多个模块拆成了多个应用,但是这些应用提供的接口都需要这些校验逻辑,不得不在这些应用中都实现这样一套校验逻辑。
- 难以重构,随着项目的迭代可能需要重新划分微服务。
为了解决上面这些常见的架构问题,API网关的概念应用而生。API网关是一个更为智能的应用服务器,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、负载均衡、校验过滤等功能之外,还拥有更多的能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。
引入服务网关后,请求流程大致如下:
Spring Cloud中提供了基于Netflix Zuul实现的API网关组件-------spring cloud zuul.
首先,对于路由规则与服务实例的维护问题。Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为eureka服务治理下的应用,同时从Eureka中获取所有其他微服务的实例信息,使得维护服务实例的工作交给了服务治理框架自动完成,不需要人工介入。而对于路由规则的维护,Zuul默认会通过以服务名作为ContextPath的方式来创建路由映射,大部分情况下,这样默认的设置已经可以实现大部分的路由需求,除了一些特殊情况(兼容一些老的URL)还需做一些特殊的配置。
对于类似签名校验、登录校验在微服务架构中的冗余问题。理论上来说,这些校验逻辑在本质上与微服务应用自身的业务并没有多大的关系,所以它们可以独立成一个单独的服务存在,只是它们被剥离和独立出来之后并不是给各个微服务调用,而是在API网关上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和检验.Spring Cloud Zuul提供了一套过滤机制,可以很好的支持。开发者可以通过使用Zuul来创建各种校验过滤器,然后指定那些规则的请求需要执行校验逻辑只有通过校验的才会被路由到具体的微服务接口,不然返回错误提示。通过这样的该着各个业务的微服务应用就不再需要非业务的校验逻辑了。
构建Zuul项目
现在的项目流程示意图如下:
pom.xml
| <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>905.spring-cloud-zuul-api-gateway</groupId> <artifactId>api-gateway</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud Maven Webapp</name> <url>http://maven.apache.org</url> <!--springboot采用1.5.x 对应springcloud版本为 Dalston --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </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>Dalston.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!--引入zuul --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </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> <!-- 这样变成可执行的jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
启动类
| package com.niugang; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * zuul启动类 * * @author niugang * */ @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker //可以直接用@SpringCloudApplication注解,该注解包含上面三个注解 /* *设置一个Zuul服务器端点并在其中安装一些反向代理过滤器,这样它就可以将请求转发给后端服务器 */ @EnableZuulProxy public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } |
配置
#指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问 |
测试
启动注册中心,启动服务提供者,消费者,api网关;如下
启动zuul项目时对spring-cloud-netflix-core.jar中ZuulProperties类打断点调试
| @PostConstruct public void init() { for (Entry<String, ZuulRoute> entry : this.routes.entrySet()) { ZuulRoute value = entry.getValue(); if (!StringUtils.hasText(value.getLocation())) { value.serviceId = entry.getKey(); } if (!StringUtils.hasText(value.getId())) { value.id = entry.getKey(); } if (!StringUtils.hasText(value.getPath())) { value.path = "/" + entry.getKey() + "/**"; } } } |
可以看到entry(键值对为)
api-a=ZuulProperties.ZuulRoute(id=null, path=/api-a/**, serviceId=feign-consumer, url=null, stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false)
|
ZuulProperties.class中部分代码,这类源码可以好好看下 public static class ZuulRoute { /*** The ID of the route (the same as its map key by default). */ private String id; /** * The path (pattern) for the route, e.g. /foo/**. * 路径模式 */private String path; /** * The service ID (if any) to map to this route. You can specify a physical URL or * a service, but not both. *服务ID(如果有的话)映射到这条路由。您可以指定物理URL或服务,但不能同时指定两者。 */private String serviceId; /** * A full physical URL to map to the route. An alternative is to use a service ID * and service discovery to find the physical address. *一个完整的物理URL,以映射到路由。另一种方法是使用服务ID和服务发现来查找物理地址。(兼容老项目的配置url路由方式) #zuul.routes.api-url.url=http://localhost:9001/ private String url; |
输入:http://localhost:8777/api-a/feign-consumer
api网关会自动根据配置跳转到
http://feign-consumer/feign-consumer 红色标注为服务名