目录
1 sp06-zuul工程创建filter包,创建继承ZuulFilter过滤器的类
3.1 sp06工程pom.xml文件添加重试依赖,配置文件启动重试
二 搭建 Hystrix Dashboard 断路器仪表盘,对熔断进行监控
一 zuul 请求过滤,统一权限的校验
通过zuul的过滤器,判断用户权限
- 有权限,继续转发调用
- 没有权限,从网关直接返回结果
模拟判断是否登录:
- http://localhost:3001/item-service/123456 -----没有令牌代表没有登录,不允许访问
- http://localhost:3001/item-service/123456?token=u6y54t ---携带token,已登录,可访问
步骤:
1. 继承ZuulFilter 过滤器
2. 添加@Component注解,使spring自动创建实例
- Zuul的自动配置类,可以从spring容器自动发现过滤器实例,完成自动配置
1 sp06-zuul工程创建filter包,创建继承ZuulFilter过滤器的类
package cn.tedu.sp06zuul.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.lang3.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,routing,post,error
@Override
public String filterType() {
//return "pre";
return FilterConstants.PRE_TYPE;//返回字符串常量
}
//过滤器的顺序号
@Override
public int filterOrder() {
//有5个默认的过滤器,自己的过滤器加到末尾
return 6;//加到第六个,不能放到前五个之间
}
//针对当前请求,判断是否要执行过滤代码
/*如果调用item-service,检查是否登录
*否则,不检查登录,直接访问*/
@Override
public boolean shouldFilter() {
//1.先获取当前请求的上下文对象
// RequestContext它在web项目中传参时使用,涉及后端获取前端参数.
// 而Context为当前对象在程序中所处的一个环境,一个与系统交互的过程,
//存放一些数据,对象, 如果很多服务需要,都从这里面拿就行
RequestContext ctx = RequestContext.getCurrentContext();
//2.然后从当前请求,调用的服务id
String serviceId = (String)
ctx.get(FilterConstants.SERVICE_ID_KEY);//"serviceId",可能是商品/用户/订单
//3.判断调用的是否是item-service
return "item-service".equals(serviceId);
}
//过滤代码--进行权限判断
@Override
public Object run() throws ZuulException {
//1.获得上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
//2.获得 request 对象
HttpServletRequest request = ctx.getRequest();
//3.接受 token 参数
String token = request.getParameter("token");
//4.若没有token,就要阻止其继续调用,从网关直接返回响应
//""空串,null值," "都是没有token
if(StringUtils.isBlank(token)){
//如果没有token,阻止继续调用
ctx.setSendZuulResponse(false);//关闭发送zuul响应
//直接向客户端返回响应--JsonResult--{code:1,msg:xxx,data:null}
ctx.addZuulResponseHeader(
"Content-Type","application/json;charset=UTF-8");
ctx.setResponseBody(JsonResult
.err().code(400)
.msg("Not login! / 未登录!")
.toString());//转成json串
}
return null;//当前zuul版本中,这个返回值没有使用,没有任何作用,so返回null
}
}
启动服务,进行测试:
此处会发生超时504错误,需要反复刷新:
2 zuul 继承 ribbon的负载均衡和重试:
- zuul默认启动负载均衡,会设置两个或多个相同服务的id,其中一个失败,可以调用另一个的
- zuul默认不启用重试,zuul不推荐启用重试(所以,请求时会报504超时,是因为设置了90%的随机阻塞,需要反复刷新请求),一般不在网关执行重试,否则,会造成后台大面积服务器压力倍增,重试尽量往后放
3 zuul启用Ribbon的重试
步骤:
- 添加 spring-retry 依赖
- yml配置启用 zuul 重试: zuul.retryable=true
- (可选)配置重试参数
3.1 sp06工程pom.xml文件添加重试依赖,配置文件启动重试
测试:
默认共重试2次也失败,就需要反复刷新,才能获得
4 zuul 集成Hystrix的容错和限流
zuul默认已启用了Hystrix,可以直接写降级代码,降级用与容错;通过熔断实现限流
步骤:
- 实现 FallbackProvider 接口
- 添加@COnponent: zuul会用自动配置类,对降级类进行自动配置
4.1 容错降级的实现
第一步: 定义过滤器,继承 ZuulFilter
package cn.tedu.sp06zuul.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.lang3.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,routing,post,error
@Override
public String filterType() {
//return "pre";
return FilterConstants.PRE_TYPE;//返回字符串常量
}
//过滤器的顺序号
@Override
public int filterOrder() {
//有5个默认的过滤器,自己的过滤器加到末尾
//在第5个过滤器中,向Context对象放入了 serviceId,
//后面组件中,才能访问这个数据
return 6;//加到第六个,不能放到前五个之间
}
//针对当前请求,判断是否要执行过滤代码
/*如果调用item-service,检查是否登录
*否则,不检查登录,直接访问*/
@Override
public boolean shouldFilter() {
//1.先获取当前请求的上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
//2.然后从当前请求,调用的服务id
String serviceId = (String)
ctx.get(FilterConstants.SERVICE_ID_KEY);//"serviceId",可能是商品/用户/订单
//3.判断调用的是否是item-service
return "item-service".equals(serviceId);
}
//过滤代码--进行权限判断
@Override
public Object run() throws ZuulException {
//1.获得上下文对象
// RequestContext它在web项目中传参时使用,涉及后端获取前端参数
// 而Context为当前对象在程序中所处的一个环境,一个与系统交互的过程
存放一些数据,对象, 如果很多服务需要,都从这里面拿就行
RequestContext ctx = RequestContext.getCurrentContext();
//2.获得 request 对象
HttpServletRequest request = ctx.getRequest();
//3.接受 token 参数
String token = request.getParameter("token");
//4.若没有token,就要阻止其继续调用,从网关直接返回响应
//""空串,null值," "都是没有token
if(StringUtils.isBlank(token)){
//如果没有token,阻止继续调用
ctx.setSendZuulResponse(false);//关闭发送zuul响应
//直接向客户端返回响应--JsonResult--{code:1,msg:xxx,data:null}
ctx.addZuulResponseHeader(
"Content-Type","application/json;charset=UTF-8");
ctx.setResponseBody(JsonResult
.err().code(400)
.msg("Not login! / 未登录!")
.toString());//转成json串
}
return null;//当前zuul版本中,这个返回值没有使用,没有任何作用,so返回null
}
}
第二步: 调用商品的后台服务item-service,出错时调用此降级服务
package cn.tedu.sp06zuul.fb;
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,表示当前降级类是针对哪个后台服务的降级类
* item-service ----只针对商品服务降级
* *星号--对所有服务都应用这个降级类
* null---等同于*星号
* */
@Override
public String getRoute() {
return "item-service";
}
//调用后台服务出错,发送降级响应给客户端
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
//比如:返回封装 500 error 的对象
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
//只返回 500 状态
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
//只返回 error 文本
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//JsonResult ----{code:500,msg:xxx,data:null}
String json = JsonResult.
err().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;
}
};
}
}
如果调用订单服务,就编辑一个订单服务的容错降级类
package cn.tedu.sp06zuul.fb;
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;
/**调用订单的后台服务,出错时调用此降级服务*/
@Component
public class OrderFB implements FallbackProvider {
/**返回一个 service-id,表示当前降级类是针对哪个后台服务的降级类
* item-service ----只针对商品服务降级
* *星号--对所有服务都应用这个降级类
* null---等同于*星号
* */
@Override
public String getRoute() {
return "order-service";
}
//调用后台服务出错,发送降级响应给客户端
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
//比如:返回封装 500 error 的对象
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
//只返回 500 状态
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
//只返回 error 文本
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//JsonResult ----{code:500,msg:xxx,data:null}
String json = JsonResult.
err().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;
}
};
}
}
第三步:访问测试
-
没有token参数不允许访问
http://localhost:3001/item-service/35 -
有token参数可以访问,出现容错降级,需要快速刷新才会有机会获取商品服务列表
http://localhost:3001/item-service/123456?token=123456
4.2 hystrix限流--熔断
1.当流量过大,后台服务器出现故障,可以断开后台服务器的链路,等待后台服务恢复
2.短路器的打开条件:
- 10秒20次请求(必须首先满足20次请求)
- 再判断是否是50%失败,是50%才能执行的降级代码(商品服务设置了90%的阻塞,满足了此要求)
3.半开状态:
- 断路状态打开一段时间后,会进入半开状态
- 会尝试发生一次客户端调用,若成功,则关闭断路器,恢复正常;若失败,则继续保持打开状态
测试:F5快速刷新请求,转圈在请求属于半开状态,出现500,属于短路状态,但是此测试效果不明显,需要hstrix监控,使用图标展现
4.3 Hystrix 熔断的监控
对Hystrix降级和熔断的情况进行监控,可以快速定位错误,查找系统中的问题.
在sp06工程中,Hystrix利用actuator来暴露自己的监控日志数据:
- 添加actuator 依赖,(这个依赖已经集成在了zuul依赖中了)
- yml配置暴露监控日志
#暴露所有监控日志
m.e.w.e.i="*"
m.e.w.e.i=health,bean,mappings
m.e.w.e.i=hystrix.stream
3.
第一步:pom文件添加actuator 依赖,(这个依赖已经集成在了zuul依赖中了)
第二步:配置文件设置暴露所有监控日志
management:
endpoints:
web:
exposure:
include: "*"
第三步:保持六个服务启动状态,sp06工程重启,访问url查看所有监控日志
http://localhost:3001/actuator ,所有的监控url都在这里面
访问熔断日志:
第四步: 访问http://localhost:3001/item-service/123456?token=123456
再访问http://localhost:3001/actuator/hystrix.stream ,访问什么服务就会有什么日志
二 搭建 Hystrix Dashboard 断路器仪表盘,对熔断进行监控
1. 新建 spring模块: sp07-hystrix-dashboard
2. 添加依赖: Hystrix Dashboard
3. yml配置文件配置:
允许抓取的服务器列表: localhost,a,b,c
4. 启动类注解: @EnableHystrixDashboard
5. 访问: http://localhost:4001/hystrix ,在输入框中填写日志数据的地址
第一步:新建sp07工程
不加任何依赖直接finish
第二步:修改parent标签的版本
添加依赖,以及依赖版本设置:
#加在dependencies标签
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
# dependencyManagement标签
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第三步: 修改配置文件的后缀为.yml,编辑允许抓取服务器的列表
server:
port: 4001
hystrix:
dashboard:
#允许抓取的服务器列表
proxy-stream-allow-list: localhost
第四步:启动类添加@EnableHystrixDashboard 注解,搭建仪表盘
第五步:启动启动类,访问 localhost:4001/hystrix
第六步:http://localhost:3001/actuator ,能看到监控所有服务,访问熔断的url
http://localhost:3001/actuator/hystrix.stream 再复制粘贴到hystrix dashboard的url地址
monitor stream进入监控日志仪表盘
第七步:访问服务,hystrix进行输出日志,hystrix dashboard仪表盘进行日志监控
第八步: 在Apache24 的bin目录下,cdm启动dos窗口命令,测试
- 用 ab 工具,以并发50次,来发送20000个请求
ab -n 20000 -c 50 http://localhost:3001/item-service/123456?token=123456
- 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法
- 断路状态打开一段时间后,会进入半开状态
- 会尝试发生一次客户端调用,若成功,则关闭断路器,恢复正常;若失败,则继续保持打开状态