第二十一章 Spring CLoud Zuul使用 API 网关构建微服务

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这个网关把服务映射到不同的服务商去处理,从而变成了微服务!)

ZuulNetflix出品的一个基于JVM路由和服务端的负载均衡器.

Zuul功能:

· 认证

· 压力测试

· 金丝雀测试

· 动态路由

· 负载削减

· 安全

· 静态响应处理

· 主动/主动交换管理

Zuul的规则引擎允许通过任何JVM语言来编写规则和过滤器,支持基于JavaGroovy的构建。

配置属性 zuul.max.host.connections 已经被两个新的配置属性替代zuul.host.maxTotalConnections (总连接数)和 zuul.host.maxPerRouteConnections,(每个路由连接数) 默认值分别是20020.

. Zuul做反向代理

  当一个UI应用调用一个或更多的后端服务的时候,我们可以用Spring Cloud创建一个Zuul代理减少开发是非常常见的例子。使用代理服务来避免必须的跨域资源共享(Cross-Origin Resource Sharing)和所有的后端需要分别认证的问题。

  在Spring Boot主函数上通过注解 @EnableZuulProxy 来开启,这样可以让本地的请求转发到适当的服务.按照约定,一个ID"users"的服务会收到 /users 请求路径的代理请求(前缀会被剥离). Zuul使用Ribbon定位服务注册中的实例,并且所有的请求都在hystrixcommand中执行,所以失败信息将会展现在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










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值