通过之前的内容,我们已经可以搭建一个基于spring cloud分布式应用。
在传统的网站中,我们还会也引入如Nginx
、F5
的网关功能。网关的功能对于分布式网站是十分重要的,首先它可以将请求路由到真实的服务器上,进而保护真是服务器的IP地址,避免直接地攻击真是服务器;其次它也可以作为一种负载均衡的手段,使得请求按照以定的算法平摊到多个节点上,减缓单点的压力;最后,它还能提供过滤器,过滤器的使用可以判定请求是否为有效请求,一旦判定失败,就可以将请求阻止,避免发送到真是服务器,这样就能够降低真是服务器的压力。
前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个微服务框架已经完成了。
我们还是少考虑了一个问题,外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
在微服务架构模式下后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。
通常而言不同的客户端对于显示时对于数据的需求是不一致的,比如手机端或者Web端又或者在低延迟的网络环境或者高延迟的网络环境。
因此为了优化客户端的使用体验,API Gateway可以对通用性的响应数据进行裁剪以适应不同客户端的使用需求。同时还可以将多个API调用逻辑进行聚合,从而减少客户端的请求数,优化客户端用户体验
当然我们还可以针对不同的渠道和客户端提供不同的API Gateway,对于该模式的使用由另外一个大家熟知的方式叫Backend for front-end, 在Backend for front-end模式当中,我们可以针对不同的客户端分别创建其BFF,进一步了解BFF可以参考这篇文章:Pattern: Backends For Frontends
对于系统而言进行微服务改造通常是由于原有的系统存在或多或少的问题,比如技术债务,代码质量,可维护性,可扩展性等等。API Gateway的模式同样适用于这一类遗留系统的改造,通过微服务化的改造逐步实现对原有系统中的问题的修复,从而提升对于原有业务响应力的提升。通过引入抽象层,逐步使用新的实现替换旧的实现。
在Spring Cloud体系中, Spring Cloud Zuul就是提供负载均衡、反向代理、权限认证的一个API gateway。
新建一个微服务zuul工程
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
package com.lay.zuul;
@SpringBootApplication
//启动zuul代理功能
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
启动类添加@EnableZuulProxy
,支持网关路由。
这里再深入看下注解@EnableZuulProxy
的源码
package org.springframework.cloud.netflix.zuul;
@EnableCircuitBreaker
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({ZuulProxyMarkerConfiguration.class})
public @interface EnableZuulProxy {
}
可以看到zuul
已经引入了@EnableCircuitBreaker
短路机制,之所以引入断路机制,是因为再请求不到的时候,会进行短路,以避免网关发生请求无法释放的场景,导致微服务瘫痪。
# 服务端口
server.port=80
# spring应用名称
spring.application.name=zuul
# 注册给服务治理中心
eureka.client.service-url.defaultZone=http://localhost:7001/eureka,http://localhost:7002/eureka
# 设置等待时间,要大于断路器等待时间,不然会ReadTimeOut异常
zuul.host.connect-timeout-millis=10000
zuul.host.socket-timeout-millis=60000
上述代码中,使用了80端口启动Zuul,再浏览器中这个端口是默认端口,因此再地址栏中不需要显示输入,而Spring应用名称则为zuul。如果我们启动了用户微服务,在地址栏中输入http://localhost/user/timeout,就可以访问到用户微服务控制器的timeout
方法了。
这是因为,localhost代表的是请求zuul服务,因为采用的是默认的80端口,所以浏览器中可以不给出端口,而在/user/timeout
中,user
代表的用户微服务ID(Service ID),而timeout
是请求路径,这样zuul就会将请求转发到用户微服务。同理,我们也可以请求产品微服务,例如在浏览器中输入http://localhost/product/product/ribbon。
除此之外,Zuul也允许配置请求映射,在zuul中有面向传统网关的配置方式,也有面向服务的配置方式。为了演示着两种方式,我们在application.properties中增加如下配置
# 服务端口
server.port=80
# spring应用名称
spring.application.name=zuul
#用户微服务映射规则
# 指定ANT风格的URL匹配
zuul.routes.user-service.path=/u/**
# 映射用户服务中心服务ID,zuul会自动使用服务端负载均衡
zuul.routes.user-service.service-id=user
# 产品微服务映射规则
zuul.routes.product-service.path=/p/**
# 指定映射的服务产品地址,这样zuul就会将请求转发到产品的微服务上
zuul.routes.product-service.url=http://localhost:9001/
# 注册给服务治理中心
eureka.client.service-url.defaultZone=http://localhost:7001/eureka,http://localhost:7002/eureka
# 设置等待时间,要大于断路器等待时间,不然会ReadTimeOut异常
zuul.host.connect-timeout-millis=10000
zuul.host.socket-timeout-millis=60000
我们看到配置将zuul网关注册给服务治理中心,这样它就能够获取各个微服务的服务ID了。
其中产品微服务是指定服务产品具体单个节点,而用户微服务则会从服务治理中心中获取节点进行负载均衡。