1. 统一的调用入口
zuul API 网关,为微服务应用提供统一的对外访问接口。zuul 还提供过滤器,对所有微服务提供统一的请求校验。
1.1 新建 sp11-zuul 项目
1.2 添加依赖
1.2.1 pom.xml
- 需要添加 sp01-commons 依赖
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
cn.tedu
sp11-zuul
0.0.1-SNAPSHOT
sp11-zuul
Demo project for Spring Boot
1.8
Hoxton.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
cn.tedu
sp01-commons
0.0.1-SNAPSHOT
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
1.3 修改 application.yml 文件
- zuul 路由配置可以省略,缺省以服务 id 作为访问路径
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
routes:
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
1.4 主程序添加 @EnableZuulProxy
注解
package cn.tedu.sp11;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class Sp11ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(Sp11ZuulApplication.class, args);
}
}
1.5 启动项目
- http://eureka1:2001
- http://localhost:3001/item-service/35
- `http://localhost:3001/i
- tem-service/decreaseNumber`
使用postman,POST发送以下格式数据:[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
2. 统一的权限校验
网关添加权限判断代码,后台服务不需要再判断权限,只关注业务就可以zuul 提供一个过滤器,可以继承过滤器,在子类中添加权限判断
2.1 定义过滤器,继承 ZuulFilter,添加 @Component 注解
在 sp11-zuul 项目中新建过滤器类
package com.tedu.sp11.filter;
import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class AccessFilter extends ZuulFilter {
//过滤器类型:pre,post,routing,error
@Override
public String filterType() {
//return "pre";//前置过滤器
return FilterConstants.PRE_TYPE;
}
//过滤器插入的位置
@Override
public int filterOrder() {
return 6;//放到第六个位置
}
//对用户请求进行判断,是否执行过滤代码
@Override
public boolean shouldFilter() {
//只对 item-service 调用进行过滤,如果调用 user-service 和 order-service,不执行过滤代码
//获得调用服务的id
RequestContext context=RequestContext.getCurrentContext();
String serviceId= (String) context.get(FilterConstants.SERVICE_ID_KEY);
return "item-service".equalsIgnoreCase(serviceId);
}
//过滤代码
@Override
public Object run() throws ZuulException {
//获得request对象
RequestContext context=RequestContext.getCurrentContext();
HttpServletRequest request=context.getRequest();
//收到token参数
String token=request.getParameter("token");
//如果没有,阻止访问,并直接向客户端返回响应
if (StringUtils.isBlank(token)){
//阻止继续访问
context.setSendZuulResponse(false);
//向客户端返回响应 JsonResult{code:401,msg:not,login,data:null} String json=JsonResult.err().code(JsonResult.NOT_LOGIN).msg("not login").toString();
context.addZuulResponseHeader("Content-Type","application/json;charset=UTF-8" );
context.setResponseBody(json);
}
return null;//返回值没有任何作用
}
}
2.2 访问测试
- 没有token参数不允许访问
http://localhost:3001/item-service/35 - 有token参数可以访问
http://localhost:3001/item-service/35?token=1234
3. zuul 集成 ribbon
3.1 zuul + ribbon 负载均衡
zuul 已经集成了 ribbon,默认已经实现了负载均衡
3.2 zuul + ribbon 重试
3.2.1 pom.xml 添加 spring-retry 依赖
- 需要 spring-retry 依赖
org.springframework.retry
spring-retry
3.2.2 配置 zuul 开启重试,并配置 ribbon 重试参数
- 需要开启重试,默认不开启 zuul:retryable: true
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
retryable: true
# routes:
# item-service: /item-service/**
# user-service: /user-service/**
# order-service: /order-service/**
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 1
4. zuul 集成 hystrix
0配置,已经启用了hystrix
4.1 zuul + hystrix 降级
4.1.1 创建降级类
- getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务
ItemServiceFallback
package com.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class ItemFB implements FallbackProvider {
//返回一个 service id,针对指定的服务进行降级处理
//返回 "*" 或者 null,对所有服务都应用这个降级类
@Override
public String getRoute() {
return "item-service";
}
//降级响应,返回一个封装响应数据的 response 对象
@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 HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//JsonResult{code:401,msg:调用商品失败,data:null}
String json = JsonResult.err().msg("调用商品失败").toString();
return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
//content-Type:application/json;charset=UTF-8
HttpHeaders headers = new HttpHeaders();
headers.add("content-Type", "application/json;charset=UTF-8");
return headers;
}
};
}
}
OrderServiceFallback
package com.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class OrderFB implements FallbackProvider {
//返回一个 service id,针对指定的服务进行降级处理
//返回 "*" 或者 null,对所有服务都应用这个降级类
@Override
public String getRoute() {
return "order-service";
}
//降级响应,返回一个封装响应数据的 response 对象
@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 HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//JsonResult{code:401,msg:调用商品失败,data:null}
String json = JsonResult.err().msg("调用订单服务失败").toString();
return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
//content-Type:application/json;charset=UTF-8
HttpHeaders headers = new HttpHeaders();
headers.add("content-Type", "application/json;charset=UTF-8");
return headers;
}
};
}
}
4.2 降低 hystrix 超时时间,以便测试降级
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
retryable: true
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2000
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 1
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500
4.3 启动服务,测试降级
http://localhost:3001/item-service/35
5. zuul + hystrix 数据监控
5.1 暴露 hystrix.stream 监控端点
- zuul 已经包含 actuator 依赖
management:
endpoints:
web:
exposure:
include: hystrix.stream
5.2 开启监控
启动 sp08-hystrix-dashboard,填入 zuul 的监控端点路径,开启监控
http://localhost:4001/hystrix
填入监控端点:
http://localhost:3001/actuator/hystrix.stream
必须通过zuul网关访问后台服务才会长生监控数据
- http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
6. zuul + turbine 聚合监控
6.1 修改 turbine 项目,聚合 zuul 服务实例
spring:
application:
name: turbin
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
turbine:
app-config: order-service, zuul
cluster-name-expression: new String("default")
- 使用hystrix仪表盘, 对 turbine 监控端点进行监控, 此端点聚合了订单服务和zull网关服务的监控数据
http://localhost:5001/turbine.stream
6.2 熔断测试
ab -n 20000 -c 50 http://localhost:3001/order-service/123abc
7. zuul Cookie过滤
zuul 会过滤敏感 http 协议头,默认过滤以下协议头:
- Cookie
- Set-Cookie
- Authorization
可以设置 zuul 不过滤这些协议头
zuul:
sensitive-headers:
8. zuul 和 feign 区别
- 调用远程服务
- 集成ribbon
- 集成hystrix
zuul - 网关,作为一个独立的服务,部署在系统的最前面
- 不推荐启用重试
- 在最前面重试,会造成后台所有服务器访问压力倍增
feign - 业务微服务系统内部,服务和服务之间调用
- 不推荐启用hystrix
- 会造成混乱