简介
Spring Cloud Zuul组件的功能是能够提供动态路由转发、安全过滤、监控等微服务架构中的一系列边缘化服务。通常情况下,后端的服务并不直接开放给前端调用,而是通过API路由的方式,将请求转发到具体的后端服务。这种情况,就类似于我们熟知的反向代理,通过API网关去具体负责由谁提供服务。Zuul利用各种Filter实现如下功能:
- 认证和安全,识别每个需要认证的资源,拒绝不符合要求的请求。
- 性能监测,在服务边界追踪并统计数据,提供精确的生产视图。
- 动态路由,根据需要将请求动态路由到后端集群。
- 压力测试,逐渐增加对集群的流量以了解其性能。
- 负载卸载,预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。
- 静态资源处理,直接在边界返回某些响应。
简单示意,如下图:
路由功能
基于学习Eureka Server的代码,加入Zuul工程,首先在Zuul工程中,引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
然后修改配置文件,所有访问/baidu的请求,都会重新指向baidu.com
server:
port: 13000
spring:
application:
name: api-gateway-zuul
zuul:
routes:
baidu:
path: /baidu/**
url: http://www.baidu.com
修改启动类,加入@EnableZuulProxy
package org.dothwinds.serviceprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class SpringCloudStudyZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStudyZuulApplication.class, args);
}
}
配置完成,启动服务,访问http://localhost:13000/baidu,访问跳转到了baidu.com
微服务架构中的应用
上面的例子,我们只是做个转发的测试,在实际微服务应用场景中,我们不能对每新增一个服务就加一段配置,这里也需要通过Eureka之类的注册中心,服务之间彼此感知,通过ServiceId等进行绑定,由于我们项目工程中已经有Eureka和Service-Provider的工程,所以在这个基础上我们改造Zuul,注册到Eureka里,把服务请求转发到Service-Provider里
修改Zuul配置文件
server:
port: 13000
spring:
application:
name: api-gateway-zuul #应用名称,会显示在eureka server中
zuul:
routes:
api-a:
path: /sayHi
serviceId: service-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka
在启动类中加入@EnableEurekaClient注解,依次启动Eureka、Service-Provider和Zuul服务
访问http://localhost:13000/sayHi?name=huxi,如下图,应用已被转发至Service-Provider服务中了
Zuul的默认路由规则
Zuul的默认路由规则是它会代理所有注册到Eureka Server的微服务,默认情况下,http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka Server上的注册serviceId/**会被转发到serviceId对应的微服务。
负载均衡效果
由于Zuul默认集成Ribbon,所以天然集成了负载均衡,不用任何配置,基于上面的例子,我们把Service-provider服务代码改造一下,在新启动一个服务测试一下负载效果,修改服务端口号和接口返回值
server:
port: 9002
spring:
application:
name: service-provider #应用名称,会显示在eureka server中
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka #eureka server地址
package org.dothwinds.serviceprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class SpringCloudStudyServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStudyServiceProviderApplication.class, args);
}
@GetMapping("/sayHi")
public String sayHi(@RequestParam(value = "name", defaultValue = "dothwinds") String name) {
return "hi," + name + ", welcome to my world, the message from service two";
}
}
启动此服务
访问 http://localhost:13000/sayHi?name=huxi,可以看到两种效果的互相切换
安全过滤
开篇的时候提到了Zuul本身有认证和安全的相关功能职责,都是借助Filter来实现的,接下来看一下ZuulFilter,其它核心方法如下:
public String filterType() {
return null;
}
public int filterOrder() {
return 0;
}
public boolean isFilterDisabled() {
return false;
}
public boolean shouldFilter() {
return true;
}
public Object run() {
return null;
}
含义如下:
shouldFilter:是否执行该过滤器,true执行,false不执行
run:过滤器的具体业务逻辑
filterType:返回过滤器类型
一共有4种:
1.pre:请求在被路由之前执行
2.routing:在路由请求时调用
3.post:在routing和errror过滤器之后调用
4.error:调用错误
filterOrder:过滤器的执行顺序,数字越小优先级越高,且该值不可重复
isFilterDisabled:是否禁用,默认false,true为禁用
过滤器执行生命周期
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理
- 服务调用时长统计:pre和post结合使用
过滤验证的例子
1,写一个Filter,继承ZuulFilter
package org.dothwinds.serviceprovider.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
@Component
public class UserFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1; //从1开始
}
@Override
public boolean shouldFilter() {
return true; //是否执行,
}
@Override
public Object run() throws ZuulException {
return null;
}
}
实现对应的方法,修改filterType,filterOrder和shouldFilter等值,最后写具体run方法的验证逻辑
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();// 1.获取Zuul提供的请求上下文对象
HttpServletRequest request = ctx.getRequest();
Object token = request.getParameter("token");
if(token == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
try {
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){
System.out.println(e);
}
return null;
}
return null;
}
重启服务,验证一下刚才通过zuul调用的方法,访问不带token
访问带token
可以看到,Filter起到了过滤验证作用
参考资料:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html
代码地址:
https://gitee.com/dothwinds/Spring-Cloud-Study/tree/master/spring-cloud-study-zuul