4.Sentinel 소스 코드 분석 - 센티넬은 어떻게 다운 그레이드하는 것입니다?

해피 중순 가을 축제, 아, 나는 ~ 내 마음이 행복 표현, 보름달 밤에 소스 해상도를 쓸 필요를 느끼지

센티넬 소스 분석 시리즈 :

1.Sentinel 소스 코드 분석 -FlowRuleManager 규칙을 어떤 한로드?

2. 센티넬 소스 코드 분석 -Sentinel 어떻게 트래픽 통계입니까?

3. 센티넬 소스 코드 분석 - QPS 트래픽 제어 방법을 달성하는 것입니다?


안에 내 두 번째 기사에서는 어떻게 트래픽 통계 2. 센티넬 소스 코드를 분석 -Sentinel? 어떤 센티넬처럼 걸쳐 주요 프로세스에 대해 설명합니다. 그래서 일반적으로 다음과 같이 처리가 요약 될 수 그레이드
1 세트 하향 전략은 평균 응답 시간 또는 비정상적 비율에 따라 다운 그레이드하는 것
같이 자원 슬롯들의 시리즈를 생성하는 2
, 3 슬롯 전화 차례로 슬롯들의 세트에 따라 유형의 다운 그레이드

의는, 예를 살펴 자신의 브레이크 포인트 추적을 용이하게하기 위해 보자 :

private static final String KEY = "abc";
private static final int threadCount = 100;
private static int seconds = 60 + 40;

public static void main(String[] args) throws Exception {
         
        List<DegradeRule> rules = new ArrayList<DegradeRule>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);
        // set threshold rt, 10 ms
        rule.setCount(10);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);

    for (int i = 0; i < threadCount; i++) {
        Thread entryThread = new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    Entry entry = null;
                    try {
                        TimeUnit.MILLISECONDS.sleep(5);
                        entry = SphU.entry(KEY);
                        // token acquired
                        pass.incrementAndGet();
                        // sleep 600 ms, as rt
                        TimeUnit.MILLISECONDS.sleep(600);
                    } catch (Exception e) {
                        block.incrementAndGet();
                    } finally {
                        total.incrementAndGet();
                        if (entry != null) {
                            entry.exit();
                        }
                    }
                }
            }
        });
        entryThread.setName("working-thread");
        entryThread.start();
    }
}

다른 프로세스 기본적으로 두 번째 기사의 주요 흐름 센티넬을 소개하는 유사한 기사를 설명하고, 센티넬에 DegradeSlot에서 작동하는 모든 강등 전략이다.

DegradeSlot

public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
        throws Throwable {
        DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
}

직접 DegradeRuleManager 다운 그레이드 작업을 호출 DegradeSlot, 우리는 DegradeRuleManager.checkDegrade 방법으로 직접 이동합니다.

DegradeRuleManager # checkDegrade

public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
    throws BlockException {
    //根据resource来获取降级策略
    Set<DegradeRule> rules = degradeRules.get(resource.getName());
    if (rules == null) {
        return;
    }
    
    for (DegradeRule rule : rules) {
        if (!rule.passCheck(context, node, count)) {
            throw new DegradeException(rule.getLimitApp(), rule);
        }
    }
}

이 방법의 논리는 무엇보다도 그것이 거짓 다음에 슬로우 다운 그레이드를 반환하는 경우, passCheck을 자원 이름에 따라 규칙을 다운 그레이드 등록 얻을, 다음 규칙 집합 호출 규칙을 통과하는 것입니다, 아주 분명하다.

DegradeRule # passCheck

public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
    //返回false直接进行降级
    if (cut.get()) {
        return false;
    }
    //降级是根据资源的全局节点来进行判断降级策略的
    ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
    if (clusterNode == null) {
        return true;
    }
    //根据响应时间降级策略
    if (grade == RuleConstant.DEGRADE_GRADE_RT) {
        //获取节点的平均响应时间
        double rt = clusterNode.avgRt();
        if (rt < this.count) {
            passCount.set(0);
            return true;
        }
        //rtSlowRequestAmount默认是5
        // Sentinel will degrade the service only if count exceeds.
        if (passCount.incrementAndGet() < rtSlowRequestAmount) {
            return true;
        }
        //    根据异常比例降级
    } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
        double exception = clusterNode.exceptionQps();
        double success = clusterNode.successQps();
        double total = clusterNode.totalQps();
        // If total amount is less than minRequestAmount, the request will pass.
        if (total < minRequestAmount) {
            return true;
        }

        // In the same aligned statistic time window,
        // "success" (aka. completed count) = exception count + non-exception count (realSuccess)
        double realSuccess = success - exception;
        if (realSuccess <= 0 && exception < minRequestAmount) {
            return true;
        }

        if (exception / success < count) {
            return true;
        }
        //    根据异常数降级
    } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
        double exception = clusterNode.totalException();
        if (exception < count) {
            return true;
        }
    }
    //根据设置的时间窗口进行重置
    if (cut.compareAndSet(false, true)) {
        ResetTask resetTask = new ResetTask(this);
        pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
    }

    return false;
}

그 후, 직류 전류 제한 동작에 해당하는 경우이 방법은 우선, 절단 값을 얻는다. 그럼 당신의 ClusterNode 글로벌 노드를 기반으로 자원을 얻을 것이다. 아래로 각각 하향 조정하기 위해 세 가지 전략에 따라.

응답 시간 DEGRADE_GRADE_RT 다운 그레이드

if (grade == RuleConstant.DEGRADE_GRADE_RT) {
    //获取节点的平均响应时间
    double rt = clusterNode.avgRt();
    if (rt < this.count) {
        passCount.set(0);
        return true;
    }
    //rtSlowRequestAmount默认是5
    // Sentinel will degrade the service only if count exceeds.
    if (passCount.incrementAndGet() < rtSlowRequestAmount) {
        return true;
    } 
}

응답 시간이 저하되면, 평균 응답 시간 passCount 크면 5 이상 (기본 밀리 초) 후 호출 passCount 플러스 1 설정 횟수보다 크면, 더 적은 직접 저하 '라면, CLUSTERNODE 평균 응답 시간을 획득한다.

그래서 우리는 응답이 즉시 다운 그레이드하지 않는 경우에도 평균 응답 시간 전에 여러 요청에 따라하는 것은 너무 오래 이관되는 것을 알아야한다, 그러나 여섯 번째 요청의 도착이 다운 그레이드 될 때까지 기다리려면 여기를 참조하십시오.

우리는 avgRt의 CLUSTERNODE 방법은 평균 응답 시간 CLUSTERNODE을 얻는 방법에 보이는에 입력합니다.

예를 들면 StatisticNode CLUSTERNODE 있습니다
StatisticNode # avgRt
java public double avgRt() { //获取当前时间窗口内调用成功的次数 long successCount = rollingCounterInSecond.success(); if (successCount == 0) { return 0; } //获取窗口内的响应时间 return rollingCounterInSecond.rt() * 1.0 / successCount; } E

이 방법은 주로 성공 rollingCounterInSecond 통화 수입니다, 다음 수로 나눈 총 응답 시간에 대한 각 호출이 성공의 평균 응답 시간을 얻기 위해 시간 창 이내에 답변을 얻을.

에서 1.Sentinel 소스 코드 분석 -FlowRuleManager 규칙을 어떤 한로드? 내가 특별히 rollingCounterInMinute 구현 원리 내부 StatisticNode에게 한 rollingCounterInMinute는 분에 의한 통계의 시간 창입니다. 이제 우리는 통계 시간 창 초에 rollingCounterInSecond에 대해 말하기된다.

초기화 rollingCounterInSecond 내부 StatisticNode에서 :

private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
    IntervalProperty.INTERVAL);

이 초기화 방법에있어서, 두 개의 매개 변수를 전달한다, SampleCountProperty.SAMPLE_COUNT가 2 값,
IntervalProperty.INTERVAL의 값은 1,000이다.

우리는 생성자 ArrayMetric에 체결 :

private final LeapArray<MetricBucket> data;
public ArrayMetric(int sampleCount, int intervalInMs) {
    this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
}

ArrayMetric 인스턴스를 생성하는 데이터는 OccupiableBucketLeapArray 인스턴스를 만들 때.

OccupiableBucketLeapArray

public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) {
    // This class is the original "CombinedBucketArray".
    super(sampleCount, intervalInMs);
    this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs);
}

LeapArray에게 부모 클래스의 생성자를 호출 할이 추상 클래스의 초기화 시간을 상속 OccupiableBucketLeapArray :
LeapArray를

public LeapArray(int sampleCount, int intervalInMs) {
    AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
    AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");
    //intervalInMs是sampleCount的整数
    AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
    //每个小窗口的时间跨度
    this.windowLengthInMs = intervalInMs / sampleCount;
    //窗口的长度
    this.intervalInMs = intervalInMs;
    //窗口个数
    this.sampleCount = sampleCount;

    this.array = new AtomicReferenceArray<>(sampleCount);
}

초기화시 OccupiableBucketLeapArray는 borrowArray에 할당 된 FutureBucketLeapArray 인스턴스를 생성합니다.

FutureBucketLeapArray는 LeapArray 상속 :

public FutureBucketLeapArray(int sampleCount, int intervalInMs) {
    // This class is the original "BorrowBucketArray".
    super(sampleCount, intervalInMs);
}

직접 슈퍼 클래스 LeapArray의 생성자를 호출하여 초기화.

여기 rollingCounterInSecond 생성 프로세스는 완료.

이제 StatisticNode에 돌아 가자, avgRt 방법의 StatisticNode를 호출 할 때하는 것은 통화의 수는 현재 시간 창을 성공 얻을 rollingCounterInSecond.success () 메서드를 호출

ArrayMetric # 성공

public long success() {
    //设置或更新当前的时间窗口
    data.currentWindow();
    long success = 0;
    //获取窗口里有效的Bucket
    List<MetricBucket> list = data.values();
    for (MetricBucket window : list) {
        success += window.success();
    }
    return success;
}

데이터 여기서 상위 클래스 LeapArray, 시간 윈도우를 기록 배열을 갖는다 LeapArray 배열이다 시간 창이 여기 초, (2)의 어레이 크기를 기반으로한다. 나는 데이터에서 직접 구조 차트 1.Sentinel 소스 코드를 분석 -FlowRuleManager 규칙을로드 무엇을 했습니까? 오버 걸릴 :

~ 빈도의 이상 횟수를 차단 주파수로 : WindowWrap 그러나 여기에서 두 개의 배열 요소, 각 요소는 다음과 같은 통계에 대한 WindowWrap MetricBucket 개체 이루어져

: currentWindow 방법의 데이터를 호출하는 것은 LeapArray의 방법은 이동 currentWindow 호출
LeapArray # currentWindow

public WindowWrap<T> currentWindow(long timeMillis) {
    if (timeMillis < 0) {
        return null;
    }
    //通过当前时间判断属于哪个窗口
    int idx = calculateTimeIdx(timeMillis);
    //计算出窗口开始时间
    // Calculate current bucket start time.
    long windowStart = calculateWindowStart(timeMillis);

    while (true) {
        //获取数组里的老数据
        WindowWrap<T> old = array.get(idx);
        if (old == null) {
           
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            if (array.compareAndSet(idx, null, window)) {
                // Successfully updated, return the created bucket.
                return window;
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
            // 如果对应时间窗口的开始时间与计算得到的开始时间一样
            // 那么代表当前即是我们要找的窗口对象,直接返回
        } else if (windowStart == old.windowStart()) {
             
            return old;
        } else if (windowStart > old.windowStart()) { 
            //如果当前的开始时间小于原开始时间,那么就更新到新的开始时间
            if (updateLock.tryLock()) {
                try {
                    // Successfully get the update lock, now we reset the bucket.
                    return resetWindowTo(old, windowStart);
                } finally {
                    updateLock.unlock();
                }
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
        } else if (windowStart < old.windowStart()) {
            //一般来说不会走到这里
            // Should not go through here, as the provided time is already behind.
            return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
        }
    }
}

여기에 단순히 방법을 설명하는 방법은 첫 번째 챕터의 소스 코드 분석에서 수행 된 자세히 설명합니다.

노드가 이미 존재하는 신규 노드로 갱신 한 후 CAS가 계산 배열 인덱스 배열하고 배열 안에 현재 소인 어레이에 기초한다이 방법은 대응하는 데이터를 찾기 위해, 상기 신규 노드가 직접 다음이면 반환, 노드가 실패 할 경우, 현재 노드를 설정 한 모든 실패한 노드를 제거합니다.

여기에 내가 직접 참조 1.Sentinel 소스 코드 분석 -FlowRuleManager로드 된 룰 무엇을 했습니까? 예 :

1. 如果array数据里面的bucket数据如下所示:
  NULL      B4
|_______|_______|
800     1000    1200   
    ^
   time=888
正好当前时间所对应的槽位里面的数据是空的,那么就用CAS更新

2. 如果array里面已经有数据了,并且槽位里面的窗口开始时间和当前的开始时间相等,那么直接返回
      B3      B4
 ||_______|_______||___
800     1000    1200  timestamp
      ^
    time=888

3. 例如当前时间是1676,所对应窗口里面的数据的窗口开始时间小于当前的窗口开始时间,那么加上锁,然后设置槽位的窗口开始时间为当前窗口开始时间,并把槽位里面的数据重置
   (old)
             B0      
 |_______||_______|
 ...    1200     1400
    ^
  time=1676

방법 ArrayMetric 다시 성공, 호출 data.values을 () 메소드를 아래로 이동합니다 :
LeapArray 성공 #

public List<T> values(long timeMillis) {
    if (timeMillis < 0) {
        return new ArrayList<T>();
    }
    int size = array.length();
    List<T> result = new ArrayList<T>(size);

    for (int i = 0; i < size; i++) {
        WindowWrap<T> windowWrap = array.get(i);
        if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
            continue;
        }
        result.add(windowWrap.value());
    }
    return result;
}

이 방법은 모든 유효한 MetricBucket를 취득하고 반환하는 데 사용됩니다.
그런 다음 수는 메서드 호출 MetricBucket의 성공에 의해 호출 된 얻을.

우리는 다음 RT ArrayMetric 방법을 보면 :

public long rt() {
    data.currentWindow();
    long rt = 0;
    //获取当前时间窗口的统计数据
    List<MetricBucket> list = data.values();
    //统计当前时间窗口的平均相应时间之和
    for (MetricBucket window : list) {
        rt += window.rt();
    }
    return rt;
}

이 방법은 성공의 위의 방법과 유사하다, 반환의 모든 데이터 합계 RT의 MetricBucket를 얻을 수 있습니다.
그런 다음 호출이 성공을 반환 RT 방법의 합으로 나눈 시간의 평균 개수의 합으로 얻어 질 수있다.

우리는 응답 시간 다운 그레이드 정책에서 다시 passCheck 방법 DegradeRule로 이동

if (grade == RuleConstant.DEGRADE_GRADE_RT) {
    //获取节点的平均响应时间
    double rt = clusterNode.avgRt();
    if (rt < this.count) {
        passCount.set(0);
        return true;
    }
    //rtSlowRequestAmount默认是5
    // Sentinel will degrade the service only if count exceeds.
    if (passCount.incrementAndGet() < rtSlowRequestAmount) {
        return true;
    }
    //    根据异常比例降级
}
//省略
return false;

평균 횟수가 설정 한 시간에 대한 응답 시간보다 작 다음 passCount가 true를 돌려 다시 경우 nothrow를 나타내는 5 연속 응답 시간이 수를 초과하는 경우, 그것은 거짓 슬로우 다운 그레이드를 반환 .

이상 DEGRADE_GRADE_EXCEPTION_RATIO 강등 비율

if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
    //获取每秒异常的次数
    double exception = clusterNode.exceptionQps();
    //获取每秒成功的次数
    double success = clusterNode.successQps();
    //获取每秒总调用次数
    double total = clusterNode.totalQps();
    // If total amount is less than minRequestAmount, the request will pass.
    // 如果总调用次数少于5,那么不进行降级
    if (total < minRequestAmount) {
        return true;
    }

    // In the same aligned statistic time window,
    // "success" (aka. completed count) = exception count + non-exception count (realSuccess)
    double realSuccess = success - exception;
    if (realSuccess <= 0 && exception < minRequestAmount) {
        return true;
    }

    if (exception / success < count) {
        return true;
    } 
}
。。。
return false;

이 방법은 검증 후 성공과 QPS QPS 이상 통화를 요구하고있는 경우 다음의 비율에 대해 계산보다 크지 물어, 그것이 사실 반환, 그렇지 않은 경우는 false 슬로우됩니다.

우리는 다음 exceptionQps 방법을 살펴보세요로 이동합니다
StatisticNode 번호의 exceptionQps을

public double exceptionQps() {
    return rollingCounterInSecond.exception() / rollingCounterInSecond.getWindowIntervalInSec();
}

rollingCounterInSecond.getWindowIntervalInSec 방법은 초 단위로 시간 윈도우의 길이이다. 여기에 1을 반환합니다.
ArrayMetric # 예외

public long exception() {
    data.currentWindow();
    long exception = 0;
    List<MetricBucket> list = data.values();
    for (MetricBucket window : list) {
        exception += window.exception();
    }
    return exception;
}

이 방법은 잘 보이는, 내 위의 분석과 유사하다.

이상 그레이드 DEGRADE_GRADE_EXCEPTION_COUNT의 개수에 따라

if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
    double exception = clusterNode.totalException();
    if (exception < count) {
        return true;
    }
}

이상 하향의 개수에 따르면 직접 통계적 이상 판정의 갯수가 카운트를 초과에 기초하여, 매우 간단하다.

여기 약간 달성하기 위해 강등 완료 ~

추천

출처www.cnblogs.com/luozhiyun/p/11517918.html