Sentinel使用入門

一、Sentinel介紹

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

  • 流量控制

    任意時間到來的請求往往是隨機不可控的,而系統(tǒng)的處理能力是有限的。我們需要根據(jù)系統(tǒng)的處理能力對流量進行控制。Sentinel 作為一個調(diào)配器,可以根據(jù)需要把隨機的請求調(diào)整成合適的形狀。

  • 熔斷降級

    及時對調(diào)用鏈路中的不穩(wěn)定因素進行熔斷也是 Sentinel 的使命之一,Sentinel 和 Hystrix 的原則是一致的,當檢測到調(diào)用鏈路中某個資源出現(xiàn)不穩(wěn)定的表現(xiàn),例如請求響應(yīng)時間長或異常比例升高的時候,則對這個資源的調(diào)用進行限制,讓請求快速失敗,避免影響到其它的資源而導(dǎo)致級聯(lián)故障。

  • 系統(tǒng)保護

    Sentinel 為服務(wù)集群提供了對應(yīng)的保護機制,讓系統(tǒng)的入口流量和系統(tǒng)的負載達到一個平衡,保證系統(tǒng)在能力范圍之內(nèi)處理最多的請求。

  • 實時監(jiān)控

    Sentinel 提供實時的監(jiān)控系統(tǒng),方便您快速了解目前系統(tǒng)的狀態(tài)。

二、Dashboard的搭建與啟動

官網(wǎng)下載最新版本的jar包,然后按照如下命令啟動Dashboard。

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.x.x.jar

啟動成功后,我們就可以在localhost:8080端口訪問Dashboard的網(wǎng)站了,初始賬密為sentinel。

三、應(yīng)用入門案例

3.1 應(yīng)用搭建

首先新建一個項目,引入必要的模塊:web和sentinel

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

然后新建一個Controller

@Slf4j
@RestController
public class HelloController {
    @GetMapping("/getHello")
    public String getHello(){
        return "hello";
    }
}

最后,我們需要增加如下的配置內(nèi)容,來對sentinel進行配置。

server.port=8081

spring.application.name=sentinel-demo
# 指定sentinel控制臺的地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 指定和控制臺通信的端口,默認8719
spring.cloud.sentinel.transport.port=8719
# 指定心跳周期,默認null
spring.cloud.sentinel.transport.heartbeat-interval-ms=10000

到此,我們可以啟動項目了,然后通過訪問localhost:8081/getHello多嘗試幾次,然后過一會就可以在Dahboard上看到我們的調(diào)用監(jiān)控了。

當然,如果需要監(jiān)控的不是接口,而是普通的方法,我們可以使用@SentinelResource注解來達成目的。

@Slf4j
@Component
public class HelloJob {

    @Scheduled(cron = "0/10 * * * * ?")
    @SentinelResource("helloJob")
    public void startHello(){
        log.info("hello! hello.");
    }

}
@EnableScheduling
@SpringBootApplication
public class SentinelDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SentinelDemoApplication.class, args);
    }

}

重新啟動項目,發(fā)現(xiàn)我們的helloJob也可以被Dashboard監(jiān)控管理了。

3.2 拋出異常的方式定義資源

此時我們還沒有用到Sentinel的任何功能,我們來試下對如上的Controller來進行QPS的限流,這里使用的方式是拋出異常的方式。

    /**
     * 拋出異常的方式定義資源
     * @return
     */
    @GetMapping("/getHello")
    public String getHello() {
        // 使用限流規(guī)則,保護”業(yè)務(wù)邏輯“
        try(Entry entry = SphU.entry("getHello")) {
            // 正常的業(yè)務(wù)邏輯
            return "OK";
        } catch (Exception e) {
            log.info("當前請求被限流了!", e);
            // 降級方案
            return "系統(tǒng)繁忙,請稍后再試!";
        }
    }

    /**
     * 定義隔離規(guī)則
     *
     * @PostConstruct 當前類的構(gòu)造函數(shù)執(zhí)行之后執(zhí)行該方法
     */
    @PostConstruct
    public void initFlowRule() {
        List<FlowRule> ruleList = new ArrayList<>();
        FlowRule rule = new FlowRule();
        // 設(shè)置資源名稱
        rule.setResource("getHello");
        // 指定限流模式為QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 指定QPS限流閾值
        rule.setCount(2);
        ruleList.add(rule);
        // 加載該規(guī)則
        FlowRuleManager.loadRules(ruleList);
    }

我們在代碼中設(shè)置了/getHello的QPS閾值為2,那么此時啟動項目后,快速刷新訪問該URL,就可能會看到限流后的降級輸出。

3.3 返回布爾值方式定義資源

我們還有其它方式來實現(xiàn)如上相同的功能:

    /**
     * 返回布爾值方式定義資源
     * @return
     */
    @GetMapping("/getHi")
    public String getHi() {
        if (SphO.entry("getHello")) {
            try {
                // 正常的業(yè)務(wù)邏輯
                return "Hi";
            } finally {
                SphO.exit();
            }
        } else {
            log.info("當前請求被限流了!");
            // 降級方案
            return "系統(tǒng)繁忙,請稍后再試!";
        }
    }

3.4 異步調(diào)用方式定義資源

前面的例子都是同步調(diào)用的,我們來個異步調(diào)用的例子,前端請求到了之后,異步去處理具體的業(yè)務(wù)邏輯。

首先我們需要在啟動類上增加@EnableAsync注解。

其次我們創(chuàng)建一個異步業(yè)務(wù)類:

@Slf4j
@Service
public class AsyncService {

    @Async
    public void asyncInvoke(){
        log.info("開始asyncInvoke。。。");
        try {
            // 沉睡10秒,模擬遠程調(diào)用
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("結(jié)束asyncInvoke。。。");
    }

}

然后,我們在上面例子中的controller類中增加一個:

    @Autowired
    private AsyncService asyncService;

    /**
     * 異步調(diào)用方式定義資源
     */
    @GetMapping("/getAsync")
    public String getAsync(){
        AsyncEntry asyncEntry = null;
        try {
            asyncEntry = SphU.asyncEntry("getHello");
            // 正常的業(yè)務(wù)邏輯
            asyncService.asyncInvoke();
        } catch (BlockException e) {
            log.info("當前請求被限流了:",e);
            // 降級方案
            return "系統(tǒng)繁忙,請稍后再試!";
        } finally {
            if(asyncEntry != null){
                asyncEntry.exit();
            }
        }
        return "OK";
    }

3.5 使用注解的方式定義資源

    /**
     * 使用注解額方式定義資源
     */
    @GetMapping("/getAnnotation")
    @SentinelResource(value = "getHello", blockHandler = "exceptionHandler")
    public String getAnnotation(){
        // 正常的業(yè)務(wù)邏輯
        return "annotation";
    }

    public String exceptionHandler(BlockException blockException){
        // 降級方案
        log.info("當前請求被限流了:",blockException);
        return "系統(tǒng)繁忙,請稍后再試!";
    }

在實際使用Sentinel的過程中,我們很少使用如上硬編碼的方式來設(shè)置規(guī)則,因為如果需要改規(guī)則會很不方便,所以一般推薦使用Sentinel Dashboard來設(shè)置和編輯規(guī)則,這部分在如下例子中講解。

四、流量控制

4.1 并發(fā)線程數(shù)控制

用于保護業(yè)務(wù)線程池不被慢調(diào)用耗盡。和Hystrix相比,Hystrix是使用了線程池隔離的技術(shù),雖然隔離的比較徹底,不會存在兩個不同業(yè)務(wù)調(diào)用相互競爭的情況,但是線程資源會比較浪費,線程的切換也會帶來很大的性能損耗。Sentinel則是不會創(chuàng)建和管理線程池,僅僅是統(tǒng)計當前資源上下文的總線程數(shù),只要超過閾值,就拒絕新的調(diào)用,效果類似于Hystrix中的信號量模式。

下面我們來實驗下并發(fā)線程數(shù)控制的效果:

還是上面入門案例,我們改造一下HelloController,主要是使得請求線程沉睡1秒,使得每一個請求線程在1秒內(nèi)只會處理一個請求。

@GetMapping("/getHello")
public String getHello() throws InterruptedException {
    Thread.sleep(1000);
    return "hello";
}

然后我們使用Jmeter做并發(fā)壓力測試,設(shè)置線程數(shù)量為30,循環(huán)2次,開始后Seninel控制臺會顯示所有請求全部成功,此時系統(tǒng)能承受的最大并發(fā)數(shù)量上限為tomcat容器允許的最大線程數(shù)。

getHello未限流

然后,在Sentinel的控制臺上找到”流控規(guī)則“,新增一個規(guī)則,并在如下選項中依次填寫:

getHello流控規(guī)則

此時,我們重復(fù)如上Jmeter的并發(fā)測試,會發(fā)現(xiàn)1秒內(nèi)的并發(fā)數(shù)量被降低到20以下了,說明Sentinel限流成功。

getHello被限流

4.2 QPS控制

是用于限制某個資源的請求并發(fā)數(shù)量,和線程無關(guān),我們使用如上HelloController的例子,去掉線程的沉睡代碼。在不限QPS的時候,Jmeter并發(fā)30個線程,循環(huán)兩次,發(fā)現(xiàn)都可以訪問成功。

然后我們增加流控規(guī)則如下:

getHello限流QPS

此時啟動Jmeter,會發(fā)現(xiàn)QPS被限制為一秒內(nèi)最多20個,超過20個的都會拒絕,從而使請求失敗。

getHello-qps-快速失敗

五、熔斷降級

5.1 RT響應(yīng)時間

DEGRADE_GRADE_RT,當一秒內(nèi)對某個資源發(fā)起的多個請求,它們的平均響應(yīng)時間超過了閾值T,那么在接下來的N秒內(nèi),所有對這個資源的請求訪問都會被熔斷進行降級處理。其中,閾值T是我們設(shè)置的毫秒數(shù),N秒是我們設(shè)置的熔斷時間窗口。

5.2 異常比例

DEGRADE_GRADE_EXCEPTION_RATIO,當每秒對某個資源的請求量超過閾值C的時候,異常請求/正常請求的比例超過閾值P時,在接下來的T秒時間窗口內(nèi),所有對這個資源的請求訪問都會被熔斷進行降級處理。其中并發(fā)量C、比例閾值P、時間窗口T都是我們可以設(shè)置的。

5.3 異常數(shù)

DEGRADE_GRADE_EXCEPTION_COUNT,最近一分鐘內(nèi)對某個資源的請求異常數(shù)量達到我們設(shè)置的閾值C的時候,在該分鐘的剩余時間里,所有對這個資源的請求訪問都會被熔斷進行降級處理。在下一分鐘才能恢復(fù)訪問。

這部分熔斷降級的案例既可以通過如上案例中硬編碼在代碼里,也可以在Sentinel Dashboard的降級規(guī)則里面設(shè)置,比較簡單,不再演示。

六、整合Feign

首先需要引入Fein的依賴:

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.0.3</version>
        </dependency>

然后,我們創(chuàng)建一個Feign調(diào)用的service,并指定降級處理方法。

@FeignClient(name="feign-helo", url = "http://localhost:8081", fallback = FeignFallBackService.class)
public interface HelloFeignService {

    @RequestMapping(value = "/getHello0", method = RequestMethod.GET)
    String getHello();

}
@Component
public class FeignFallBackService implements HelloFeignService {
    @Override
    public String getHello() {
        // 降級方案
        return "Feign提示系統(tǒng)繁忙,請稍后再試!";
    }
}

然后,我們在如上Controller里面新增一個:

    @Autowired
    private HelloFeignService helloFeignService;

    /**
     * 整合Feign
     */
    @GetMapping("/getFeignHello")
    public String getFeignHello(){
        return helloFeignService.getHello();
    }

現(xiàn)在我們需要在啟動類上加上@EnableFeignClients注解,使得Feign生效。
最后一步,增加如下的配置內(nèi)容:

# feign開啟sentinel支持,否則降級方法得不到執(zhí)行,直接拋出異常
feign.sentinel.enabled=true

此時我們使用JMeter做測試,調(diào)用/getFeignHello接口,就會發(fā)現(xiàn)通過Feign調(diào)用的/getHello是有限流的,當/getHello服務(wù)不可用時,會執(zhí)行Feign的降級方法。

相關(guān)內(nèi)容和Hystrix整合OpenFeign比較類似,可以參考:
Hystrix使用入門

七、整合Gateway

關(guān)于Gateway的配置可以參考:Gateway使用入門,本文直接在這基礎(chǔ)上進行改造。

首先,我們新建一個配置類,用以當網(wǎng)關(guān)轉(zhuǎn)發(fā)請求失敗的時候降級處理的邏輯。

@Component
public class GatewayConfiguration {

    /**
     * 設(shè)置限流或者降級之后的處理方法,對所有路徑都生效
     */
    @PostConstruct
    public void doInit(){
        GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                return ServerResponse.status(200).syncBody("gateway提示:系統(tǒng)繁忙,請稍后再試!");
            }
        });
    }

}

然后在配置文件中先增加和Sentinel Dashboard的鏈接配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8720

然后增加一個路由規(guī)則,用以將請求網(wǎng)關(guān)的請求轉(zhuǎn)發(fā)到上面例子中的getHello中。

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-hello
          uri: http://localhost:8081
          predicates:
            - Path=/getHello

此時,我們同時啟動網(wǎng)關(guān)服務(wù)和上面的getHello服務(wù)。先測試下gateway能否正常工作,請求localhost:8089/getHello,如果正常返回getHello的返回內(nèi)容,則說明gateway項目沒有問題。

此時,我們的降級方案配置GatewayConfiguration 還不能起作用,因為還沒有限流的設(shè)置,所以我們可以在SentinelDashboard上增加相應(yīng)的限流規(guī)則。

  • Route ID,根據(jù)配置文件中配置的id進行限流;
  • API分組,根據(jù)請求API路徑進行匹配限流;

一旦觸發(fā)限流,那么就會使用GatewayConfiguration 的降級處理方案來響應(yīng)請求。

需要注意的是,在本案例中,gateway和hello項目的限流規(guī)則將會同時起作用,如果要準確測試gateway的限流效果,建議可以先關(guān)閉hello項目的限流規(guī)則。

  • 場景1:gateway限流6,hello限流2,我們并發(fā)20個請求,那么gateway將會攔截14個,進入gateway降級方案處理,只通過6個請求轉(zhuǎn)發(fā)給hello,然后hello再攔截4個,進入hello降級方案處理,最終只通過2個。
  • 場景2:gateway限流6,hello不限流,我們并發(fā)20個請求,那么gateway將會攔截14個,進入gateway降級方案處理,只通過6個請求轉(zhuǎn)發(fā)給hello,全部處理成功。
  • 場景3:gateway限流6,hello停機,我們并發(fā)20個請求,那么gateway攔截的會走降級處理方案,通過的請求因為找不到hello服務(wù),所以拋出異常了。

八、系統(tǒng)自適應(yīng)保護

Sentinel還支持根據(jù)應(yīng)用機器的負載情況來自適應(yīng)地對我們的資源進行限流和降級。使得系統(tǒng)的入口流量和系統(tǒng)的負載達到一個平衡,不會因為流量過大拖垮機器。

Sentinel自適應(yīng)保護的主要模式有如下幾種:

  • Load自適應(yīng),僅對Linux/Unix-like的機器生效,即將系統(tǒng)的Load1作為啟發(fā)指標,當系統(tǒng)超過設(shè)定的啟發(fā)值后,Sentinel估算當前機器的并發(fā)線程數(shù)超過了預(yù)估的最大容量后,對資源進行熔斷降級。
  • CPU Usage,根據(jù)當前機器的CPU使用率是否超過了設(shè)定的閾值來觸發(fā)熔斷降級。
  • 平均RT,當前機器的所有入口流量平均RT超過設(shè)定的閾值時觸發(fā)熔斷降級。
  • 并發(fā)線程數(shù),當前機器的所有入口流量造成的并發(fā)線程數(shù)超過設(shè)定的閾值;
  • 入口QPS,當前機器的所有入口流量造成的QPS數(shù)超過設(shè)定的閾值;

需要注意的是,以上設(shè)定的自適應(yīng)保護規(guī)則,會對所有入口流量的資源生效。

    /**
     * 系統(tǒng)自適應(yīng)保護規(guī)則測試
     */
    @GetMapping("/getAdapt1")
    // 標識為入口流量資源
    @SentinelResource(entryType = EntryType.IN)
    public String getAdapt1(){
        return "adapt1";
    }

    @GetMapping("/getAdapt2")
    // 未標識為入口資源,但也屬于入口流量
    public String getAdapt2(){
        return "adapt2";
    }

以上兩個都屬于系統(tǒng)的入口流量資源,當超過設(shè)定的閾值后,都會被Sentinel自動降級處理,比如返回結(jié)果如下:

Blocked by Sentinel (flow limiting) 

九、授權(quán)控制

主要作用就是對資源的訪問進行授權(quán)控制,通過黑白名單的形式來限制某個請求是否有權(quán)限訪問我們的某個資源。

  • 白名單,在白名單中的請求來源(IP)允許訪問資源;
  • 黑名單,在黑名單中的請求來源(IP)不允許訪問資源;

原理就是通過HttpServletRequest.getRemoteAddr()來獲取請求方的IP地址,從而判斷其是否在黑、白名單中來達到限制的目的。

十、動態(tài)規(guī)則擴展

在上面的例子中,講到規(guī)則的配置只有兩種方式,要么在代碼里面寫好了,要么在Sentinel Dashboard上進行配置。在實際的場景中,我們很少采用第一種方式,因為如果涉及到規(guī)則的變更那就需要改動代碼了,十分不便,那么對于第二種方式,配置的規(guī)則是保存在內(nèi)存中的,一旦服務(wù)重啟規(guī)則就沒有了,所以不能持久化。

動態(tài)規(guī)則要想實現(xiàn)擴展,一般都是結(jié)合配置中心來實現(xiàn)持久化的,同時也方便動態(tài)地更改規(guī)則。Sentinel支持Consul、Zookeeper、Redis、Nacos、Apollo、etcd等數(shù)據(jù)源的持久化支持,本文則演示使用Nacos的例子。

開始之前,可以先參考Nacos使用入門了解下Nacos的基本入門使用,我們需要先將Nacos服務(wù)端啟動起來。

然后,我們的sentinel項目中需要引入nacos的datasource支持:

        <!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.2</version>
            <scope>test</scope>
        </dependency>

然后在nacos服務(wù)端,新建并寫上我們的限流規(guī)則:

[
    {
        "resource": "/getNacos",
        "limitApp": "default",
        "grade": 1,
        "count": 2,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

對應(yīng)dataId為自定義的,比如sentinel-nacos;group就是用DEFAULT_GROUP。

然后,我們就需要將如上Nacos中的限流規(guī)則加載到應(yīng)用中,如下兩種方式是等同的,可以視情況選擇一種。

  • init
    我們在上面getHello的例子中使用過在代碼中配置限流規(guī)則的案例,現(xiàn)在把其改為:
    /**
     * 使用Nacos數(shù)據(jù)源來管理規(guī)則
     */
    @PostConstruct
    public void initDataSource() {
        String remoteAddress = "192.168.31.17:8848";
        String groupId = "DEFAULT_GROUP";
        String dataId = "sentinel-nacos";
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId
                , source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
        }));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    }

如上代碼即是在初始化的時候就將Nacos上的限流規(guī)則加載進來了,此時我們重啟項目,對/getNacos資源的限流就生效了,此時查看Sentinel Dashboard的限流規(guī)則就能看到這條規(guī)則,并且重啟后不會丟失。

注意:@PostConstruct只能有一個,如果有多個,那么按照順序只有第一個生效,所以此處需要替換/getHello中的例子,而不是增加。

  • 配置
    新建一個類:
public class DataSourceInitFunc implements InitFunc {
    @Override
    public void init() throws Exception {
        String remoteAddress = "192.168.31.17:8848";
        String groupId = "DEFAULT_GROUP";
        String dataId = "sentinel-nacos";

        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    }
}

然后在resources目錄下,新建目錄META-INF/services,然后再創(chuàng)建一個文件,名稱為com.alibaba.csp.sentinel.init.InitFunc,里面寫上剛才新建類的名稱,比如:

com.example.sentineldemo.config.DataSourceInitFunc

此時就OK了。

注意,當啟用Nacos作為配置規(guī)則的數(shù)據(jù)源之后,代碼里面init時配置的規(guī)則就不再生效了。

十一、參考資料:

官方文檔講解的很詳細很到位,各種問題和案例基本都能找到,推薦閱讀官方文檔。

介紹 · alibaba/Sentinel Wiki · GitHub

?著作權(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)容