Spring CLoud Zuul使用 API 网关构建微服务
客户端与微服务直接通信
从理论上讲,客户端可以直接向每个微服务发送请求。每个微服务都有一个公开的端点(https ://.api.company.name)。该 URL 映射到微服务的负载均衡器,由后者负责在可用实例之间分发请求。为了获取产品详情,移动客户端将逐一向上文列出的 N 个服务发送请求。
遗憾的是,这种方法存在挑战和局限。问题之一是客户端需求和每个微服务暴露的细粒度 API 不匹配。在这个例子中,客户端需要发送 7 个独立请求。在更复杂的应用程序中,可能要发送更多的请求;按照 Amazon 的说法,他们在显示他们的产品页面时就调用了数百个服务。然而,客户端通过 LAN 发送许多请求,这在公网上可能会很低效,在移动网络上就根本不可行。这种方法还使得客户端代码非常复杂。
客户端直接调用微服务的另一个问题是,部分服务使用的协议对 web 并不友好。一个服务可能使用 Thrift 二进制 RPC,而另一个服务可能使用 AMQP 消息传递协议。不管哪种协议对于浏览器或防火墙都不够友好,最好是内部使用。在防火墙之外,应用程序应该使用诸如 HTTP 和 WebSocket 之类的协议。
这种方法的另一个缺点是,它会使得微服务难以重构。随着时间推移,我们可能想要更改系统拆分成服务的方式。例如,我们可能合并两个服务,或者将一个服务拆分成两个或更多服务。然而,如果客户端与微服务直接通信,那么执行这类重构就非常困难了。
由于上述三种问题的原因,客户端直接与服务器端通信的方式很少在实际中使用。
使用 API 网关构建微服务
通常来说,使用 API 网关是更好的解决方式。API 网关是一个服务器,也可以说是进入系统的唯一节点。这与面向对象设计模式中的 Facade 模式很像。API 网关封装内部系统的架构,并且提供 API 给各个客户端。它还可能还具备授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等功能。
API 网关负责服务请求路由、组合及协议转换。客户端的所有请求都首先经过 API 网关,然后由它将请求路由到合适的微服务。API 网关经常会通过调用多个微服务并合并结果来处理一个请求。它可以在 web 协议(如 HTTP 与 WebSocket)与内部使用的非 web 友好协议之间转换。
API 网关还能为每个客户端提供一个定制的 API。通常,它会向移动客户端暴露一个粗粒度的 API。以产品详情的场景为例,API 网关可以提供一个端点(/productdetails?productid=xxx),使移动客户端可以通过一个请求获取所有的产品详情。API 网关通过调用各个服务(产品信息、推荐、评论等等)并合并结果来处理请求
API 网关的优点和缺点
如你所料,使用 API 网关有优点也有不足。使用 API 网关的最大优点是,它封装了应用程序的内部结构。客户端只需要同网关交互,而不必调用特定的服务。API 网关为每一类客户端提供了特定的 API,这减少了客户端与应用程序间的交互次数,还简化了客户端代码。
API 网关也有一些不足。它增加了一个我们必须开发、部署和维护的高可用组件。还有一个风险是,API 网关变成了开发瓶颈。为了暴露每个微服务的端点,开发人员必须更新 API 网关。API网关的更新过程要尽可能地简单,这很重要;否则,为了更新网关,开发人员将不得不排队等待。不过,虽然有这些不足,但对于大多数现实世界的应用程序而言,使用 API 网关是合理的。
服务调用
基于微服务的应用程序是一个分布式系统,必须使用一种进程间通信机制。有两种类型的进程间通信机制可供选择。一种是使用异步的、基于消息传递的机制。有些实现使用诸如 JMS 或 AMQP 那样的消息代理,而其它的实现(如 Zeromq)则没有代理,服务间直接通信。
另一种进程间通信类型是诸如 HTTP 或 Thrift 那样的同步机制。通常,一个系统会同时使用异步和同步两种类型。它甚至还可能使用同一类型的多种实现。总之,API 网关需要支持多种通信机制。
服务发现
API 网关需要知道它与之通信的每个微服务的位置(IP 地址和端口)。在传统的应用程序中,或许可以硬连线这个位置,但在现代的、基于云的微服务应用程序中,这并不是一个容易解决的问题。基础设施服务(如消息代理)通常会有一个静态位置,可以通过 OS 环境变量指定。但是,确定一个应用程序服务的位置没有这么简单。应用程序服务的位置是动态分配的,而且,单个服务的一组实例也会随着自动扩展或升级而动态变化。
总之,像系统中的其它服务客户端一样,API 网关需要使用系统的服务发现机制,可以是服务器端发现,也可以是客户端发现。下一篇文章将更详细地描述服务发现。现在,需要注意的是,如果系统使用客户端发现,那么 API 网关必须能够查询服务注册中心,这是一个包含所有微服务实例及其位置的数据库。
处理局部失败
在实现 API 网关时,还需要处理局部失败的问题。该问题出现在所有的分布式系统中。当一个服务调用另一个服务,而后者响应慢或不可用的时候,就会出现这个问题。API 网关不能因为无限期地等待下游服务而阻塞。不过,如何处理失败取决于特定的场景以及哪个服务失败。例如,在产品详情场景下,如果推荐服务无响应,那么 API 网关应该向客户端返回产品详情的其它内容,因为它们对用户依然有用。推荐内容可以为空,也可以用一个固定的 TOP 10 列表取代。不过,如果产品信息服务无响应,那么 API 网关应该向客户端返回一个错误信息。
如果缓存数据可用,那么 API 网关还可以返回缓存数据。例如,鉴于产品价格不会频繁变动,如果价格服务不可用,API 网关可以返回缓存的价格数据。数据可以由 API 网关自己缓存,也可以存储在像 Redis 或 Memcached 之类的外部缓存中。通过返回默认数据或者缓存数据,API 网关可以确保系统故障不影响用户体验。
路由是微服务架构中必须(integral )的一部分,比如,“/”可能映射到你的WEB程序上,”/api/users “可能映射到你的用户服务上,“/api/shop”可能映射到你的商品服务商。(注解:我理解这里的这几个映射就是说通过Zuul这个网关把服务映射到不同的服务商去处理,从而变成了微服务!)
Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器.
Zuul功能:
· 认证
· 压力测试
· 金丝雀测试
· 动态路由
· 负载削减
· 安全
· 静态响应处理
· 主动/主动交换管理
Zuul的规则引擎允许通过任何JVM语言来编写规则和过滤器,支持基于Java和Groovy的构建。
配置属性 zuul.max.host.connections 已经被两个新的配置属性替代, zuul.host.maxTotalConnections (总连接数)和 zuul.host.maxPerRouteConnections,(每个路由连接数) 默认值分别是200和20.
一 . Zuul做反向代理
当一个UI应用调用一个或更多的后端服务的时候,我们可以用Spring Cloud创建一个Zuul代理减少开发是非常常见的例子。使用代理服务来避免必须的跨域资源共享(Cross-Origin Resource Sharing)和所有的后端需要分别认证的问题。
在Spring Boot主函数上通过注解 @EnableZuulProxy 来开启,这样可以让本地的请求转发到适当的服务.按照约定,一个ID为"users"的服务会收到 /users 请求路径的代理请求(前缀会被剥离). Zuul使用Ribbon定位服务注册中的实例,并且所有的请求都在hystrix的command中执行,所以失败信息将会展现在Hystrix metrics中,并且一旦断路器打开,代理请求将不会尝试去链接服务.
Zuul starter没有包含服务发现的客户端,所以对于路由你需要在classpath中提供一个根据service IDs做服务发现的服务.(例如, eureka是一个不错的选择)
总结
对于大多数基于微服务的应用程序而言,实现 API 网关,将其作为系统的唯一入口很有必要。API 网关负责服务请求路由、组合及协议转换。它为每个应用程序客户端提供一个定制的 API。API 网关还可以通过返回缓存数据或默认数据屏蔽后端服务失败。在本系列的下一篇文章中,我们将探讨服务间通信。
项目结构如下:注意以bank结尾的是不同的demo
EurekaApplication类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication //开启启动程序入口类
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
application.yml配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
spring-cloud-user:
path: /user/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization #表示进行HTTPS请求时,忽视Header敏感信息传递到下游服务器
url: https://downstream
# ignore-security-headers 是否拦截Header
application.yml.bank1配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
ignoredServices: '*' #忽视所有服务,只为以下routes服务进行反向代理,或者直接配上想忽视的服务,多个用逗号隔开
routes:
spring-cloud-user: /user-path/** # 一个*号只能匹配一个级别的路径,比如只能访问:/user-path/order 而两个*表示可以匹配多个层级,例如项目访问路径为:http://loaclhost:8084/user-path/user/1
application.yml.bank2配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
users: #此处可以随便定义一个,只要唯一就行
path: /user-path/** # 一个*号只能匹配一个级别的路径,比如只能访问:/user-path/order 而两个*表示可以匹配多个层级,例如项目访问路径为:http://loaclhost:8084/user-path/user/1
serviceId: spring-cloud-user #反向代理到这个服务
application.yml.bank3配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
users: #此处可以随便定义一个,只要唯一就行
path: /user-path/** # 一个*号只能匹配一个级别的路径,比如只能访问:/user-path/order 而两个*表示可以匹配多个层级,例如项目访问路径为:http://loaclhost:8084/user-path/user/1
url: http://localhost:7900/ #此处采用的是URL配置,但是不能进行负载均衡
application.yml.bank4配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
#节点要想进行负载均衡,需要配置以下信息
zuul:
routes:
users: #此处可以随便定义一个,只要唯一就行
path: /user-path/** # 一个*号只能匹配一个级别的路径,比如只能访问:/user-path/order 而两个*表示可以匹配多个层级,例如项目访问路径为:http://loaclhost:8084/user-path/user/1
serviceId: spring-cloud-user
ribbon:
eureka:
enabled: false #禁用eureka
spring-cloud-user: #这里是ribbon要请求的微服务的serviceId
ribbon:
listOfServers: example.com,google.com
application.yml.bank5配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
#以下访问路径为:http://localhost:8040/user/spring-cloud-user/1
zuul:
prefix: /user #前缀:这里表示用户微服务的路径的前缀
stripPrefix: false
logging:
level:
com.netflix: debug
application.yml.bank6配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
ignoredPatterns: /**/admin/** #忽视某些敏感路径,带有admin的路径
routes:
spring-cloud-user: /user/** #只请求spring-cloud-user微服务
application.yml.bank7配置
server:
port: 8040
spring:
application:
name: spring-cloud-zuul-api-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
#以下配置表示用户微服务路由的是/user/** ,剩下的是路由到/**
zuul:
routes:
spring-cloud-user:
path: /user/**
legacy:
path: /**
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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.demo</groupId>
<artifactId>spring-cloud-zuul-api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-cloud-zuul-api-gateway</name>
<description>spring-cloud-zuul-api-gateway</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<properties>
<!-- 文件拷贝时的编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 编译时的编码 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- jdk版本 -->
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<!-- 注册中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 用于热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- 版本依赖管理,故之后添加依赖无需指定version -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 用以为integration-test提供支持。 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动:Spring-cloud-eureka-server->spring-cloud-user->spring-cloud-zuul-api-gateway