微服务框架springcloud(面试篇)——Sentinel源码分析

Sentinel的线程隔离与Hystix的线程隔离有什么差别?

线程隔离有两种方式实现:

线程池隔离(Hystix默认采用)

信号量隔离(Sentinel默认采用)

Sentinel的限流与Gateway的限流有什么差别?

限流   含义:对应用服务器的请求做限制,避免因过多请求而导致服务器过载甚至宕机。

限流算法常见的包括三种:

        计数器算法,又包括窗口计数器算法、滑动窗口计数器算法

        令牌桶算法(Token Bucket)

        漏桶算法(Leaky Bucket)

1. 固定窗口计数器算法

将时间划分为多个窗口,窗口时间跨度称为Interval,本例中为1000ms;

每个窗口维护一个计数器,每有一次请求就将计数器加一,限流就是设置计数器阈值,本例为3

如果计数器超过了限流阈值,则超出阈值的请求都被丢弃。

(把时间窗口固定了,但是时间是没有界限的,在4500-5500其实一共有6次请求,但是最高阈值是3,超过了上限,可能会给服务器带来压力,甚至使服务器宕机)

2. 滑动窗口计数器算法

会将一个窗口划分为n个更小的区间,例如

窗口时间跨度Interval为1秒;区间数量 n = 2 ,则每个小区间时间跨度为500ms

限流阈值依然为3,时间窗口(1秒)内请求超过阈值时,超出的请求被限流

窗口会根据当前请求所在时间(currentTime)移动,窗口范围是从(currentTime-Interval)之后的第一个时区开始,到currentTime所在时区结束。

(1250-2100之间差850ms在1s之内,说明1s之内有4个请求,这个时候就需要划分更小的区间,2100-1000为1100,这个时间段内有1250,1300,1600三个了,这个时候2100就为有问题的请求,超出阈值,窗口划分的越细,限流越准确 )

3.令牌桶算法

以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃

请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理

如果令牌桶中没有令牌,则请求等待或丢弃

4.漏桶算法

将每个请求视作"水滴"放入"漏桶"进行存储;

"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水”;

如果"漏桶"满了则多余的"水滴"会被直接丢弃。

可以理解成请求在桶内排队等待

Sentinel在实现漏桶时,采用了排队等待模式:

让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。并发的多个请求必须等待,预期的等待时长 =最近一次请求的预期等待时间 + 允许的间隔。如果请求预期的等待时间超出最大时长,则会被拒绝。

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常

限流算法对比


一、Sentinel的基本概念

Sentinel实现限流、隔离、降级、熔断等功能,本质要做的就是两件事情:

        统计数据:统计某个资源的访问数据(QPS、RT等信息)

        规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足

这里的  资源  就是希望被Sentinel保护的业务,例如项目中定义的controller方法就是默认被Sentinel保护的资源。

1.ProcessorSlotChain

实现上述功能的核心骨架是一个叫做ProcessorSlotChain(处理器插槽列)的类。这个类基于责任链模式来设计,将不同的功能(限流、降级、系统保护)封装为一个个的Slot,请求进入后逐个执行即可。

其工作流如图:

责任链中的Slot也分为两大类:

1)统计数据构建部分(statistic)

        NodeSelectorSlot:负责构建簇点链路中的节点(DefaultNode),将这些节点形成链路树

        ClusterBuilderSlot:负责构建某个资源的ClusterNode,ClusterNode可以保存资源的运行信息(响应时间、QPS、block 数目、线程数、异常数等)以及来源信息(origin名称)

        StatisticSlot:负责统计实时调用数据,包括运行信息、来源信息等

2)规则判断部分(rule checking)

        AuthoritySlot:负责授权规则(来源控制)

        SystemSlot:负责系统保护规则

        ParamFlowSlot:负责热点参数限流规则

        FlowSlot:负责限流规则

        DegradeSlot:负责降级规则

2.Node

Sentinel中的簇点链路是由一个个的Node组成的,Node是一个接口,包括下面的实现:

所有的节点都可以记录对资源的访问统计数据,所以都是StatisticNode的子类。

按照作用分为两类Node:

        DefaultNode:代表链路树中的每一个资源,一个资源出现在不同链路中时,会创建不同的DefaultNode节点。而树的入口节点叫EntranceNode,是一种特殊的DefaultNode
        ClusterNode:代表资源,一个资源不管出现在多少链路中,只会有一个ClusterNode。记录的是当前资源被访问的所有统计数据之和。

DefaultNode记录的是资源在当前链路中的访问数据,用来实现基于链路模式的限流规则。ClusterNode记录的是资源在所有链路中的访问数据,实现默认模式、关联模式的限流规则。

例如:我们在一个SpringMVC项目中,有两个业务:

        业务1:controller中的资源`/order/query`访问了service中的资源`/goods`
        业务2:controller中的资源`/order/save`访问了service中的资源`/goods`

创建的链路图如下:

3.Entry

默认情况下,Sentinel会将controller中的方法作为被保护资源,那么问题来了,我们该如何将自己的一段代码标记为一个Sentinel的资源呢?

Sentinel中的资源用Entry来表示。声明Entry的API示例:

// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
  // 被保护的业务逻辑
  // do something here...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 在此处进行相应的处理操作
}

1)自定义资源

例如,我们在order-service服务中,将`OrderService`的`queryOrderById()`方法标记为一个资源。

1] 首先在order-service中引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2] 然后配置Sentinel地址

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8089 # 这里我的sentinel用了8089的端口

3] 修改OrderService类的queryOrderById方法

代码这样来实现:

public Order queryOrderById(Long orderId) {
    // 创建Entry,标记资源,资源名为resource1
    try (Entry entry = SphU.entry("resource1")) {
        // 1.查询订单,这里是假数据
        Order order = Order.build(101L, 4999L, "小米 MIX4", 1, 1L, null);
        // 2.查询用户,基于Feign的远程调用
        User user = userClient.findById(order.getUserId());
        // 3.设置
        order.setUser(user);
        // 4.返回
        return order;
    }catch (BlockException e){
        log.error("被限流或降级", e);
        return null;
    }
}

4] 访问

打开浏览器,访问order服务:http://localhost:8080/order/101

然后打开sentinel控制台,查看簇点链路:

2)基于注解标记资源

在之前学习Sentinel的时候,我们知道可以通过给方法添加@SentinelResource注解的形式来标记资源。

这个是怎么实现的呢?

来看下我们引入的Sentinel依赖包:

其中的spring.factories声明需要就是自动装配的配置类,内容如下:

我们来看下`SentinelAutoConfiguration`这个类:

可以看到,在这里声明了一个Bean,`SentinelResourceAspect`:

/**
 * Aspect for methods with {@link SentinelResource} annotation.
 *
 * @author Eric Zhao
 */
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
	// 切点是添加了 @SentinelResource注解的类
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }
	
    // 环绕增强
    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        // 获取受保护的方法
        Method originMethod = resolveMethod(pjp);
		// 获取 @SentinelResource注解
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        // 获取注解上的资源名称
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
            // 创建资源 Entry
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            // 执行受保护的方法
            Object result = pjp.proceed();
            return result;
        } catch (BlockException ex) {
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }
}

简单来说,@SentinelResource注解就是一个标记,而Sentinel基于AOP思想,对被标记的方法做环绕增强,完成资源(`Entry`)的创建。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值