路由网关 SpringCloud Zuul
智能路由网关组件Zuul,作为微服务系统的网关组件,用于构建边界服务。致力于动态路由,过滤,监控,弹性伸缩和安全。
为什么需要Zuul?
- Zuul Ribbon 以及 Eureka 相结合,可以实现智能路由和负载均衡的功能, Zuul 能够将请求流量按某种策略分发到集群状态的多个服务实例。
- 网关将所有服务的 API 接口统一聚合,并统一对外暴露。外界系统调用 API 接口时,都是由网关对外暴露的 API 接口,外界系统不需要知道微服务系统中各服务相互调用的复杂性。微服务系统也保护了其内部微服务单元的 API 接口,防止其被外界直接调用,导致服务的敏感信息对外暴露。
- 网关服务可以做用户身份认证和权限认证,防止非法请求操作 API 接口,对服务器起到保护作用。
- 网关可以实现监控功能,实时日志输出,对请求进行记录。
- 网关可以用来实现流量监控,在高流量的情况下,对服务进行降级。
- API 接口从内部服务分离出来 ,方便做测试。
Zuul工作原理
- PRE 过滤器 它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、 参数验证等。
- ROUTING 过滤器,它用于将请求路由到具体的微服务 。在默认情况下,它使用Http Client 进行网络请求。
- POST 过滤器:它是在请求己被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响传输到客户端。
- ERROR 过滤器:它是在其他过滤器发生错误时执行的。
Zuul 采取了动态读取、编译和运行这些过滤器,过滤器,间不能直接通信,而是通过RequestContext 对象来共享数据 。每个请求都会创建 RequestContext 对象。
Zuul 请求的生命周期
当一个客户端 Request 请求进入 Zuul 服务时,网关先进入“pre filter”进行一系列的验证、操作或者判断。然后交给”routing filter”进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完后,最后由”post filter”,进行处理,该类型的处理器处理完之后,将 Response 信息返回给客户端。
搭建 Zuul 服务
配置
在上一个案例基础上,新建module,eureka-zuul-client,然后添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.zwq</groupId>
<artifactId>eureka</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zwq</groupId>
<artifactId>eureka-zuul-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-zuul-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
修改application.yml配置文件:
spring:
application:
name: service-zuul
server:
port: 5000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
prefix: /zwq
来看zuul配置:
zuul.routes.hiapi.path和serviceId可以将以”/hiapi”开头的url路由到eureka-client服务,hiapi是自定义的,需要指定path,serciceId,下面的ribbonapi同理
最后的zuul.prefix是给每个服务 API 接口加上前缀。
访问测试
启动eureka-server、2个eureka-client实例、eureka-ribbon-client、eureka-zuul-client服务。
多次访问浏览器:http://localhost:5000/hiapi/hi?name=zwq ,浏览器会轮流显示:
可见Zuul在路由转发做了负载均衡。
zuul使用熔断器
配置
在Zuul 实现熔断功能需要实现 ZuulFallbackProvider 接口。实现该接口有两个方法,一个是getRoute方法,用于指定熔断功能应用于哪些路由的服务,另一个方法 fallbackResponse为进入熔断功能时执行的逻辑
在eureka-zuul-client工程创建config包,新建MyFallBackProvider类实现ZuulFallbackProvider:
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
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;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* description: 熔断
*
* @author zwq
* @date 2021/10/22 15:19
*/
@Component
public class MyFallBackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "eureka-client";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() {
return 200;
}
@Override
public String getStatusText() {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream("ops,error,i'm fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
启动测试
启动 eureka-zuul-client 工程,并且关闭 eureka-client 的所有实例,在浏览器上访问
http://localhost:5000/hiapi/hi?name=zwq ,浏览器显示:
以上代码是对eureka-client服务的熔断器,其中把getRoute()方法返回值改成”*”,则是给所有路由功能加熔断功能。
zuul使用过滤器
配置
zuul使用过滤器需要继承 ZuulFilter ,并实现 ZuulFilter 中的抽象方法,包括 filterType,filterOrder,以及IZuulFilter的shouldFilter和 Object run()的两个方法。其中:
- filterType()即过滤器的类型,它有 4种类型:pre,post,routing,error。
- filterOrder()是过滤顺序,它为 Int 类型的值,值越小,越早执行该过滤器。
- shouldFilter()表示该过滤器是否过滤逻辑,如果为 true ,则执行 run()方法:如果为 false ,则不执行 run()方法。run()方法写具体的过滤的逻辑
下面来模拟一个用户身份验证功能,使用过滤器检查请求参数中是否传了token这个参数,如果没有,直接返回响应,不被路由。
在eureka-zuul-client工程的config包中新建MyFilter类:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* description: 过滤器
*
* @author zwq
* @date 2021/10/22 15:55
*/
@Component
public class MyFilter extends ZuulFilter {
@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();
Object accessToken = request.getParameter("token");
if(accessToken == null){
System.out.println("token is null");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is null");
} catch (Exception e) {
return null;
}
}
System.out.println("ok");
return null;
}
}
访问测试
开启服务eureka-server、eureka-client、eureka-ribbon-client、eureka-zuul-client:
访问:http://localhost:5000/zwq/ribbonapi/hi?name=zwqzwq ,提示没有token:
接下来携带token: http://localhost:5000/zwq/ribbonapi/hi?name=zwqzwq&token=121212 ,成功:
可见, MyFilter对请求进行了过滤,并在请求路由转发之前进行了逻辑判断。在实际开发中,可以用此过滤器进行安全验证。
本例架构图:
zuul常见使用方式架构图: