spring cloud Alibaba 容错服务-Sentinel学习笔记七
容错服务-Sentinel
- 一、雪崩效应
- 二、Sentinel实现容错
- 三、容错规则
- sentinel配置规则
- 代码配置规则
- 四、sentinel与控制台通信原理剖析
- 五、控制台相关配置项
- 六、sentinel API详解(自定义异常提示)
- 七、sentinel Resource注解详解(自定义异常提示)
- 八、Template整合sentinel
- 九 、feign整合sentinel
- 十、规则持久化
- 十一、集群流控
- 十二、sentinel扩展
- 十三、[Alibaba Sentinel 配置项总结](https://www.imooc.com/article/289562)
一、雪崩效应
英文名为cascading failure,也叫级联失效,级联故障;每个微服务并不是100%可用,网络也有可能出问题,如有一个高并发的微服务系统,如下图,包含四个微服务,开始都为正常,在某个时间点,当A挂了,而此系统为高并发系统,B服务疯狂调用A服务,而A挂了,B发往A的请求就会强制等待,知道请求超时,在java程序中,一次请求往往对应一个线程,请求强制等待,线程就会强制阻塞,一直等到线程超时,才会被释放,在高并发情况下,阻塞线程越来越多,而线程对应的又是服务器计算资源,如不做任务处理,随着积累,B服务将无法创建线程,B服务也挂了,同理,C、D也疯狂请求B,C、D也会因为同样原因而不可用,这就是雪崩效应;导致雪崩效应的原因就是服务消费者未做好容错措施
1、业界常用容错方案
- 设置请求超时时间
- 设置限流,根据最大qbs,限流,超出后不再接受请求
- 仓壁模式:如ShareController有一个thread-pool-1独立线程池,coreSize=10;TestController也有一个thread-pool-x线程池;当线程池1满了,将拒绝访问请求,不会影响线程x运行
- 断路器模式:
① 现实中例子:监控+开关,实时监控电路状态,当发现某段时间电流过大,认为电路短路,将会自动跳闸,保证电路不被烧毁
② 代码例子:某服务接口5秒以内请求错误率、错误次数达到阈值,就跳闸;而该接口又恢复正常,该怎么办?断路器模式巧妙设计了一种半开状态,如下图
当请求错误率达到阈值,断路器打开,然后会有一个短暂的窗口期,允许服务请求一次,若失败,则拒绝服务;果断时间又打开窗口,允许服务请求一次,成功,则断路器关闭,允许请求,达到自我修复
PS:四种方案思想,超时,释放够快,就不那么容易死;限流,只有一碗饭量,哪怕给三碗,也只吃一碗;仓壁模式,不把鸡蛋放一个篮子里,你有你的线程池,我有我的线程池;断路器模式,监控+开关,当监控API达到一定预值,就跳闸
二、Sentinel实现容错
轻量级的流量控制、熔断降级java库
1、整合sentinel
加依赖:通过 /actuator/sentinel 暴露端点进行容错启用验证
org.springframework.cloud
spring-cloud-starter-alibaba-sentinel
0.2.0.RELEASE
org.springframework.boot
spring-boot-starter-actuator
actuator配置,将隐藏的端点暴露出来
- 加注解
- 写配置
2、Sentinel控制台
- 选择最新的jar包下载到本地,然后用java -jar xxx.jar启动控制台 控制台下载地址
- 为内容中心整合控制台:
访问localhost:8080,发现列表为空,因为sentinel是懒加载,内容中心发送请求后,列表就会显示内容中心微服务信息
三、容错规则
sentinel配置规则
1、流控规则
- 关联:用于保护资源,如资源为查询,关联资源为修改,若修改过于频繁,就会限制查询,是保护关联资源的一种设计
- 链路:细粒度配置,直接、关联都是微服务级别,链路则细分至API级别
2、降级规则(断路器模式)
3、热点规则(特殊的流控规则):可对参数限流,亦或是对参数的值进行限流;适用于某些参数传递频繁,但有希望提高接口可靠性的场景;热点参数必须是基本类型或者String,否则不会生效
4、系统规则-阈值类型
5、授权规则:对消费者进行授权控制,白名单,则是允许test微服务访问内容中心 /shares/1 API,黑名单则是不允许访问
代码配置规则
Alibaba Sentinel 规则参数总结
四、sentinel与控制台通信原理剖析
控制台是如何获取到微服务的监控信息?用控制台配置规则时,控制台是如何将规则发送到各个微服务?
- sentinel 与 控制台通信:sentinel实现了一套服务发现机制
- 监控信息:通过调用API实现
五、控制台相关配置项
- 应用端连接控制台配置项
- 控制台配置项
配置使用
六、sentinel API详解(自定义异常提示)
- SphU:定义资源,让资源受到监控,并且可以保护资源
- Tracer:对想要的异常进行统计
- ContextUtil:可以实现调用来源,还可以标记调入
PS:代码示例
@GetMapping("/test-sentinel-api")
public String testSentinelApi(@RequestParam String a){
//定义一个sentinel保护资源,名称为test-sentinel-api
String resourceName = "test-sentinel-api";
//标记资源
ContextUtil.enter(resourceName,"test-wfw");
Entry entry = null;
try {
entry = SphU.entry(resourceName);
if(StringUtils.isBlank(a)){
throw new IllegalArgumentException("a不能为空!");
}
return a;
} catch (BlockException e) {
e.printStackTrace();
return "限流或者降级了";
}catch (IllegalArgumentException e2){
Tracer.trace(e2);
return "非法参数";
}finally {
if(entry != null){
entry.exit();
}
ContextUtil.exit();
}
}
七、sentinel Resource注解详解(自定义异常提示)
PS:让代码更加优雅,参考手记
PS:代码示例
/** * sentinel 注解 * @param a * @return */
@GetMapping("/testSentinelResource")
@SentinelResource(
value = "test-sentinel-resource",
blockHandler = "block",
blockHandlerClass = TestControllerBlockHandler.class,
fallback = "fallback",
defaultFallback = "defaultFallback",
fallbackClass = TestControllerFallback.class
)
public String testSentinelResource(@RequestParam String a){
if(a.equals("0")){
throw new IllegalArgumentException("a不能为0!");
}
return a;
}
public class TestControllerFallback {
public static String defaultFallback(String a,Throwable throwable){
return "降级,或者其他异常:" + throwable.getMessage();
}
public static String fallback(String a,Throwable throwable){
return "指定异常:" + throwable.getMessage();
}
}
public class TestControllerBlockHandler {
public static String block(String a, BlockException e){
return "限流或者降级了,block";
}
}
八、Template整合sentinel
加注解
//在spring容器中,创建一个对象,类型RestTemplate,名称/id为方法名
//相当于传统
@Bean
@LoadBalanced
@SentinelRestTemplate(blockHandler = "block",
blockHandlerClass = TestControllerBlockHandler.class,
fallback = "fallback",
fallbackClass = TestControllerFallback.class
)
public RestTemplate restTemplate(){return new RestTemplate();
}
开关
Template整合sentinel 开关
resttemplate:
sentinel:enabled: true
相关源码:涉及Spring生命周期、反射、拦截器,其中不懂的知识点可以重新复习
九 、feign整合sentinel
- 加注解
限流降级发生时,如何定义自己的处理逻辑
① 在用户中心 feign客户端进行fallback配置
② 新建类实现 feign客户端接口package com.hzb2i.contentcenter.feignClient;
import com.hzb2i.feignapi.domain.dto.user.UserDto;
import org.springframework.stereotype.Component;@Component
public class UserCenterFeignClientFallback implements UserCenterFeignClient{@Override
public UserDto findById(Integer id) {
UserDto userDto = new UserDto();
userDto.setWxNickname("默认用户");
return userDto;
}
@Override
public UserDto query(Integer id, String userName) {
UserDto userDto = new UserDto();
userDto.setWxNickname("默认用户");
return userDto;
}
}
如何获取异常
① 在用户中心 feign客户端进行fallbackFactory配置
② 实现FallbackFactory类package com.hzb2i.contentcenter.feignClient;
import com.hzb2i.feignapi.domain.dto.user.UserDto;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class UserCenterFeignClientFallbackFactory implements FallbackFactory{ @Override
public UserCenterFeignClient create(Throwable throwable) {
return new UserCenterFeignClient() {
@Override
public UserDto findById(Integer id) {
log.error("限流或降级:" + throwable);
UserDto userDto = new UserDto();
userDto.setWxNickname("默认用户");
return userDto;
}
@Override
public UserDto query(Integer id, String userName) {
log.error("限流或降级:" + throwable);
UserDto userDto = new UserDto();
userDto.setWxNickname("默认用户");
return userDto;
}
};
}
}
PS:sentinel小结
十、规则持久化
从测试反馈,微服务每次重启,规则就消失了,每次都需要重新配置,不适用于生产,需进行规则持久化
- 拉模型:Alibaba Sentinel规则持久化-拉模式-手把手教程【基于文件】
- 推模式:Alibaba Sentinel规则持久化-推模式-手把手教程【基于Nacos】
PS:生产环境使用sentinel
- 推拉模式持久化规则:推模式更佳
- AHAS:开通地址
十一、集群流控
十二、sentinel扩展
错误页提示优化:对异常进行细分(衔接六 sentinelAPI和七 sentinel Resource注解)
① 打开spring MVC端点保护
默认错误提示:
② 实现BlockExceptionHandler,对异常提示进行优化:@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
ErrMsg msg = null;
if(e instanceof FlowException){ //限流异常
msg = ErrMsg.builder().status("100").msg("限流了").build();
}
else if(e instanceof DegradeException){ //降级异常
msg = ErrMsg.builder().status("101").msg("降级了").build();
}
else if(e instanceof ParamFlowException){ //热点异常
msg = ErrMsg.builder().status("102").msg("热点参数限流").build();
}
else if(e instanceof SystemBlockException){ //系统异常
msg = ErrMsg.builder().status("100").msg("系统规则不满足").build();
}
else if(e instanceof AuthorityException){ //权限异常
msg = ErrMsg.builder().status("100").msg("授权规则不通过").build();
}
//http状态码
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-type","application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(
httpServletResponse.getWriter(),
msg
);
}
}
@Data
@Builder
@AllArgsConstructor
class ErrMsg{private String status;
private String msg;
}
优化后提示:
区分来源,异常提示优化
@Component
public class MyRequestOriginParser implements RequestOriginParser {@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getParameter("origin");
if(StringUtils.isBlank(origin)){
throw new IllegalArgumentException("origin must be special!");
}
return origin;
}
}
测试:针对browser进行流控,访问地址参数带?origin=browser则会被限流,其他传值则不会限流
PS:不建议将origin参数放在url地址中,可放在header头部里
- restfulUrl支持,实现URLCleaner接口,通过处理返回相同的url路径
处理前:
处理后:
@Slf4j
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String s) {
log.info(s);
String[] split = s.split("/");
return Arrays.stream(split).map(string -> {
if(StringUtils.equals(string,"{shareId}")){
//if(NumberUtils.isNumber(string)){
return "{number}";
}
return string;
}).reduce((a,b) -> a + "/" + b).orElse("");
}
}
PS:透过现象看本质,核心是CommonFilter过滤器
还没有评论,来说两句吧...