Zuul
是 Netflix
开源的一个 API Gateway
服务器, 本质上是一个基于 Servlet
的 Web
应用。在微服务框架 Spring Cloud
中,Zuul
被作为 服务的网关,负责对 请求 进行一些 预处理,比如:安全验证、动态路由、负载分配 等等。
在前面几篇的基础上,新建一个 service-zuul
的项目模块,配置 pom.xml
如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
1.5.3.RELEASE
io.github.ostenant.springcloud
service-zuul
0.0.1-SNAPSHOT
service-zuul
Demo project for Spring Boot
1.8
Dalston.SR1
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
在应用程序启动类上,使用 注解 @EnableZuulProxy
开启 路由网关:
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class, args);
}
}
在 application.yml
文件中配置 URL
前缀和 服务 的映射关系。首先指定 服务注册中心 的地址为 http://localhost:8761/eureka/
,服务的 端口号 为 8769
。服务名 为 service-zuul
,以 /api-a/
为 前缀 的请求都转发给 service-feign
服务,以 /api-b/
为 前缀 的请求都转发给 service-ribbon
服务。
server:
port: 8769
spring:
application:
name: service-zuul
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-feign
api-b:
path: /api-b/**
serviceId: service-ribbon
按顺序依次运行 eureka-server
、service-hi
、service-ribbon
、service-feign
、service-zuul
几个应用,其中在 8762
和 8763
端口 启动两个 service-hi
服务实例。
访问 http://localhost:8769/api-a/hi?name=zuul
两次,服务端返回数据如下:
Hi zuul, I am from port: 8763
Hi zuul, I am from port: 8762
这说明 Zuul
起到了 路由 的作用。如果某个 服务 存在 多个实例,Zuul
会结合 Ribbon
做 负载均衡,将请求 均分并路由 到不同的 服务实例。
Zuul
不仅只作为 路由,并且提供 过滤功能,包括一些 安全验证。继续改造项目,增加一个 Zuul
提供的过滤器。
@Component
public class MyFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(getClass());
@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();
logger.info(String.format("%s >> %s", request.getMethod(),
request.getRequestURL().toString()));
Object accessToken = request.getParameter("token");
if (accessToken == null) {
log.warn("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
} catch (Exception e) {
logger.error(e);
return null;
}
}
return null;
}
}
filterType()
方法表示 过滤器 的类型:
pre:路由发生之前;
routing:路由发生时;
post:路由发生之后;
error:发送错误调用时。
访问 http://localhost:8769/api-b/hi?name=zuul
,服务端返回的响应数据如下:
token is empty
访问 http://localhost:8769/api-b/hi?name=zuul&token=123
两次,服务端返回的响应数据如下:
Hi zuul, I am from port: 8763
Hi zuul, I am from port: 8762
在上面 Zuul
工程 application.yml
的基础上增加一条配置 zuul.prefix: /v1
,然后重启应用,访问 http://localhost:8769/v1/api-b/hi?name=mark&token=123
即可。
在 Zuul
的基础上实现 熔断功能 很简单,类似实现 过滤器 的步骤,只需要实现 ZuulFallbackProvider
接口即可,代码如下:
@Component
public class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "service-feign";
}
@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("Fallback method is invoked!".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
getRoute():指定 熔断器 作用于哪些 服务;
fallbackResponse():指定 熔断 时返回的响应数据。
重新启动 service-zuul
应用,并关闭所有的 service-hi
的 服务实例,在浏览器上访问 http://localhost:8769/v1/api-a/hi?name=mark&token=123
,服务端返回的响应数据如下:
Fallback method is invoked!
如果需要所有的 路由服务 都加上 熔断功能,只需要在 getRoute()
方法上返回 “*”
的 通配符 即可。
Zuul
采用的是 同步步阻塞模型,性能比 Nginx
差,由于 Zuul
和其他 Netflix
组件 相互配合、无缝集成,Zuul
很容易就能实现 负载均衡、智能路由 和 熔断器 等功能。在大多数情况下,Zuul
都是以 集群 的形式部署。
对不同的 终端用户,使用不同的 Zuul
来进行 路由,例如 移动端 共用一个 Zuul
网关实例,Web
端用另一个 Zuul
网关实例,其他的 客户端 用另一个 Zuul
实例进行路由。
另外一种常见的 集群部署方式 就是通过 Nginx
和 Zuul
相互结合来做 负载均衡。
欢迎关注技术公众号: 零壹技术栈
本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。