什么是服务网关
API Gateway(API网关)是一个用于管理、调度和保护微服务架构中的API(Application Programming Interface)的服务器。API网关充当客户端和后端服务之间的中介,提供一种集中式的入口点,用于处理从客户端到各个微服务的请求和响应。以下是API网关的主要功能和特点:
- 路由和调度: API网关负责将客户端请求路由到相应的后端服务。它可以基于路径、域名或其他规则进行路由,并将请求分发给相应的微服务。
- 负载均衡: API网关可以实施负载均衡策略,确保后端服务的请求得到平均分配,提高系统的可伸缩性和性能。
- 安全性: API网关提供了安全性的特性,包括身份验证、授权、加密和防火墙等。它可以验证请求的身份,并确保只有授权的用户或系统可以访问后端服务。
- 请求转换: API网关可以处理和转换客户端请求和后端服务的响应,以适应不同的协议、数据格式或版本。
- 监控和分析: API网关提供了监控和分析功能,用于跟踪请求和响应的性能、错误率和其他指标。这对于系统的健康和故障排除非常重要。
- 缓存: API网关可以实施缓存策略,缓存常见的请求和响应,减轻后端服务的压力,提高响应速度。
- 限流和配额: API网关可以实施限流和配额控制,防止滥用和过度使用后端服务资源。
- API文档和发现: API网关可以提供API文档和发现服务,使开发者能够了解可用的API和如何使用它们。
- 服务发现: 在微服务架构中,API网关可以与服务发现工具集成,动态地发现和管理后端服务的实例。
一些流行的API网关包括NGINX, Kong, Apigee, AWS API Gateway等。API网关在微服务架构中起到了重要的作用,简化了前端和后端之间的通信和协作,提高了系统的可维护性、扩展性和安全性。
举个实际的例子,我们为什么要使用网关?
服务网关解决了什么问题?
什么是Zuul
Zuul是从设备和网站到应用程序后端的所有请求的前置。作为边缘服务应用程序,它主要用于处理微服务架构中的请求路由、负载均衡、服务过滤和服务发现等功能。Zuul最初是为Netflix的基于云的架构而设计的,但现在已经成为一个开源项目,广泛应用于各种微服务架构中。
以下是Zuul的一些主要特点和功能:
- 请求路由: Zuul可以根据请求的URL将流量路由到相应的微服务。这使得微服务架构中的各个服务可以通过一个中心入口点提供服务。
- 负载均衡: Zuul支持负载均衡(Ribbon),可以将请求分发到多个相同功能的微服务实例中,以确保高可用性和性能。
- 服务过滤: Zuul允许定义一系列过滤器,可以在请求和响应的不同阶段执行,实现例如身份验证、日志记录、性能监控等功能。
- 服务降级: (Hystrix/Sentinel)当后端微服务不可用时,Zuul可以提供一些默认的响应或转发到备用服务,以防止整个系统的故障。
- 动态路由: Zuul支持动态路由配置(2.x版本才支持),可以根据运行时的条件动态调整路由规则,而不需要重启Zuul服务。
- 服务发现: Zuul可以集成服务注册与发现机制,与服务发现工具(如Eureka)协同工作,以自动获取可用的微服务实例。
- 安全性: Zuul可以通过配置安全规则,例如基于路径的访问控制、HTTPS支持等,来保护系统的安全。
- 易于扩展: Zuul是一个可插拔的架构,可以通过定义自定义过滤器和路由规则来扩展其功能。
在微服务架构中,Zuul扮演着一个重要的角色,帮助开发者实现了一些常见的跨服务的功能,同时提供了一些重要的运维和安全性特性。值得注意的是,Zuul 1.x版本是基于Servlet技术的,而Zuul 2.x及之后版本是基于Netty的异步非阻塞架构。
Zuul实现API网关入门案例
首先引入zuul的spring依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
一个注解开启zuul
@EnableZuulProxy
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DemoApplication.class)
.listeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {
}).run(args);
}
}
路由配置参数
## 路径前缀
zuul.routes.demo-product.path=/dome-product/**
## 转发路由地址
zuul.routes.demo-product.url=http://localhost:8089
## 转发时是否去掉前缀
zuul.routes.demo-product.stripPrefix=false
## --------------------------------------------------
## 如果用到注册中心(如Eureka)可以指定这个参数
zuul.routes.demo-product.serviceId=demo-product
## 如果只是简单的实现上面的功能则所有的都无需配置即可默认支持(约定大于配置)
## --------------------------------------------------
## 移除内置的三个敏感参数"Cookie", "Set-Cookie", "Authorization"
zuul.sensitiveHeaders=
## 排除包含actuator、error的所有路径(禁止访问的意思)
zuul.ignored-patterns=/**/error/**,/**/actuator/**
## 排除服务(禁止访问的意思),如果是*代表所有
zuul.ignored-services=demo-product1
## 路由前缀,等于访问的路径前面加这一串如:
## 之前是http://localhost:8080/dome-product/
## 现在之前是http://localhost:8080/api/dome-product/
zuul.prefix=/api
## --------------------------------------------------
spring.application.name=demo-product
## eureka注册配置
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${server.port}
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
## --------------------------------------------------
#熔断超时时间配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=16000
#zuul超时配置,路由规则是url才生效
zuul.host.connect-timeout-millis=17000(连接超时)
zuul.host.socket-timeout-millis=60000(请求超时)
#负载均衡超时时间配置,路由规则是注册中心ID生效,此处生效
#总的超时时间 = (1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout
ribbon.ReadTimeout=5000
ribbon.ConnectTimeout=5000
Zuul过滤器
-
Filter(过滤器): 过滤器是Zuul中的基本构建块,用于执行特定类型的过滤逻辑。Zuul的请求处理过程中,会经过一系列的过滤器,包括前置过滤器、路由过滤器、后置过滤器等。
-
Filter Type(过滤器类型): 过滤器类型定义了过滤器的执行时机,包括"pre"(前置过滤器)、“route”(路由过滤器)、“post”(后置过滤器)、“error”(错误过滤器)。
-
Filter Order(过滤器顺序): 过滤器顺序决定了过滤器的执行顺序。具有相同类型的过滤器会根据过滤器顺序从小到大执行。
-
Should Filter(是否执行过滤器):
shouldFilter
方法返回一个布尔值,用于决定是否执行当前过滤器。如果返回true
,则执行过滤器的run
方法;如果返回false
,则跳过该过滤器。 -
Request Context(请求上下文): 请求上下文是Zuul中的一个对象,包含了当前请求的信息和状态。过滤器可以通过请求上下文获取请求和响应等信息,并对其进行修改。
-
Filter Result(过滤器结果): 过滤器的
run
方法的返回值。用于控制是否继续执行后续的过滤器。返回null
表示继续执行后续过滤器,返回new FilterResult().setShouldFilter(true)
表示继续执行,返回new FilterResult().setShouldFilter(false)
表示中止过滤器链。
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
public class FtdZuulFilter extends AbstractRouteFilter {
public FtdZuulFilter(RouteLocator routeLocator, UrlPathHelper urlPathHelper) {
super(routeLocator, urlPathHelper);
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// Zuul上下文
RequestContext ctx = RequestContext.getCurrentContext();
// 获取目标服务名
Route route = route(ctx.getRequest()); // http://localhost:8080/lingxi/getUser
String serviceId = route.getId(); //lingxi
// 判断用户是否登录
String currentUserName = UserInfoHolder.getUsername();
if (currentUserName == null) {
throw new ZuulException("未登录", 401, "未登录");
}
// 获取用户名信息
if (StringUtils.equals(serviceId, "demo-product")) {
ctx.addZuulRequestHeader("UserInfo", currentUserName);
ctx.addZuulRequestHeader("Token", "saas");
ctx.addZuulRequestHeader("Authorization", "Bearer 123123213" );
}
//如果 run() 方法返回 null,表示该过滤器逻辑执行成功,将继续执行后续的过滤器(如果有的话)。
return null; // 或者 return new FilterResult().setShouldFilter(true);
}
protected Route route(HttpServletRequest request) {
String requestURI = urlPathHelper.getPathWithinApplication(request);
return routeLocator.getMatchingRoute(requestURI);
}
}
Zuul Error过滤器
如果需要定义自己的异常返回,那使用下面的示例统一进行处理。
package com.demo.web.gateway.filter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;
import java.io.IOException;
import java.io.PrintWriter;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_ERROR_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE;
@Component
public class ErrorZuulFilter extends AbstractRouteFilter {
public ErrorZuulFilter(RouteLocator routeLocator, UrlPathHelper urlPathHelper) {
super(routeLocator, urlPathHelper);
}
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER-1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setResponseStatusCode(HttpStatus.EXPECTATION_FAILED.value());
PrintWriter writer = null;
try {
writer = ctx.getResponse().getWriter();
writer.println("Is Cookie Error!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer != null){
writer.close();
}
}
return null;
}
}
另外:如果没有请求到自己的异常类则要加一下这个参数,排除掉Zuul自带的异常过滤器SendErrorFilter
zuul.SendErrorFilter.error.disable=true
Zuul请求生命周期
1、HTTP客户端发送请求到Zuul网关
2、Zuul网关首先经过Pre filter
3、验证通过后进入Routing filter,接着将请求转发给远端服务,远端服务执行后返回结果,如果出错则Error filter
4、继续执行Post filter
5、最后返回响应给HTTP客户端
Zuul接入Sentinel限流
Zuul本身自带Hystrix限流器,我们优先接入Sentinel限流,MSF中已经带了雨Sentinel:msf-spring-boot-starter-sentinel
依赖包,引入即可,对Zuul的Sentinel过滤器提交给Spring托管【Sentinel里面的默认实现】。
@Configuration
public class GatewayConfig {
@Bean
public ZuulFilter sentinelPreFilter() {
return new com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter();
}
@Bean
public ZuulFilter sentinelPostFilter() {
return new com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter();
}
@Bean
public ZuulFilter sentinelErrorFilter() {
return new com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter();
}
}
测试效果:
总结
总的来说,Zuul是一个强大而小巧灵活的API网关,为构建和管理微服务架构中的API提供了一套全面的解决方案。主要组件有路由器、过滤器和限流器。缺点是在 Zuul 1.x 版本中,动态添加新路由是相对困难的,因为在启动时就需要定义路由规则,而后期动态修改这些规则较为复杂,通常需要通过重启 Zuul 服务来应用新的配置。
然而,在 Zuul 2.x 及之后的版本中,支持动态路由的能力得到了显著改进。Zuul 2.x 使用了基于 Netty 的异步非阻塞架构,允许在运行时动态修改路由规则而无需重启整个服务。这使得可以更灵活地适应不同的需求,例如微服务的动态变化、新服务的添加等。
Zuul(Netflix开源的版本)并没有被维护的情况。然而,需要注意的是,Netflix已经在一段时间内将注意力转移到了新的网关项目Spring Cloud Gateway上。
Spring Cloud Gateway是Spring Cloud生态系统中的一个组件,用于构建基于Spring Boot的微服务架构的网关服务。相比于Zuul 1.x版本,Spring Cloud Gateway采用了Reactor和WebFlux,支持反应式编程,提供更好的性能和可伸缩性。由于Spring Cloud Gateway是Spring Cloud生态系统的一部分,因此与Spring Boot以及其他Spring Cloud组件更好地集成,具有更现代化的架构。
因此,一些团队和开发者可能更愿意选择Spring Cloud Gateway,而不是继续使用Zuul,特别是在构建新的微服务架构时。然而,这并不意味着Zuul就不再使用或维护了,而是取决于具体的项目需求和技术栈选择。
在选择API网关时,需要根据项目的需求、团队的经验以及生态系统的集成性等因素进行权衡。如果你正在考虑使用API网关,建议查看最新的技术趋势和生态系统的发展,以便做出符合项目需求的选择。