Sentinel簡介及相關(guān)功能

sentinel 簡介

概述

Sentinel 是面向分布式、多語言異構(gòu)化服務(wù)架構(gòu)的流量治理組件,主要以流量為切入點,從流量路由、流量控制、流量整形、熔斷降級、系統(tǒng)自適應(yīng)過載保護、熱點流量防護等多個維度來幫助開發(fā)者保障微服務(wù)的穩(wěn)定性。

支持功能:

  1. 流量控制
  2. 熔斷降級
  3. 系統(tǒng)自適應(yīng)保護
  4. 集群流量控制
  5. 網(wǎng)關(guān)流量控制
  6. 熱點參數(shù)限流
  7. 來源訪問控制

Sentinel Hystrix
隔離策略 信號量隔離 線程池隔離/信號量隔離
熔斷降級策略 基于慢調(diào)用比例或異常比例 基于失敗比率
實時指標(biāo)實現(xiàn) 滑動窗口 滑動窗口(基于RXJava)
規(guī)則配置 支持多數(shù)據(jù)源 支持多數(shù)據(jù)源
擴展性 多個擴展點 插件的形式
基于注解的支持 支持 支持
限流 基于QPS、支持基于調(diào)用關(guān)系的限流 有限的支持
流量整形 支持慢啟動、勻速排隊模式 不支持
系統(tǒng)自適應(yīng)保護 支持 不支持
控制臺 開箱即用,可配置規(guī)則、查看秒級監(jiān)控、機器發(fā)現(xiàn)等 不完善
常見框架的適配 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Cloud Netflix
Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2021.0.1.0 1.8.3 1.4.2 4.9.2 ~ 1.4.2

基于sentinel實現(xiàn)的功能

  1. 流量控制和限流 基于可視化控制臺
  2. 實現(xiàn)熔斷降級

1.實現(xiàn)流量控制

基于 QPS/并發(fā)數(shù)的流量控制
基于調(diào)用關(guān)系的流量控制

1.1 啟動控制臺服務(wù)及主要概述說明

1.1.1 配置

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080

1.1.2 啟動控制臺

首先下載所需版本的sentinel可視化服務(wù)jar包,下載地址https://github.com/alibaba/Sentinel/releases
默認(rèn)參數(shù):
端口:8080
用戶名:sentinel
密碼:sentinel


1.1.3 基于可視化控制臺設(shè)置QPS或并發(fā)線程數(shù)控制訪問

資源名:流控針對的方法名;
針對來源:需要在代碼中指定 在方法中添加ContextUtil.enter(resourceName,"填寫來源名稱") 下面代碼中有示例;
閾值類型:QPS、并發(fā)線程數(shù);
1). QPS:每秒請求查詢數(shù)量
2). 并發(fā)線程數(shù):瞬時的請求數(shù)量
單機閾值:限制的訪問次數(shù)
支持集群,可設(shè)置集群單機訪問也可設(shè)置集群總訪問量
流控模式:直接、關(guān)聯(lián)、鏈路;
1). 關(guān)聯(lián)資源:如果流控模式為關(guān)聯(lián)時,才有該參數(shù);
2). 入口資源:如果流控模式為鏈路時,才有該參數(shù);
流控效果:快速失敗、Warm Up、排隊等待;
1). 預(yù)熱時長:如果流控效果為Warm Up才有該參數(shù)

流控模式:
直接:代表請求請求到該接口如果達到設(shè)置到閾值將直接進行流控,直接拋出異常
關(guān)聯(lián):代表請求到該接口,關(guān)聯(lián)的資源達到設(shè)置的閾值,然后才觸發(fā)流控,比如在同一個服務(wù)中會有兩個接口,一個讀資源操作的接口,一個是寫資源操作的接口,如果讀資源操作的接口QPS太大時,會影響寫操作業(yè)務(wù)完整性,即可以在設(shè)定資源名為寫操作的流控規(guī)則時流控模式設(shè)定為關(guān)聯(lián)模式,關(guān)聯(lián)的資源為讀操作的接口,就是當(dāng)讀操作的QPS達到閾值時,將寫操作進行流控。(當(dāng)關(guān)聯(lián)當(dāng)資源達到閾值時,就限流自己)
鏈路:比如一個微服務(wù)中的兩個接口都調(diào)用了該微服務(wù)中的同一個service方法,并且該方法用SentinelResource注解給標(biāo)注了,然后對該注解標(biāo)注的資源進行規(guī)則配置,則可以選擇鏈路模式,規(guī)則中資源名,就是注解標(biāo)注的名稱,入口資源就是指定,對那個接口調(diào)用該service的方法進行流控。

流控效果:
快速失敗:顧名思義,就是直接拋出異常
Warm Up :可以設(shè)置預(yù)熱的時長,即預(yù)熱/冷啟動方式。當(dāng)系統(tǒng)長期處于低水位的情況下,當(dāng)流量突然增加時,直接把系統(tǒng)拉升到高水位可能瞬間把系統(tǒng)壓垮。通過"冷啟動",讓通過的流量緩慢增加,在一定時間內(nèi)逐漸增加到閾值上限,給冷系統(tǒng)一個預(yù)熱的時間,避免冷系統(tǒng)被壓垮。
排隊等待:顧名思義,就是請求一個一個排隊等待,設(shè)定一個請求等待的超時時間,等待時間超過設(shè)定的值,然后拋出異常。

官網(wǎng)地址:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6


1.1.4 可以通過注解形式控制方法的訪問

通過在方法上添加@SentinelResource注解對該方法進行流控,如果超過流控規(guī)則限制,則返回下面方法的錯誤提示
注:兩個方法的返回值要一致;
value值要和方法名一致,blockHandler值要和下面方法名一致;
兩個方法的參數(shù)一定要一致(除BlockException外);
下面的方法一定要有BlockException異常的參數(shù)

    @GetMapping(value = "/get")
    @SentinelResource(value = "getMethod",blockHandler = "fail")
    public String getMethod(){
        String resourceName = "get"; //一般即為請求路徑
        ContextUtil.enter(resourceName,"填寫來源名稱")
        return "SUCCESS";
    }

    public String fail(BlockException e){
        return "已被限制訪問";
    }

如果不使用可視化控制臺添加流控規(guī)則,而是在系統(tǒng)中自定義開發(fā)流控規(guī)則配置功能,則需要通過spring AOP功能來完成sentinel源碼改造工作相關(guān)重要屬性如下表
具體改造工作可以參考sentinel原理https://blog.csdn.net/wanger5354/article/details/122493842

Field 說明 默認(rèn)值
resource 資源名,資源名是限流規(guī)則的作用對象
count 限流閾值
grade 限流閾值類型,QPS 或線程數(shù)模式 QPS 模式
limitApp 流控針對的調(diào)用來源 default,代表不區(qū)分調(diào)用來源
strategy 判斷的根據(jù)是資源自身,還是根據(jù)其它關(guān)聯(lián)資源 (refResource),還是根據(jù)鏈路入口 根據(jù)資源本身
controlBehavior 流控效果(直接拒絕 / 排隊等待 / 慢啟動模式) 直接拒絕

2.實現(xiàn)熔斷降級

2.1 降級策略

2.1.1 降級策略(sentinel 1.8.0及以上版本):

慢調(diào)用比例:需要設(shè)置慢調(diào)用的RT(即最大相應(yīng)時間,單位毫秒),請求的相應(yīng)時間大于該值,則統(tǒng)計為慢調(diào)用。
當(dāng)單位統(tǒng)計時長內(nèi),請求數(shù)目大于設(shè)置的最小請求數(shù)目并且慢調(diào)用的比例大于閾值,則接下來的熔斷時長內(nèi)請求會自動被熔斷。
經(jīng)過熔斷時長后熔斷器會進入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求響應(yīng)時間小于設(shè)置的慢調(diào)用RT,則結(jié)束熔斷,若大于設(shè)置的慢調(diào)用RT,則會再次被熔斷。

異常比例:當(dāng)單位統(tǒng)計時長內(nèi)請求數(shù)目大于設(shè)置的最小請求數(shù)目,并且異常的比例大于閾值,則接下來的熔斷時長內(nèi)請求會自動被熔斷。
經(jīng)過熔斷時長后熔斷器會進入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求成功完成(沒有錯誤)則結(jié)束熔斷,否則會再次被熔斷。
異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。

異常數(shù):當(dāng)單位統(tǒng)計時長內(nèi)的異常數(shù)目超過閾值之后會自動進行熔斷。
經(jīng)過熔斷時長后熔斷器會進入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求成功完成(沒有錯誤)則結(jié)束熔斷,否則會再次被熔斷。

參數(shù)說明:
資源名:需要做熔斷處理的接口名
熔斷策略:慢比例調(diào)用、異常比例、異常數(shù)
最大RT(毫秒):設(shè)置接口響應(yīng)時間比較標(biāo)準(zhǔn)值,如果接口響應(yīng)時間超過最大RT,則判定該次接口調(diào)用為慢調(diào)用
比例閾值(取值[0.0~1.0]):設(shè)置 慢調(diào)用的次數(shù)占有的比例或異常次數(shù)占有的比例,如果超過設(shè)置的比例閾值,則滿足熔斷條件的50%
熔斷時長:如果滿足熔斷條件,則熔斷的時長
最小請求數(shù):設(shè)置單位時間內(nèi)接口請求次數(shù)標(biāo)準(zhǔn)值,如果超過設(shè)置的最小請求數(shù),則滿足熔斷條件的50%
統(tǒng)計時長(毫秒):單位統(tǒng)計時長,設(shè)置多長時間作為一個單位統(tǒng)計時長,默認(rèn)1000ms
異常數(shù):設(shè)置單位時間內(nèi),調(diào)用接口異常次數(shù)比較標(biāo)準(zhǔn)值

注意異常降級僅針對業(yè)務(wù)異常,對 Sentinel 限流降級本身的異常(BlockException)不生效。為了統(tǒng)計異常比例或異常數(shù),需要通過 Tracer.trace(ex) 記錄業(yè)務(wù)異常。 示例:

Entry entry = null;
try {
    entry = SphU.entry(key, EntryType.IN, key);

    // Write your biz code here.
    // <<BIZ CODE>>
} catch (Throwable t) {
    if (!BlockException.isBlockException(t)) {
        Tracer.trace(t);
    }
} finally {
    if (entry != null) {
        entry.exit();
    }
}

2.1.2 降級策略(sentinel 1.8.0以下版本):

RT(平均響應(yīng)時間,秒級):平均響應(yīng)時間超出閾值在時間窗口內(nèi)通過的請求>=5,兩個條件同時滿足后出發(fā)降級,窗口期過后關(guān)閉斷路器;RT最大4900 (更大需要通過-Dcsp.sentinel.statistic.max.rt=XXX才能生效)
異常比例(秒級):QPS>=5且異常比例(秒級統(tǒng)計)超過閾值時,觸發(fā)降級;時間窗口結(jié)束后,關(guān)閉降級
異常數(shù)(分鐘級):異常數(shù)超過閾值時,觸發(fā)降級;時間窗口結(jié)束后,關(guān)閉降級
對比Hystrix熔斷降級比較,Hystrix是有半開狀態(tài)的,服務(wù)自動去檢測是否請求有異常,沒有異常就關(guān)閉斷路器恢復(fù)使用,有異常則繼續(xù)打開斷路器不可使用

2.2 重要參數(shù)

如果通過apollo等配置中心進行配置時,重要參數(shù)非常重要;

Field 說明 默認(rèn)值
resource 資源名,即規(guī)則的作用對象
grade 熔斷策略,支持慢調(diào)用比例/異常比例/異常數(shù)策略 慢調(diào)用比例
count 慢調(diào)用比例模式下為慢調(diào)用臨界 RT(超出該值計為慢調(diào)用);異常比例/異常數(shù)模式下為對應(yīng)的閾值
timeWindow 熔斷時長,單位為 s
minRequestAmount 熔斷觸發(fā)的最小請求數(shù),請求數(shù)小于該值時即使異常比率超出閾值也不會熔斷(1.7.0 引入) 5
statIntervalMs 統(tǒng)計時長(單位為 ms),如 60*1000 代表分鐘級(1.8.0 引入) 1000 ms
slowRatioThreshold 慢調(diào)用比例閾值,僅慢調(diào)用比例模式有效(1.8.0 引入)

2.3 熔斷器事件監(jiān)聽

Sentinel 支持注冊自定義的事件監(jiān)聽器監(jiān)聽熔斷器狀態(tài)變換事件(state change event)。示例:

EventObserverRegistry.getInstance().addStateChangeObserver("logging",
    (prevState, newState, rule, snapshotValue) -> {
        if (newState == State.OPEN) {
            // 變換至 OPEN state 時會攜帶觸發(fā)時的值
            System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                TimeUtil.currentTimeMillis(), snapshotValue));
        } else {
            System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                TimeUtil.currentTimeMillis()));
        }
    });

監(jiān)聽demo類,官網(wǎng)都提供demo,也可以直接去官網(wǎng)找,這里我只是搬運工,嘻嘻?。?!
慢調(diào)用比例 監(jiān)聽示例SlowRatioDemo.java

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;

/**
 * 慢調(diào)用比例 監(jiān)聽示例
 */
public class SlowRatioDemo {

    private static final String KEY = "some_method";

    private static volatile boolean stop = false;
    private static int seconds = 120;

    private static AtomicInteger total = new AtomicInteger();
    private static AtomicInteger pass = new AtomicInteger();
    private static AtomicInteger block = new AtomicInteger();

    public static void main(String[] args) throws Exception {
        // 初始化加載熔斷規(guī)則
        initDegradeRule();
        // 熔斷器事件監(jiān)聽
        registerStateChangeObserver();
        startTick();

        int concurrency = 8;
        for (int i = 0; i < concurrency; i++) {
            Thread entryThread = new Thread(() -> {
                while (true) {
                    Entry entry = null;
                    try {
                        entry = SphU.entry(KEY);
//                        get();
                        pass.incrementAndGet();
                        // RT: [40ms, 60ms)  su
                        sleep(ThreadLocalRandom.current().nextInt(40, 60));
                    } catch (BlockException e) {
                        block.incrementAndGet();
                        sleep(ThreadLocalRandom.current().nextInt(5, 10));
                    } finally {
                        total.incrementAndGet();
                        if (entry != null) {
                            entry.exit();
                        }
                    }
                }
            });
            entryThread.setName("sentinel-simulate-traffic-task-" + i);
            entryThread.start();
        }
    }

    // 熔斷器事件監(jiān)聽
    private static void registerStateChangeObserver() {
        EventObserverRegistry.getInstance().addStateChangeObserver("logging",
                (prevState, newState, rule, snapshotValue) -> {
                    if (newState == State.OPEN) {
                        System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                                TimeUtil.currentTimeMillis(), snapshotValue));
                    } else {
                        System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                                TimeUtil.currentTimeMillis()));
                    }
                });
    }

    private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule(KEY)
                // 熔斷策略
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                // Max allowed response time  RT 慢調(diào)用標(biāo)準(zhǔn)值 接口相應(yīng)時長超過RT則被判定為慢調(diào)用
                .setCount(50)
                // Retry timeout (in second)   窗口期  熔斷時長
                .setTimeWindow(10)
                // Circuit breaker opens when slow request ratio > 60%  慢調(diào)用比例   判定是否熔斷的條件之一
                .setSlowRatioThreshold(0.3)
                // 單位時長最小請求數(shù)   判定是否熔斷的條件之一
                .setMinRequestAmount(10)
                // 統(tǒng)計時長也叫單位時長
                .setStatIntervalMs(20000);
        rules.add(rule);

        DegradeRuleManager.loadRules(rules);
        System.out.println("Degrade rule loaded: " + rules);
    }

    private static void sleep(int timeMs) {
        try {
            TimeUnit.MILLISECONDS.sleep(timeMs);
        } catch (InterruptedException e) {
            // ignore
        }
    }

    private static void startTick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-tick-task");
        timer.start();
    }

    static class TimerTask implements Runnable {
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("Begin to run! Go go go!");
            System.out.println("See corresponding metrics.log for accurate statistic data");

            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;

            while (!stop) {
                sleep(1000);

                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(Thread.currentThread().getName()+"\t\t"+TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
                        + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock);

                if (seconds-- <= 0) {
                    stop = true;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total: " + total.get() + ", pass:" + pass.get()
                    + ", block:" + block.get());
            System.exit(0);
        }
    }


    public static String get() {
        System.out.println("===========SUCCESS==============");
        // RT: [40ms, 60ms)
        sleep(ThreadLocalRandom.current().nextInt(40, 60));
        return "Success";
    }

}

系統(tǒng)自適應(yīng)保護 demoSystemGuardDemo.java

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;

/**
 * 系統(tǒng)自適應(yīng)保護 demo
 *
 * 系統(tǒng)保護規(guī)則是從應(yīng)用級別的入口流量進行控制,從單臺機器的總體 Load、RT、入口 QPS 和線程數(shù)四個維度監(jiān)控應(yīng)用數(shù)據(jù),讓系統(tǒng)盡可能跑在最大吞吐量的同時保證系統(tǒng)整體的穩(wěn)定性。
 *
 * 系統(tǒng)保護規(guī)則是應(yīng)用整體維度的,而不是資源維度的,并且僅對入口流量生效。入口流量指的是進入應(yīng)用的流量(EntryType.IN),比如 Web 服務(wù)或 Dubbo 服務(wù)端接收的請求,都屬于入口流量。
 *
 * 系統(tǒng)規(guī)則支持以下的閾值類型:
 *
 * Load(僅對 Linux/Unix-like 機器生效):當(dāng)系統(tǒng) load1 超過閾值,且系統(tǒng)當(dāng)前的并發(fā)線程數(shù)超過系統(tǒng)容量時才會觸發(fā)系統(tǒng)保護。系統(tǒng)容量由系統(tǒng)的 maxQps * minRt 計算得出。設(shè)定參考值一般是 CPU cores * 2.5。
 * CPU usage(1.5.0+ 版本):當(dāng)系統(tǒng) CPU 使用率超過閾值即觸發(fā)系統(tǒng)保護(取值范圍 0.0-1.0)。
 * RT:當(dāng)單臺機器上所有入口流量的平均 RT 達到閾值即觸發(fā)系統(tǒng)保護,單位是毫秒。
 * 線程數(shù):當(dāng)單臺機器上所有入口流量的并發(fā)線程數(shù)達到閾值即觸發(fā)系統(tǒng)保護。
 * 入口 QPS:當(dāng)單臺機器上所有入口流量的 QPS 達到閾值即觸發(fā)系統(tǒng)保護。
 *
 */
public class SystemGuardDemo {

    private static AtomicInteger pass = new AtomicInteger();
    private static AtomicInteger block = new AtomicInteger();
    private static AtomicInteger total = new AtomicInteger();

    private static volatile boolean stop = false;
    private static final int threadCount = 100;

    private static int seconds = 60 + 40;

    public static void main(String[] args) throws Exception {

        tick();
        initSystemRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        Entry entry = null;
                        try {
                            entry = SphU.entry("methodA", EntryType.IN);
                            pass.incrementAndGet();
                            try {
                                TimeUnit.MILLISECONDS.sleep(20);
                            } catch (InterruptedException e) {
                                // ignore
                            }
                        } catch (BlockException e1) {
                            block.incrementAndGet();
                            try {
                                TimeUnit.MILLISECONDS.sleep(20);
                            } catch (InterruptedException e) {
                                // ignore
                            }
                        } catch (Exception e2) {
                            // biz exception
                        } finally {
                            total.incrementAndGet();
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }

            });
            entryThread.setName("working-thread");
            entryThread.start();
        }
    }

    private static void initSystemRule() {
        List<SystemRule> rules = new ArrayList<SystemRule>();
        SystemRule rule = new SystemRule();
        // max load is 3
        rule.setHighestSystemLoad(3.0);
        // max cpu usage is 60%
        rule.setHighestCpuUsage(0.6);
        // max avg rt of all request is 10 ms
        rule.setAvgRt(10);
        // max total qps is 20
        rule.setQps(20);
        // max parallel working thread is 10
        rule.setMaxThread(10);

        rules.add(rule);
        SystemRuleManager.loadRules(Collections.singletonList(rule));
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {
        @Override
        public void run() {
            System.out.println("begin to statistic!!!");
            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;
            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:"
                        + oneSecondTotal + ", pass:"
                        + oneSecondPass + ", block:" + oneSecondBlock);
                if (seconds-- <= 0) {
                    stop = true;
                }
            }
            System.exit(0);
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容