Featured image of post 微服务保护 - Sentinel

微服务保护 - Sentinel

微服务保护 - Sentinel

雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用

服务A依赖服务B

服务B挂掉了,但此时连接没有放开

服务A tomcat资源耗尽,A服务也接着挂掉

解决方式

  • 超时处理: 不无休止等待,直接返回错误信息
  • 舱壁模式: 限定每个业务能使用的线程数。避免耗尽整个tomcat的资源。(线程隔离)
  • 熔断降级: 由断路器统计业务执行的异常比例,超出阈值则会熔断该业务,拦截访问该业务的一切请求
  • 流量控制: 限制业务访问的QPS,避免服务因流量的突增而故障

前三种是避免故障传递到其他服务,最后一种是预防服务故障

服务保护技术对比

Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于异常比例或慢调用比例 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于 RxJava)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件化的形式
基于注解的支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持
流量整形 支持慢启动、匀速排队模式 不支持
系统自适应保护 支持 不支持
控制台 开箱即用,可以配置规则,查看秒级监控,机器发现等 不完善
常见的框架匹配 Servlet, Spring Cloud, Dubbo, gRPC 等 Servlet, Spring Cloud Netflix

如何使用 Sentinel

在微服务中

引入依赖

spring-cloud-starter-alibaba-sentinel

配置文件

spring:
	cloud:
		sentinel:
			transport:
				dashboard: localhost:8000

访问微服务的任意endpoint, 出发sentinel监控

限流规则

簇点链路: 项目内的调用链路,链路中被监控的每个接口就是一个资源。

默认sentinel会监控SpringMVC的每个端点(endpoint)

  • SpringMVC的每个端点(endpoint)就是调用链路中的一个资源

规则: 流控,熔断,它们都是针对簇点链路中的资源来限制的

流控模式

  • 目标:统计当前资源的请求,触发阈值对资源直接限流,也是默认的模式
  • 关联:统计当前资源相关联的另一个资源,触发阈值时,对当前资源限流
  • 统计:统计从指定链路访问本资源的请求,触发阈值时,对指定链路限流

使用场景 - 关联:

用户支付时会修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是有限支付和更新订单的业务,因此当修改订单业务出发阈值时,需要对查询订单业务限流。

/order/update 资源被访问的QPS超过5时,对/order/query 请求限流 (在query中设置)

使用场景 - 链路:

有查询订单和创建订单业务,两者都需要查询商品。针对查询订单进入到查询商品的请求统计,并设置限流

有两条请求链路:

/order/query -> /querygoods

/order/save -> /querygoods

如果只希望统计从/order/query进入到/querygoods的请求,则对/querygoods 资源进行配置

链路入口设置成/order/query

注意: Sentinel默认会将Controller方法做context整合,导致链路模式的流控失效,需要修改applicaiton.yml

spring:
	cloud:
		sentinel:
			web-context-unify: false

流控效果

  • 快速失败: 达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。默认
  • warm up: 预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
  • 排队等待: 让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长。

warm up:

初始值: threshold / coldFactor,持续指定时长后,逐渐提高到threshould值

排队等待:

后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

比如qps限制为5,就意味着每200ms处理一个请求; timeout = 2000 意味着预期等待超过2000ms的请求会被拒绝并抛出异常

image-20240218180049947

热点参数限流

分别统计参数值相同的请求,判断是否超过QPS阈值

比如/goods/{id}, id=1请求了3次,id=2请求了1次

参数值 QPS
id = 1 3
id = 2 1

配置:

参数索引: 对这个参数的 i 号参数做统计 (从0开始)

  • 单机阈值 = 5,统计窗口时长=1 时意味着每1秒相同参数值的请求数不能超过5

参数例外: 对某个特定的参数 (常用于限制某个火爆商品) 进行限流

  • 参数类型,参数值,限流阈值。这里的限流阈值是qps

注意: 热点参数限流对默认的SpringMVC资源无效

  • 必须有 @SentinalResource([name]) 注解的才有效

隔离和降级

虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为其他原因而故障。而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了。

线程隔离和熔断降级都是对客户端 (调用方) 的保护

FeignClient 整合 Sentinel

feign:
	sentinel:
		enabled: true

给FiegnClient编写失败后的降级逻辑

  1. FallbackClass 无法对远程调用的异常做处理
  2. FallbackFactory 可以对远程调用的异常做处理

FallbackkFactory实现

public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        // 创建UserClient实例失败时,实现降级的逻辑
        return new UserClient() {
            @Override
            public User findById(Long id) {
                // 记录异常信息
                log.error("查询用户异常回退", throwable);
                // 返回空用户对象以避免前端错误
                return new User();
            }
        };
    }
}

在DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean

@Bean
public UserClientFallbackFactory userClientFallback(){
	return new UserClientFallbackFactory();
}

在UserClient接口中使用UserClientFallbackFactory

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

线程隔离

  • 线程池隔离
    • 支持主动超时和异步调用
    • 线程额外开销比较大
    • 适用低扇出场景: 类比扇子,扇子越大,根部连接扇子的线就越多
  • 信号量隔离: 一个计数器,每请求一次服务计数器-1
    • 轻量级,无额外开销
    • 不支持主动超时或异步调用
    • 适用高频调用,高扇出场景

image-20240218202443246

熔断降级

断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务回复时,断路器会放行该服务的请求。

image-20240218203134819

熔断策略

  • 慢调用
  • 异常比例
  • 异常数

授权规则

对调用方的来源做控制

  • 有黑名单和白名单两种模式

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的

public interface RequestOriginParser {
    /**
     * 解析request对象中获取origin, 获取方式自定义
     */
    String parseOrigin(HttpServletRequest request);
}
@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getHeader("origin");
        if(StringUtils.isEmpty(origin)){
            return "blank";
        }
        return origin;
    }
}

网关可以加入一个Header,方便sentinel进行识别。

在gateway服务中:

spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway

自定义异常结果

默认都会抛出异常到接收方

public interface BlockExceptionHandler {
    /**
     * 处理请求被限流、降级、熔断时的处理器:BlockException
     */
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
英文 中文
FlowException 限流异常
ParamFlowException 热点参数限流的异常
DegradeException 降级异常
AuthorityException 授权规则异常
SystemBlockException 系统规则异常

示例

@Component
public class SentinelBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse, 
        BlockException e) throws Exception {
        
        String msg = "未知异常异常";
        int status = 429;
        if (e instanceof FlowException) {
            msg = "流控限流异常了!";
        } else if (e instanceof DegradeException) {
            msg = "降级降级异常了!";
        } else if (e instanceof ParamFlowException) {
            msg = "热点参数限流异常!";
        } else if (e instanceof AuthorityException) {
            msg = "授权授权异常!";
            status = 401;
        }
        
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setStatus(status);
        httpServletResponse.getWriter().println("{\"message\":\"" + msg + "\", \"status\":" + status + "}");
    }
}

规则持久化

规则管理模式

  • 原始模式: 将规则保存在内存,重启服务会丢失
  • pull模式: 控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则
    • image-20240218205653042
  • push模式: 控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新
    • image-20240218205819440
Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
Last updated on Feb 19, 2024 15:18