下游服务挂了造成雪崩
微服务间的依赖.png
- 请求进来访问 A,A 需要调 B,C,D;
- D 挂了;
- A 调 D 的线程就挂起;
- 不停的有请求访问 A,A 就不停的开新线程调 D;
- 当 A 开的线程数超过阈值,A 也失去响应了;
- 依赖 A 的服务慢慢的也失去响应,造成连锁的反应;
- 根上其实就是服务 D 不行了,但 D 造成了和它有依赖关系的服务都不行了;
- 这就是雪崩;
- 要避免雪崩,就是当 D 不行了,不会把调用它的服务都拖死;
熔断器
熔断器的加入.png
- 上游服务和下游服务之间加了一个熔断器;
- 熔断器平时处于关闭状态;
- A 一旦发现 D 不可用了,熔断器就会打开,此时,A 再调 D 的时候,发现 D 不行了,请求不会再发到 D 上,而是从熔断器直接就返回了;
- A 不会再等 D 的响应,后面再有访问 A 的请求,也不会造成线程的堆积;
熔断器详解图
熔断器详解图.png
- 熔断器其实是一个 3 个状态的状态机;
- 请求进来熔断器,如果熔断器是关闭状态,请求就会放给后面的服务,后面的服务如果正常返回 ,没问题;如果后面的服务出现问题,比如数据库连不上了,抛异常了,网络抖动,服务压力大导致响应时间变长,服务超时,在熔断器这都会做一个统计;熔断器一共放过去 10 个请求,5 个超时,3 个报错;可以配置熔断器,在什么情况下,从关闭状态到打开状态,比如 1s 过去 10 个请求,有 5 个超时,就把熔断器打开;
- 如果熔断器打开,请求进入熔断器,从熔断器就直接返回了;
- 如果后面的服务是因为网络抖动,服务压力瞬间变大,当熔断器打开一段时间后,后面的服务会自己恢复,这时候,需要一个机制,让熔断器知道后面的服务正常了,让熔断器重新回到关闭状态;这个机制就是一个定时任务,定时的时间是可以配置的,当时间到了,熔断器会变成版打开状态,放一个请求给后面的服务,如果后面的服务成功处理了这个请求,熔断器就从半打开状态变成关闭状态,请求后可以到达后面的服务了;如果放过去的请求还是失败,熔断器回到打开状态,等待下一个时间窗口,尝试下一次试探,直到试探成功;
降级
- 降级就是,当熔断器打开的时候,可以指定一个更简化的处理;
- 比如后面的服务是一个推荐服务,如果后面的服务挂了,就把预先设计好的列表,响应给请求;
- 用户此时看到的列表,不是针对他实时计算推荐出来的,而是预先设定好的;
- 降级是将的响应的质量,但不会造成用户端没有响应;
用代码配置 Sentinel 的熔断规则
Sentinel 的三种失败指标
- 一秒内(请求数大于等于 5)响应时间超过某个值:RuleConstant.DEGRADE_GRADE_RT;
- 一秒内(请求数大于等于 5)的错误比率:RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;
- 一分钟内的错误数量:RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT;
代码
@Component
public class SentinelConfig implements ApplicationListener<ContextRefreshedEvent> {
/**
* 整个系统启动好了之后,会这行整个方法;
* 这里要声明一个规则;
* 这里是用代码的方式控制规则的制定,这种方式的控制程度是最高的,所有的细节都可以控制,缺点就是麻烦;
* @param contextRefreshedEvent
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
fusingRule();
}
/**
* 熔断规则
*/
private void fusingRule() {
DegradeRule rule = new DegradeRule();
rule.setResource("createOrder");
// 1s 内的请求数超过 5 的情况下,响应时间超过多少,打开熔断器;
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 如果响应时间超过 10ms,就记一次数;如果 1s 内有 5 个请求,5 个请求都超时了,就熔断;
rule.setCount(10);
// 熔断的持续时间为 10s,熔断时间超过 10s 就进入半打开状态;
rule.setTimeWindow(10);
List<DegradeRule> degradeRules = new ArrayList<>();
degradeRules.add(rule);
DegradeRuleManager.loadRules(degradeRules);
}
}
使用 Sentinel 实现降级
正常的业务方法
@PostMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
@SentinelResource(value = "createOrder", blockHandler = "doOnBlock")
public OrderInfo create(@RequestBody OrderInfo info, @AuthenticationPrincipal String username)
throws InterruptedException {
// 这里的代码就是资源 createOrder
log.info("user is " + username);
Thread.sleep(50);
return info;
}
发生熔断后的降级方法
/**
* 降级方法:一定是 public,返回值和参数要和
* @SentinelResource(value = "createOrder", blockHandler = "doOnBlock") 标记的方法一样,只是多出一个参数;
* @param info
* @param username
* @param exception
* @return
* @throws InterruptedException
*/
public OrderInfo doOnBlock(@RequestBody OrderInfo info,
@AuthenticationPrincipal String username,
BlockException exception) throws InterruptedException {
// 降级逻辑,比如返回预先准备好的数据啥的
log.info("Blocked by : " + exception.getClass().getSimpleName());
return info;
}
网友评论