文章目录
概念
SpringCloud中的网关工具,,基于这个网关, 可以实现负载均衡、降级/熔断等策略
准备环境
新建zuul工程
- springboot工程
添加依赖
<parent>
<artifactId>springcloud1</artifactId>
<groupId>cn.tedu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp06-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp06-zuul</name>
<description>sp06-zuul</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置yml
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/**
启动类
- 添加注解
@EnableZuulProxy
- 可以过滤客户端请求, 在过滤器中可以检查访问权限
测试
### 获取订单的商品列表
GET http://localhost:8001/ty4g33t3
### 减少商品库存
POST http://localhost:8001/decreaseNumber
Accept: application/json
Content-Type: application/json
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
### 获取用户
GET http://localhost:8101/7
###
GET http://localhost:8101/8
###
GET http://localhost:8101/9
###
GET http://localhost:8101/10
### 增加积分
GET http://localhost:8101/8/score?score=1000
### 获取订单
GET http://localhost:8201/iujhygf435tg
### 添加订单
GET http://localhost:8201/add
# ---------------------------------------------------------------------------------
### 获取订单的商品列表
GET http://localhost:3001/item-service/ty4g33t3
### 减少商品库存
POST http://localhost:3001/item-service/decreaseNumber
Accept: application/json
Content-Type: application/json
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
### 获取用户
GET http://localhost:3001/user-service/7
###
GET http://localhost:3001/user-service/8
###
GET http://localhost:3001/user-service/9
###
GET http://localhost:3001/user-service/10
### 增加积分
GET http://localhost:3001/user-service/8/score?score=1000
### 获取订单
GET http://localhost:3001/order-service/iujhygf435tg
### 添加订单
GET http://localhost:3001/order-service/add
Zuul 权限校验
zuul的过滤器 ZuulProxy, 可以过滤客户端请求, 在过滤器中可以检查访问权限
创建filter过滤类
-
继承ZuulFilter并重写ZuulFilter的所有方法
@Component // 自动创建实例
public class AccessFilter extends ZuulFilter {
@Override
public String filterType() {
// return "pre";
return FilterConstants.PRE_TYPE; // 常量
}
@Override
public int filterOrder() {
return 6;
}
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
String serviceId = (String) currentContext.get(FilterConstants.SERVICE_ID_KEY);//"serviceId"
return "item-service".equals(serviceId);
}
@Override
public Object run() throws ZuulException {
// http://localhost:3001/item-service/dawdwa?token=dwadawdwad
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if (StringUtils.isBlank(token)) {
currentContext.setSendZuulResponse(false);
String json = JsonResult.build().code(400).msg("未登录").toString();// toString() : 变成json格式字符串
currentContext.addZuulResponseHeader("Content-Type"," application/Json; charset=UTF-8"); // 协议头属性
currentContext.setResponseBody(json); // 设置协议体属性
}
return null; // Zuul当前版本这个返回值不启任何作用, 所以返回null即可
}
}
内容解析
filterType()
方法: 设置过滤器的类型
- 一共提供了四种类型: pre, routing, post, error
- return “pre” : 设置为"pre"类型(字符串写法)
- return FilterConstants.PRE_TYPE; (常量写法) 两种完全一样
filterOrder()
: 设置顺序号
- 前置过滤器中有五个默认过滤器, 自定义的过滤器放到末尾
- return 6 设置为第6个过滤器
- 第五个过滤器: 在上下文对象中放入serviceId, 后面过滤器中才能访问这个数据
shouldFilter()
: 根据发起的请求, 设置是否执行过滤代码
- 比如:
调用商品需要判断权限, 调用用户或订单不检查权限
RequestContext.getCurrentContext()
: 获得请求上下文对象String serviceId =(String)currentContext.get(FilterConstants.SERVICE_ID_KEY);
: 从上下文对象获得调用的后台服务的serviceID"item-service".equals(serviceId);
: 判断调用的 Item-service 返回true
public Object run()
: 设计过滤代码
-
RequestContext currentContext = RequestContext.getCurrentContext();
: 获取上下文对象 -
HttpServletRequest request = currentContext.getRequest();
: 获取request对象 -
String token = request.getParameter("token");
: 接收 token 参数 -
if (StringUtils.isBlank(token)){}
: 判断token是否存在-
使用了StringUtils提供的方法(isBlank)
-
isBlank:
-
isEmpty:
-
-
-
如果为空, 证明不存在, 则执行里面代码
-
- ponse(false);`: 上下文对象的方法(设置发送Zuul响应(关闭)) 阻止继续调用
-
String json = JsonResult.build().code(400).msg("未登录").toString();
: 返回响应提示toString
: 将对象编程json字符串- 因为没有写用户登录, 使用自己编写的响应信息测试
-
currentContext.addZuulResponseHeader("Content-Type"," application/Json; charset=UTF-8");
设置协议头属性 -
currentContext.setResponseBody(json);
: 设置协议体属性
-
- ponse(false);`: 上下文对象的方法(设置发送Zuul响应(关闭)) 阻止继续调用
-
return null;
: 返回null ( Zuul当前版本这个返回值不启任何作用, 不管写什么都返回 null, 所以返回 null 即可)
测试
- 通过网关不携带token访问
GET http://localhost:3001/item-service/ty4g33t3
会返回提示信息: 未登录
- 通过网关携带token访问
得到商品信息:
Zuul 集成 ribbon
-
负载均衡默认启用
-
重试默认禁用
- 在最前面重试, 会造成后台服务器大面积压力倍增, 大面积出现故障
-
启用重试
-
添加 spring-retry依赖
-
yml 配置启用重试:
Zuul.retryable=true
-
添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置yml
# 对所有服务都有效
ribbon:
MaxAutoRetries: 1
# 对 item-service 单独配置
item-service:
ribbon:
MaxAutoRetries: 0
Zuul 集成 Hystrix
Hystrix是容错和限流工具
- Hystrix容错: 降级
- Hystrix限流: 熔断
降级操作
添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
新建fb包
- 新建ItemFB实现类
@Component
public class ItemFB implements FallbackProvider {
/*设置针对哪个后台服务进行降级
* - item-service: 只针对商品服务降级
* - * : 对所有服务
* - null : 对所有服务*/
@Override
public String getRoute() {
return "item-service";
}
/*向客户端返回的响应数据*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() { // 包含响应数据
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
// 用来关闭下面的流
// BAIS不占用系统资源, 不需要关闭
}
@Override
public InputStream getBody() throws IOException {
// JsonResult - {code:500, msg:xxx, data:null}
String json = JsonResult.build().code(500).msg("后台服务出错, 请稍后重试").toString();
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add("Content-Type","application/json;charset=UTF-8");
return h;
}
};
}
}
注意: ClientHttpResponse()
里重写的方法在new时会自己创建
- 创建orderFB
@Component
public class OrderFB implements FallbackProvider {
/*设置针对哪个后台服务进行降级
* - item-service: 只针对商品服务降级
* - * : 对所有服务
* - null : 对所有服务*/
@Override
public String getRoute() {
return "order-service";
}
/*向客户端返回的响应数据*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() { // 包含响应数据
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
// 用来关闭下面的流
// BAIS不占用系统资源, 不需要关闭
}
@Override
public InputStream getBody() throws IOException {
// JsonResult - {code:500, msg:xxx, data:null}
String json = JsonResult.build().code(500).msg("后台服务出错, 请稍后重试").toString();
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add("Content-Type","application/json;charset=UTF-8");
return h;
}
};
}
}
测试
http://localhost:3001/item-service/ty4g33t3?token=hhjgjhh
当访问设置了休眠的方法时不会再报错, 而是提示返回的出错信息
限流/熔断操作
当流量过大时, 后台服务出现故障, 可以断开链路, 限制后台故障服务的流量, 等待他从故障中恢复
- 断路器打开条件:
- 多少秒内的请求次数(必须首先满足)
- 请求出错率, 执行了降级代码
- 断路器打开以后, 会进入半开状态
- 在半开状态下, 会向后台服务尝试发送一次客户端调用
- 如果调用成功, 自动关闭断路器, 恢复正常
- 如果调用失败, 会继续保持打开状态, 一段时间后进入半开状态尝试调用
Hystrix dashboard 监控器
对Hystrix 降级和熔断的情况进行监控, 可以通过监控快速定位故障模块, (通过抓取服务的日志进行监控)
查看监控指标
暴露 actuator 监控指标
添加yml配置
management:
endpoints:
web:
exposure:
include: "*"
- “*” 表示所有的
hystrix dashboard 断路器仪表盘
- 创建 hystrix dashboard 工程
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<parent>
<artifactId>springcloud1</artifactId>
<groupId>cn.tedu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
- 新建的spring boot 项目
- 将parent标签改为工程父目录
- 使用generate可以一键生成
- 配置yml
server:
port: 4001
hystrix:
dashboard:
# 允许抓取的服务器列表
proxy-stream-allow-list: localhost
- 启动类添加 @EnableHystrixDashboard 注解
浏览器访问测试
- 数据监控路径
http://localhost:3001/actuator/hystrix.stream
- Eureka路径
http://localhost:4001/hystrix
-
打开数据监控路径
-
将数据监控路径复制到Eureka点击进入
Apache24 压力测试
- 在Apache24\bin目录下cmd
ab -n 20000 -c 50 http://localhost:3001/item-service/35?token=sdasad
- -c 并发请求次数 (并发50次)
- -n 一共发送多少次请求 (共发送20000次请求)
- 打开仪表盘查看
http://localhost:4001/hystrix
Turbine 聚合监控
聚合 Hystrix 监控数据, hystrix dashboard 仪表盘从 Turbine 抓取聚合后的数据
创建turbine工程
-
父工程创建springboot项目
-
添加依赖
-
<parent> <artifactId>springcloud1</artifactId> <groupId>cn.tedu</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <!-- <parent>--> <!-- <groupId>org.springframework.boot</groupId>--> <!-- <artifactId>spring-boot-starter-parent</artifactId>--> <!-- <version>2.5.6</version>--> <!-- <relativePath/> <!– lookup parent from repository –>--> <!-- </parent>-->
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency>
-
-
配置yml
-
server: port: 5001 spring: application: name: turbion eureka: client: service-url: defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka turbine: # 要聚合的服务 app-config: zuul # aaa, bbb, ccc (用逗号隔开可以写多个) # 起一个名字(默认: default) cluster-name-expression: new String("default") # turbine设计者喝酒了, 就这么设计的
-
-
启动类添加注解
@EnableTurbine
测试
-
访问http://localhost:4001/hystrix 仪表盘
-
添加http://localhost:5001/turbine.stream 监控地址