一、简介
Zuul 是一个基于JVM路由和服务的负载均衡器,提供路由、监控、安全等方面的服务框架。Zuul能够与Eureka、Ribbon、Hystrix等组件配合使用。
Zuul的核心是过滤器,通过这些过滤器我们可以扩展出很多功能,例如:
- 动态路由:动态的将客户端的请求路由到后端不同的服务,做一些逻辑处理,比如聚合多个服务的数据返回。
- 请求监控:可以对整个系统的请求进行监控,记录详细的请求响应日志,可以试试统计出当前系统的访问量以及监控状态。
- 认证鉴权:对每一个访问的请求做认证。拒绝非法请求,保护好后端服务。
- 压力测试:通过Zuul可以动态的将请求转发到后端服务的集群中,还可以识别测试流量和真实流量,从而做一些特殊处理。
- 灰度发布:灰度帆布可以保证整体系统的稳定,在初始灰度时就可以发现,调整问题,以保证其影响度。
二、简单使用
1. 引用依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
2. 在配置 文件中添加配置信息
spring.application.name=zuul-demo
server.port=2103
#通过zuul.routes配置路由转发 testzuul是自定义的名称,当访问 /testzuul/**地址时就会跳转到http://baidu.com/
zuul.routes.testzuul.path=/testzuul/**
zuul.routes.testzuul.url=http://baidu.com/
3. 在启动类加入 @EnableZuulProxy 注解
启动应用后 访问 http://localhost:2103/testzuul 就会跳转到 百度。
三、 集成 Eureka
1. 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
2. 在配置文件中添加配置
eureka.client.serviceUrl.defaultZone=XXXX
注意:因为 @EnableZuulProxy 中已经自带了 @EnableDiscoveryClient 所以在启动类中不用再加Eureka 的注解。
重启服务,我们可以通过默认的转发规则来访问Eureka中的服务,访问规则是:“API网关地址+访问的服务名称+接口URI”,例如 :http://localhost:2103/ribbon/test/fegin?userName=113
四、Zuul路由配置
Zuul的默认的转发规则 “API网关地址+访问的服务名称+接口URI”,在给服务制定名称时应该尽量短点,这样就可以使用默认的路由会泽进行请求,不需要为每个服务都定一个路由规则,默认规则举例:
API网关地址:http://localhost:2103
用户服务名称:ribbon
那么通过Zuul的访问登录接口的规则就是:http://localhost:2103/ribbon/test/fegin?userName=113
1. 指定具体服务路由:
zuul.routes.ribbon.path=/user-service/**
这里将服务名称为 ribbon的服务配置为user-service,也就是说想访问 ribbon服务的接口要通过 user-service访问。
注意:配置文件中的 /user-services/** 这里一定要是两个 * ,如果配置一个 * 就只能转发一级,两个表示可以转发任意层级的URL
2. 配置路由前缀
zuul.prefix=/rest
配置了路由前缀后要访问前加你配置的val,例如我配置的rest,例如:http://localhost:2103/rest/user-service/test/fegin?userName=%E2%80%981111%E2%80%99
3. 本地跳转
zuul.routes.local.path=/api/**
zuul.routes.local.url=forward:/local
配置后,重启服务访问 http://localhost:2103/rest/api/1 自动跳转本地的local的接口中。
五、Zuul过滤器
1. 过滤器的类型
- pre: 可以在请求被路由之前调用,适用于身份认证的场景,认证通过后再继续执行下面的流程。
- route:在路由请求时被调用,适用于灰度发布场景,在将要路由的时候可以做一些自定义的逻辑。
- post:在route 个error 过滤之后被调用,这种过滤器将请求路由到达具体的服务之后执行,适用于需要添加响应头、记录响应日志等应用场景。
- error:处理请求时发生错误时被调用,在执行过程中发送错误时会进入error过滤器,可以用来统一记录错误信息。
整个执行顺序是,请求发过来首先到 pre 过滤器,然后到 routing 过滤器 最后到post 过滤器,任何一个过滤器有异常都会进入到 error过滤器。
2. 过滤器的使用
过滤器的创建
public class IpFilter extends ZuulFilter {
private List<String> blackIpList = Arrays.asList("127.0.0.1");
public IpFilter() {
super();
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
//true为过期生效 false 为过滤器不执行
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = getIpAddr(ctx.getRequest());
if(StringUtils.isNotBlank(ip) && blackIpList.contains(ip)){
//禁止转发
ctx.setSendZuulResponse(false);
ctx.setResponseBody("非法请求");
//禁止本地转发
ctx.set("sendForwardFilter.ran",true);
ctx.getResponse().setContentType("application/json;charset=utf-8");
//过滤器中传递数据
ctx.set("msg","hello---------");
ctx.set("isSuccess" ,true);
return null;
}
return null;
}
}
说明:
- shouldFilter:是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心的配置来实现,达到动态的开启和关闭过滤器。filterType:过滤器类型,可选值有pre、route、post、error。
- filterOrder:过滤器执行的顺序,数值越小,优先级越高。
- run:执行自己的业务逻辑。
过滤器定义完成之后需要配置过滤器才能生效。
@Configuration
public class FilterConfig {
@Bean
public IpFilter ipFilter(){
return new IpFilter();
}
}
过滤器中的异常处理
ublic class ErrorFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
public ErrorFilter() {
super();
}
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 100;
}
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
logger.error("Filter Error :{}",throwable.getCause().getMessage());
return null;
}
}
@Configuration
public class FilterConfig {
@Bean
public ErrorFilter errorFilter(){
return new ErrorFilter();
}
}
再创建一个error过滤器之后,我们需要在其他额过滤器中制造一个异常,我选择在 上面的pre的过滤器中 加入下面的代码制造一个异常
System.out.println(2/0);
重启应用访问接口时会出现下图所示的界面
我们可以自定义异常的返回格式
@RestController
public class ErrorHandlerController implements ErrorController {
private ErrorAttributes errorAttributes;
@Autowired
public ErrorHandlerController(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
@Override
public String getErrorPath() {
return "/error";
}
@GetMapping("/error")
public String error(HttpServletRequest request){
Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
String message = (String) errorAttributes.get("message");
String trace = (String) errorAttributes.get("trace");
if(StringUtils.isNotBlank(trace)){
message += String.format("and trace %s",trace);
}
return message;
}
}
再次重启应用,访问同样的接口,返回的格式如下:
Zuul的容错机制
引入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置文件添加如下配置:
#开启重试
zuul.retryable=true
#请求连接的超时时间
ribbon.connectTimeOut=500
#请求处理的超时时间
ribbon.readTimeOut=5000
#对当前实例的重试次数
ribbon.maxAutoRetries=1
#切换实例的最大重试次数
#ribbon.maxAutoRetriesNextServer=3
#对所有操作请求都进行重试
ribbon.okToRetryOnAllOperations=true
#对制定的Http响应码进行重试
ribbon.retryableStatusCode=500,404,502
Zuul回退机制
在springcloud中Zuul默认整合了Hystrix,当后端服务异常时可以为 Zuul 添加回退功能,返回默认的数据给客户端。
实现回退机制需要实现 FallbackProvider 接口,具体代码如下:
@Component
public class ServiceConsumerFallbackProvider implements FallbackProvider {
private Logger logger = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
if(null != cause){
logger.error("",cause.getCause());
}
RequestContext ctx = RequestContext.getCurrentContext();
return new ByteArrayInputStream("服务器内部错误".getBytes());
}
@Override
public HttpHeaders getHeaders() {
MediaType mediaType = new MediaType("application","json", Charset.forName("UTF-8"));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
return httpHeaders;
}
};
}
}
getRoute 方法中法返回 * 代表对所有服务进行回退操作,如果只想对某个服务进行回退操作,那么就返回需要回退的服务名称,这个名称一定要是注册到 Eureka 中的名称。
通过 ClientHttpResponse 构造回退的内容,通过getStatusCode 返回响应的状态码,通过getStatusText 返回响应状态码对应的文本,通过getBody返回回退的内容。通过getHeaders 返回响应的请求头信息。