本文是继复习了Eureka、Ribbon、OpenFeign、Hystrix之后,SpringCloud系列的微服务网关组件部分。
SpringCloud Zuul虽然现在用的比较少,基本都被SpringCloud Gateway替代了,但是因为公司的老项目中还是用的Zuul,因此有必要再复习下。
本文的写作思路主要包括以下几个方面来,主要是实战和源码验证
Zuul本身是Netflix公司研发的网关组件,1.x的版本底层是基于阻塞式IO(Zuul 2.x版本基于Netty性能也很高)SpringCloud Zuul就是SpringCloud官方基于Zuul做了一层封装。说到网关,那么先回想一下之前的实战中没有网关,写在fc-service-portal和fc-service-screen里的接口,要想访问他们这两个服务里的接口,需要切换对应的服务的ip和端口。这还只是个示例,真正生产环境中,微服务的个数是很多的,不可能让前端访问后端的接口要记住那么多的host,那前端哥们会疯掉。
网关就是让前端所有请求都先路由到网关,由网关和Ribbon,Hystrix等整合决定访问路由下游具体哪一个服务实例。如果网关要做高可用,其实很简单,只需要把网关部署多个实例,然后用Nginx做负载均衡,此时的架构就是
前端 => 负载均衡 => 网关 => 后端服务
Zuul在SpringCloud中的角色定位就是请求路由,然后解析请求URI,再根据你application.yml里配置的路由规则,进行路由匹配,然后将请求封装一下,基于Eureka + Ribbon实现服务的负载均衡,基于Hystirx包裹实际的请求,完成熔断降级就转发到对应的服务。
然后就是有很多过滤器
1、执行请求前阶段的pre过滤器
ServletDetectionFilter
Servlet30WrapperFilter
FromBodyWrapperFilter
DebugFilter
PreDecorationFilter
2、请求路由阶段的routing过滤器
RibbonRoutingFilter
SimpleHostRoutingFilter
SendForwardFilter
3、执行请求后返回结果前阶段的post过滤器
SendResponseFilter
4、执行错误阶段的error过滤器
SendErrorFilter
新建一个fc-gateway-zuul的工程项目
1、pom.xml引入坐标依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
2、启动类增加@EnableZuulProxy注解开启Zuul功能
@SpringBootApplication
@EnableZuulProxy
public class FcGatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(FcGatewayZuulApplication.class, args);
}
}
3、配置文件application配置路由到fc-service-portal里的
server:
port: 8000
spring:
application:
name: fc-gateway-zuul
#eureka相关配置
eureka:
client:
service-url:
defaultZone: http://root:123456@localhost:8761/eureka/
instance:
#显示的微服务名称
instance-id: ms-fc-gateway-zuul-8000
#eureka客户端向服务端发送心跳时间默认30s
lease-renewal-interval-in-seconds: 10
#Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
lease-expiration-duration-in-seconds: 30
#路由配置
zuul:
routes:
#主要用来唯一标识一个path的,一般用服务名
fc-service-portal:
path: /portal/**
启动ureka-server,fc-gateway-zuul,fc-service-portal,fc-service-screen
然后通过fc-gateway-zuul来访问fc-service-portal的一个接口
http://localhost:8000/portal/getUser3/2?age=27
看接口访问的结果,是预期的
说明fc-gateway-portal的路由效果起到了。
自定义一个继承ZuulFilter的MyZuulFilter类
@Slf4j
public class MyZuulFilter extends ZuulFilter {
@Override
public String filterType() {
//在哪个阶段执行
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//数字越小优先级越大
return 1;
}
@Override
public boolean shouldFilter() {
//是否需要执行过滤器,默认是false
return true;
}
@Override
public Object run() throws ZuulException {
log.info("执行MyZuulFilter逻辑");
return null;
}
}
然后注入到Spring容器
@Configuration
public class MyZuulFilterConfig {
@Bean
public MyZuulFilter zuulFilter() {
return new MyZuulFilter();
}
}
我们启动服务,然后访问一个接口看看效果
http://localhost:8000/portal/getPortByFeign
Zuul与Ribbon整合会用到RibbonRoutingFilter过滤器,转发的时候会用Hystrix包裹请求,如果请求失败会执行fallback逻辑。
定义一个实现了FallbackProvider接口的MyFallbackProvider类,然后通过@Bean注入到Spring容器。
import com.netflix.hystrix.exception.HystrixTimeoutException;
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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//也可以针对某一个服务配置,比如fc-service-portal
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
//这里可以根据实际的异常做不同的处理,hystrix默认超时时间是1s
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback:" + MyFallbackProvider.this.getRoute()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
注入Spring容器
@Configuration
public