路由网关Zuul
本章讲解构建微服务的另一个组件——智能网关组件Zuul。Zuul用于构建边界服务,致力于动态路由、过滤、监控、弹性伸缩和安全。
为什么需要Zuul
- 能和ribbon、eureka相结合实现智能路由和负载均衡的功能。
- 将所有服务的API接口统一聚合并对外界暴露。给外部的感觉是一个单体系统,这样保护了各个微服务的接口。
- 可以做用户身份验证和权限认证,对微服务起到保护作用。
- 可以实现监控功能,对请求进行记录。
- 可以实现流量监控,在高流量下对服务降级。
- api聚合,方便做测试。
工作原理
image.png如图,zuul的核心是一系列过滤器,对自定义的ZuulServlet(类似于spring mvc的DispatcherServlet)来对请求进行控制,经过过滤后返回请求。Zuul包括以下4种过滤器:
- PRE过滤器:路由到具体服务之前执行。可以做安全校验、身份验证、参数验证等。
- ROUTING过滤器:用于路由到具体服务。默认使用HttpClient。
- POST过滤器:路由到服务之后执行。用于收集统计信息、指标、以及将相应信息传输到客户端。
- ERROR过滤器:其他过滤器发生错误时执行。
过滤器之间通过RequestContext对象共享数据。过滤器有以下特性 - Type(类型): 过滤器类型,决定在那个阶段起作用。
- Execution Order(执行顺序):order值越小,越先执行。
- Criteria(标准):过滤器执行所需的条件。
- Action(行动):如果符合执行条件,则执行Action逻辑代码。
案列实战
搭建Zuul服务
在之前项目上新建一个Modul——api-gateway。
- 添加pom依赖,主要包括zuul、eureka、web等的起步依赖。
<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>
</dependency>
- 在启动类上加上@EnableZuulProxy开启zuul的功能,@EnableEurekaClient开启EurekaClient的功能。
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
- 工程配置
spring:
application:
name: api-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 5000
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
重点说Zuul的配置写法。zuul.routes.hiapi.path: /hiapi/**,zuul.routes.hiapi.serviceId:eureka-client。表示可以将 “/hiapi”开头的url路由到eureka-client,其中的“hiapi”时自定义的。如果服务存在多个实例,Zuul会结合Ribbon做负载均衡。
- 测试
依此启动工程eureka-server,eureka-client启动两个实例,端口为8763,8764。
多次访问 http://localhost:5000/hiapi/hi?name = dzy
会交替出现
hi dzy ,I am from 8763
hi dzy ,I am from 8764
可见实现了路由并做了负载均衡。
注意
如果不需要负载均衡,可以指定服务实例的Url,用zuul.routes.hiapi.url配置,配置后直接访问指定的Url,在实际开发中很少用到。
zuul:
routes:
hiapi:
path: /hiapi/**
url: http://localhost:8763
如果想指定Url,并且想做负载均衡,可以自己维护负载均衡的列表
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: hiapi-v1 # 自定义服务名
ribbon:
eureka:
enabled: false # 不从Eureka Client获取服务注册列表信息。
hiapi-v1: #使用自定义服务名,配置多个负载均衡的url
ribbon:
listOfServers: http://localhost:8763,http://localhost:8764
如果想给服务的api接口加前缀,例如http://localhost:5000/v1/hiapi/hi?name = dzy,加一个v1作为版本号。这时需要用zuul.perfix的配置
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
prefix: /v1
在Zuul上配置熔断器
需要实现ZuulFallbackProvider接口。实现改接口中的两个办法,一个是getRoute()方法,用于指定熔断功能用于那些路由的服务;另一个是fallbackResponse()为进入熔断功能是执行的逻辑。ZuulFallbackProvider源码如下
public interface ZuulFallbackProvider {
/**
* The route this fallback will be used for.
* @return The route the fallback will be used for.
*/
public String getRoute();
/**
* Provides a fallback response.
* @return The fallback response.
*/
public ClientHttpResponse fallbackResponse();
}
现实现一个针对eureka-client服务的熔断器,当eureka-client的服务出现故障时,进入熔断逻辑。代码如下
@Component
public class HystrixProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
//所有的路由都加熔断功能,返回*
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
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("error! I am fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
注意
:这里一定要加@Component把熔断器注入IOC容器。
Zuul使用过滤器
之前讲了Zuul的工作原理就是一系列过滤器。下面讲解如何实现一个自定义的过滤器。只需要继承ZuulFilter,并实现ZuulFilter中的抽象方法和IZuulFilter接口中的方法,其中filterType()为过滤器的类型;filterOrder()为过滤顺序,为一个Int类型的值,值越小,越早执行。shouldFilter()表示是否执行过滤逻辑,true则执行run()方法,false则不执行run()方法。其中run()写具体的过滤的逻辑。
现在实现检查请求中的参数是否传了token这个参数,如果没有传,请求不被路由到具体的服务实例,直接返回响应。代码如下:
@Component
public class MyFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)) {
LOGGER.info("token is not exist");
context.setSendZuulResponse(false);
context.setResponseStatusCode(401);
try {
context.getResponse().getWriter().write( "token is required");
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
return null;
}
}
注意
:这里一定要加@Component把filter注入IOC容器。
总结
在这一章学习了搭建了Zuul服务,并介绍了Zuul的工作原理和添加熔断器、过滤器。在下一章学习构建微服务中的配置中心——Spring Cloud Config组件。
PS:项目github地址