您的位置:首页 > 编程语言 > Java开发

SpringCloud微服务知识整理七:API网关服务:Spring Cloud Zuul

2019-01-15 15:35 736 查看

通过前几章形成的微服务基础架构:

在该架构中,我们的服务集群包含内部服务ServiceA和ServiceB, 它们都会向Eureka Server集群进行注册与订阅服务,而OpenService是一个对外的RESTfulAPI服务,它通过FS、 Nginx等网络设备或工具软件实现对各个微服务的路由与负载均衡,并公开给外部的客户端调用。

什么是API网关服务:Spring Cloud Zuul

API网关是一个更为智能的应用服务器,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、 负载均衡、 校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。

Spring Cloud Zuul既具备路由转发功能,又具备过滤器功能。

注意:另外有Spring Cloud Gateway也可实现网关功能。

一、快速入门

1.构建网关
新建一个SpringBoot项目,这里命名api-gateway,然后导入相关依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

主类:

@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class SpringcloudzuulApplication {

public static void main(String[] args) {
SpringApplication.run(SpringcloudzuulApplication.class, args);
}
}

配置:

spring.application.name=api-gateway
server.port=5555

2.请求路由
传统路由:
增加配置:

zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:8080/

面向服务路由:

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service

zuul.routes.api-b.path=/api-a/**
zuul.routes.api-b.serviceId=hello-service

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

3.请求过滤
例子:检查是否有accessToken参数
过滤器类:

public class AccessFilter extends ZuulFilter {

private static Logger log = LoggerFactory.getLogger(AccessFilter.class);

@Override
public String filterType() {
return "pre";
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to{}", request.getMethod(), request.getRequestURL().toString());
Object accessToken = request.getParameter("accessToken");
if (accessToken == null) {
log.warn("accessToken is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("accessToken is empty");
} catch (Exception e) {
}
return null;
}
log.info("access is ok");
return null;
}
}

filterType: 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
filterOrder: 过滤器的执行顺序。
shouldFilter: 判断该过滤器是否需要被执行。
run: 过滤器的具体逻辑。

在主类中增加:

@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}

总结一下API网关:
它作为系统的统一入口,屏蔽了系统内部各个微服务的细节
可以和服务治理框架结合,实现自动化服务实例维护和负载均衡
实现接口权限校验与微服务业务逻辑的解耦
通过网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外的服务层做的校验前移,保证微服务的无状态性,降低测试难度,解放程序员专注业务的处理

二、路由详解

1.传统路由配置

单实例配置:
通过zuul.routes.<路由名>.path与zuul.routes.<路由名>.url
多实例配置:
通过zuul.routes.<路由名>.path与zuul.routes.<路由名>.serviceId

传统路由方式都需要手动为每一对映射指定一个名称,每个<路由名>对应了一条路由规则;每条路由规则都必须有一个path用来匹配请求路径表达式,并通过与之相对应的url或serviceId属性来请求表达式映射的url或服务名

2.服务路由配置

通过zuul.routes.<路由名>.path与zuul.routes.<路由名>.serviceId成对配置
简洁方式:zuul.routes.<服务名>=<映射地址>

zuul.routes.user-service=/user-service/**

当有外部请求到达API网关的时候,根据请求的URL路径去匹配path的规则,通过path找到路由名,去找对应的serviceId的服务名。

传统路由就会去根据这个服务名去找listOfServers参数,从而进行负载均衡和请求转发。
面向服务路由会从注册到服务治理框架中取出服务实例清单,通过清单直接找到对应的实例地址清单,从而通过Ribbon进行负载均衡选取实例进行路由(请求转发)。

3.服务路由的默认规则

Zuul在注册到Eureka服务中心之后,它会为Eureka中的每个服务都创建一个默认的路由规则,默认规则的path会使用serviceId配置的服务名作为请求前缀。
我们可以使用zuul.ignored-services参数来设置一个不自动创建该服务的默认路由。

4.自定义路由映射规则

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}

5、路径匹配

为路由规则定义匹配表达式-path参数
path通常需要使用通配符:

如果有一个可以同时满足多个path的匹配的情况,此时匹配结果取决于路由规则的定义顺序。
这里需要注意的是:properties无法保证路由规则的顺序,推荐使用yml格式配置文件

6.忽略表达式

Zuul提供了用于忽略路径表达式的参数zuul.ignored-patterns。使用该参数可以用来设置不希望被API网关进行路由的URL表达式。

zuul.ignored-patterns: /**/hello/**

7.路由前缀

为了方便全局为路由path增加前缀信息,Zuul提供了zuul.prefix参数来进行设置。
代理前缀会从默认路径中移除掉,可以使用zuul.stripPrefix=false 来关闭移除代理前缀的动作,也可以通过zuul.routes.<路由名>.strip-prefix=false来指定服务关闭移除代理前缀的动作。

8.本地跳转

Zuul代理是一个有用的工具,因为可以使用它来处理来自旧端点客户端的所有流量,但会将某些请求重定向到新端点。

zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.url=forward:/local

9.Cookie与头信息

默认情况下,Zuul在请求路由时会过滤掉HTTP请求头信息中的一些敏感信息,防止这些敏感的头信息传递到下游外部服务器。
可以通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性来使Cookie可以被传递。

全局放行:

zuul.sensitiveHeaders=

指定路由名放行(推荐使用):

zuul.routes.<router>.customSensitiveHeaders=true
zuul.routes.<router>.sensitiveHeaders=

10.Hystrix和Ribbon支持

Zuul中包含了Hystrix和Ribbon的依赖,所以Zuul拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡。
1.设置Hystrix超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
2.设置Ribbon连接超时时间
ribbon.ConnectTimeout
3. 设置Ribbon的请求转发超时时间
ribbon.ReadTimeout
4.关闭重试配置
全局配置: zuul.retryable=false
针对路由配置: zuul.routes.<路由名>.retryable=false

三、过滤器详解

1.过滤器

在Spring Cloud Zuul 中实现过滤器必须包含4 个基本特征:过滤类型、执行顺序、执行条件、具体操作。实际上就是ZuulFilter抽象类中定义的抽象方法:
String filterType();
int filterOrder();
boolean shouldFilter();
Object run();

filterType:该方法需要返回一个字符串来代表过滤器的类型,而这个类型就是Zuul中的4种不同生命周期的过滤器类型,如下
pre:在请求到达路由前被调用
route:在路由请求时被调用
error: 处理请求时发生的错误时被调用。
post:在route和error过滤器之后被调用,最后调用。

filterOrder:通过int值定义过滤器执行顺序,数值越小优先级越高。

shouldFilter:返回布尔值来判断该过滤器是否执行。

run:过滤器的具体逻辑。可以在此确定是否拦截当前请求等。

2.请求生命周期


HTTP请求到达Zuul,最先来到pre过滤器,在这里会去映射url patern到目标地址上,
然后将请求与找到的地址交给route类型的过滤器进行求转发,请求服务实例获取响应,
通过post类型过滤器对处理结果进行加工与转换等操作返回。
error类型的过滤器比较特殊,在这整个请求过程中只要有异常才会触发,将异常结果交给post类型过滤器加工返回。

3.异常处理

1.严格的try-catch处理

public class ThrowExceptionFilter extends ZuulFilter {

private static Logger log = Logger.getLogger(ThrowExceptionFilter.class);

@Override
public String filterType() {
return "pre";
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
log.info("This is a pre filter, it will throw a RuntimeException");
RequestContext ctx = RequestContext.getCurrentContext();
try {
doSomething();
} catch (Exception e) {
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception", e);
}
return null;
}

}

2.ErrorFilter处理

public class ErrorFilter extends ZuulFilter {

Logger log = Logger.getLogger(ErrorFilter.class);

@Override
public String filterType() {
return "error";
}

@Override
public int filterOrder() {
return 10;
}

@Override
public boolean shoul
1b024
dFilter() {
return true;
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
log.error("this is a ErrorFilter :" + throwable.getCause().getMessage(), throwable);
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception", throwable.getCause());
return null;
}

}

3.不足优化
问题:error类型的过滤器处理完毕之后,除了来自post阶段的异常之外,都会再被post过滤器进行处理。而对于从post过滤器中抛出异常的情况,在经过了error过滤器处理之后,就没有其他类型的过滤器来接手了。

解决:需要在ErrorFilter过滤器之后再定义一个error类型的过滤器,让它来实现SendErrorFilter的功能,但是这个error过滤器并不需要处理所有出现异常的情况,它仅对post过滤器抛出的异常才有效。
判断引起异常的过滤器是来自什么阶段:
扩展processZuulFilter(ZuulFilter filter)方法,当过滤器执行抛出异常的时候,捕获它,并往请求上下中记录一些信息。

public class DidiFilterProcessor extends FilterProcessor {

@Override
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
try {
return super.processZuulFilter(filter);
} catch (ZuulException e) {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("failed.filter", filter);
throw e;
}
}

}

完善ErrorExtFilter

@Component
public class ErrorExtFilter extends SendErrorFilter {

@Override
public String filterType() {
return "error";
}

@Override
public int filterOrder() {
return 30; // 大于ErrorFilter的值
}

@Override
public boolean shouldFilter() {
// 判断:仅处理来自post过滤器引起的异常
RequestContext ctx = RequestContext.getCurrentContext();
ZuulFilter failedFilter = (ZuulFilter) ctx.get("failed.filter");
if (failedFilter != null && failedFilter.filterType().equals("post")) {
return true;
}
return false;
}

}

在应用主类中,通过调用FilterProcessor.setProcessor(new DidiFilterProcessor());方法来启用自定义的核心处理器。

4.禁用过滤器

zuul.<过滤器名>.<过滤器类型>.disable=true

四、动态加载

在微服务中,API网关担负着外部统一入口的重任,与其他服务不同,它必须保证不关闭不停机,从而确保整个系统的对外服务。所以我们就不能关机改东西再上线,这样会影响到其它服务。Zuul实现了不关机状态下的动态路由和动态添加/删除过滤器等功能。

需要和下一章Spring Cloud Config 组合起来,起到动态刷新配置路由的功能,学习下一章后再学习这部分

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: