API服务网关(Zuul)
2021-06-09 05:02
标签:lap 权限控制 创建 service 做了 版本号 als 场景 framework 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢。 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问题的,但出于种种原因,这并不是一个好的选择。 让客户端直接与各个微服务通讯,会有以下几个问题: 面对类似上面的问题,我们要如何解决呢?答案就是:服务网关! 使用服务网关具有以下几个优点: 服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。 Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。 在Spring Cloud体系中, Spring Cloud Zuul 封装了Zuul组件,作为一个API网关,负责提供负载均衡、反向代理和权限认证。 Zuul的核心是一系列的filters, 其作用类似Servlet框架的Filter,Zuul把客户端请求路由到业务处理逻辑的过程中,这些filter在路由的特定时期参与了一些过滤处理,比如实现鉴权、流量转发、请求统计等功能。Zuul的整个运行机制,可以用下图来描述。 Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示。 基于Zuul的这些过滤器,可以实现各种丰富的功能,而这些过滤器类型则对应于请求的典型生命周期。 PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。 POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。 ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。 Zuul默认实现了很多Filter,这些Filter如下面表格所示。 可以在application.yml中配置需要禁用的filter,格式为 比如要禁用 实现自定义滤器需要继承ZuulFilter,并实现ZuulFilter中的抽象方法。 新建一个项目 spring-cloud-consul-zuul,如下图。 添加 consul、zuul 依赖。 pom.xml 启动类添加 @EnableZuulProxy 注解,开启服务网关支持。 配置启动端口为 8541, 注册服务到注册中心,配置zuul转发规则。 这里配置在访问 locathost:8541/feigin/call 和 ribbon/call时,调用消费者(8521,8522)的对应接口。 依次启动注册中心、服务提供者、服务消费者、服务网关项目。 访问 http://localhost:8541/ribbon/call, 效果如下图。 访问 http://localhost:8541/feign/call, 效果如下图。 说明Zuul已经成功转发请求,并成功调用后端微服务。 如果想给每个服务的API接口加上一个前缀,可使用zuul.prefix进行配置。 例如http://localhost:8541/v1/feign/call,即在所有的API接口上加一个v1作为版本号。 上面我们是通过添加路由配置进行请求转发的。 但是如果后端微服务服务非常多的时候,每一个都这样配置还是挺麻烦的,所以Spring Cloud Zuul已经帮我们做了默认配置。默认情况下,Zuul会代理所有注册到注册中心的微服务,并且Zuul的默认路由规则如下: 我们移除上面的配置,直接通过 serviceId/feign/call 的方式访问。 访问 http://localhost:8541/spring-cloud-consul-consumer/feign/call,效果如下。 结果也是可用访问的,说明ZUUL默认路由规则正在产生作用。 Zuul作为Netflix组件,可以与Ribbon、Eureka和Hystrix等组件相结合,实现负载均衡、熔断器的功能。默认情况下Zuul和Ribbon相结合,实现了负载均衡。实现熔断器功能需要实现FallbackProvider接口,实现该接口的两个方法,一个是getRoute(),用于指定熔断器功能应用于哪些路由的服务;另一个方法fallbackResponse()为进入熔断器功能时执行的逻辑。 创建 MyFallbackProvider 类,getRoute()方法返回"spring-cloud-consul-consumer",只针对consumer服务进行熔断。如果需要所有的路由服务都加熔断功能,需要在getRoute()方法上返回”*“的匹配符。getBody()方法返回发送熔断时的反馈信息,这里在发送熔断时返回信息:"Sorry, the service is unavailable now." 。 重新启动,访问 http://localhost:8541/spring-cloud-consul-consumer/ribbon/call, 可以正常访问。 停掉spring-cloud-consumer相关的服务, 再次访问 http://localhost:8541/spring-cloud-consul-consumer/ribbon/call, 效果如下。 说明我们自定义的熔断器已经起作用了。 创建一个MyFilter, 继承ZuulFilter类,覆写run()方法逻辑,在转发请求前进行token认证,如果请求没有携带token,返回"there is no request token"提示。 OK,这样就行了,Zuul会自动加载Filter执行过滤的。 重新启动Zuul项目,访问 http://localhost:8541/spring-cloud-consul-consumer/ribbon/call,效果如下。 请求时带上token,访问 http://localhost:8541/spring-cloud-consul-consumer/ribbon/call?token=111,效果如下。 Zuul作为API服务网关,不同的客户端使用不同的负载将请求统一分发到后端的Zuul,再有Zuul转发到后端服务。因此,为了保证Zuul的高可用性,前端可以同时开启多个Zuul实例进行负载均衡,另外,在Zuul的前端还可以使用Nginx或者F5再次进行负载转发,从而保证Zuul的高可用性。 API服务网关(Zuul) 标签:lap 权限控制 创建 service 做了 版本号 als 场景 framework 原文地址:https://www.cnblogs.com/7788IT/p/10667138.html技术背景
Spring Cloud Zuul
Zuul工作机制
过滤器机制
过滤器的生命周期
Zuul中默认实现的Filter
类型
顺序
过滤器
功能
pre
-3
ServletDetectionFilter
标记处理Servlet的类型
pre
-2
Servlet30WrapperFilter
包装HttpServletRequest请求
pre
-1
FormBodyWrapperFilter
包装请求体
route
1
DebugFilter
标记调试标志
route
5
PreDecorationFilter
处理请求上下文供后续使用
route
10
RibbonRoutingFilter
serviceId请求转发
route
100
SimpleHostRoutingFilter
url请求转发
route
500
SendForwardFilter
forward请求转发
post
0
SendErrorFilter
处理有错误的请求响应
post
1000
SendResponseFilter
处理正常的请求响应
禁用指定的Filter
zuul.
。org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
就设置如下。zuul:
SendResponseFilter:
post:
disable: true
自定义Filter
public class MyFilter extends ZuulFilter {
@Override
String filterType() {
return "pre"; // 定义filter的类型,有pre、route、post、error四种
}
@Override
int filterOrder() {
return 5; // 定义filter的顺序,数字越小表示顺序越高,越先执行
}
@Override
boolean shouldFilter() {
return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
Object run() {
return null; // filter需要执行的具体操作
}
}
实现案例
新建工程
添加依赖
启动类
package com.louis.spring.cloud.consul.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
添加配置
server:
port: 8541
spring:
application:
name: spring-cloud-consul-zuul
cloud:
consul:
host: localhost
port: 8500
discovery:
serviceName: ${spring.application.name} # 注册到consul的服务名称
zuul:
routes:
ribbon:
path: /ribbon/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /ribbon/
feign:
path: /feign/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /feign/
测试效果
配置接口前缀
zuul:
prefix: /v1
routes:
ribbon:
path: /ribbon/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /ribbon/
feign:
path: /feign/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /feign/默认路由规则
zuul:
routes:
ribbon:
path: /ribbon/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /ribbon/
feign:
path: /feign/**
serviceId: spring-cloud-consul-consumer # 转发到消费者 /feign/
http://ZUUL_HOST:ZUUL_PORT/微服务在注册中心的serviceId/**
会被转发到serviceId对应的微服务,所以说如果遵循默认路由规则,基本上就没什么配置了。路由熔断
package com.louis.spring.cloud.consul.zuul;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "spring-cloud-consul-consumer";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
System.out.println("exception:"+cause.getMessage());
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "ok";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
自定义Filter
package com.louis.spring.cloud.consul.zuul;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class MyFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return "pre"; // 定义filter的类型,有pre、route、post、error四种
}
@Override
public int filterOrder() {
return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行
}
@Override
public boolean shouldFilter() {
return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
public Object run() throws ZuulException {
// filter需要执行的具体操作
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
System.out.println(token);
if(token==null){
log.warn("there is no request token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("there is no request token");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
log.info("ok");
return null;
}
}
Zuul的高可用性
下一篇:C#默认参数原理探究