14.微服務(wù)保護Sentinel

一、Sentinel安裝介紹

  1. 雪崩問題
    微服務(wù)調(diào)用鏈路中的某個服務(wù)故障,引起整個鏈路中的所有微服務(wù)都不可用,這就是雪崩。
  • 解決雪崩問題的常見方式有四種:
    1. 超時處理:設(shè)定超時時間,請求超過一定時間沒有響應(yīng)就返回錯誤信息,不會無休止等待
    2. 艙壁模式:限定每個業(yè)務(wù)能使用的線程數(shù),避免耗盡整個tomcat的資源,因此也叫線程隔離。
    3. 熔斷降級:由斷路器統(tǒng)計業(yè)務(wù)執(zhí)行的異常比例,如果超出閾值則會熔斷該業(yè)務(wù),攔截訪問該業(yè)務(wù)的一切請求
    4. 流量控制:限制業(yè)務(wù)訪問的QPS,避免服務(wù)因流量的突增而故障。

安裝控制臺
GitHub下載任意一個版本的jar文件,然后使用

$ java -jar sentinel-dashboard-1.8.4.jar

如果出現(xiàn)錯誤,可以嘗試先指定jdk版本,路徑改成自己本地的目錄地址

$ set Path=K:\java\jdk1.8.0_321\jre\bin 

訪問http://localhost:8080/
默認用戶名和密碼都是 sentinel。

微服務(wù)整合Sentinel

  1. 引入依賴
        <!--引入Sentinel依賴-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
  1. 配置控制臺地址
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服務(wù)地址
    sentinel:
      transport:
        dashboard: localhost:8080 # Sentinel控制臺地址
  1. 訪問微服務(wù)的任意端點,觸發(fā)sentinel監(jiān)控,之后即可在控制臺看到信息。

二、Sentinel限流規(guī)則

點擊資源/order/{orderId}后面的流控按鈕,就可以彈出表單。表單中可以添加流控規(guī)則,如下圖所示:


image.png

其含義是限制 /order/{orderId}這個資源的單機QPS為1,即每秒只允許1次請求,超出的請求會被攔截并報錯。

  • 在添加限流規(guī)則時,點擊高級選項,可以選擇三種流控模式:
  1. 直接:統(tǒng)計當前資源的請求,觸發(fā)閾值時對當前資源直接限流,也是默認的模式
  2. 關(guān)聯(lián):統(tǒng)計與當前資源相關(guān)的另一個資源,觸發(fā)閾值時,對當前資源限流
  3. 鏈路:統(tǒng)計從指定鏈路訪問到本資源的請求,觸發(fā)閾值時,對指定鏈路限流

關(guān)聯(lián)模式

  • 關(guān)聯(lián)模式:統(tǒng)計與當前資源相關(guān)的另一個資源,觸發(fā)閾值時,對當前資源限流
  • 使用場景:比如用戶支付時需要修改訂單狀態(tài),同時用戶要查詢訂單。查詢和修改操作會爭搶數(shù)據(jù)庫鎖,產(chǎn)生競爭。業(yè)務(wù)需求是有限支付和更新訂單的業(yè)務(wù),因此當修改訂單業(yè)務(wù)觸發(fā)閾值時,需要對查詢訂單業(yè)務(wù)限流。


    image.png

    當/write資源訪問量觸發(fā)閾值時,就會對/read資源限流,避免影響/write資源。

流控模式-鏈路
鏈路模式:只針對從指定鏈路訪問到本資源的請求做統(tǒng)計,判斷是否超過閾值。
例如有兩條請求鏈路:

  • /test1 到 /common
  • /test2 到/common
    如果只希望統(tǒng)計從/test2進入到/common的請求,則可以這樣配置:


    image.png
  1. Sentinel默認只標記Controller中的方法為資源,如果要標記其它方法,需要利用@SentinelResource注解,示
  @SentinelResource("goods")
  public void queryGoods() {
    System.out.println("商品信息");
  }
  1. Sentinel默認會將Controller方法做context整合,導(dǎo)致鏈路模式的流控失效,需要修改application.yml,添加配置:
spring:
  application:
    name: orderservice
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # Sentinel控制臺地址
      web-context-unify: false #關(guān)閉context整合

流控模式有哪些?

  • 直接:對當前資源限流
  • 關(guān)聯(lián):高優(yōu)先級資源觸發(fā)閾值,對低優(yōu)先級資源限流。
  • 鏈路:閾值統(tǒng)計時,只統(tǒng)計從指定資源進入當前資源的請求,是對請求來源的限流

二、流控效果

  • 流控效果是指請求達到流控閾值時應(yīng)該采取的措施,包括三種:
  1. 快速失?。哼_到閾值后,新的請求會被立即拒絕并拋出FlowException異常。是默認的處理方式。
  2. warm up:預(yù)熱模式,對超出閾值的請求同樣是拒絕并拋出異常。但這種模式閾值會動態(tài)變化,從一個較小值逐漸增加到最大閾值。
  3. 排隊等待:讓所有的請求按照先后次序排隊執(zhí)行,兩個請求的間隔不能小于指定時長


    image.png

流控效果-warm up
warm up也叫預(yù)熱模式,是應(yīng)對服務(wù)冷啟動的一種方案。請求閾值初始值是 threshold / coldFactor,持續(xù)指定時長后,逐漸提高到threshold值。而coldFactor的默認值是3.
例如,我設(shè)置QPS的threshold為10,預(yù)熱時間為5秒,那么初始閾值就是 10 / 3 ,也就是3,然后在5秒后逐漸增長到10

流控效果-排隊等待
當請求超過QPS閾值時,快速失敗和warm up 會拒絕新的請求并拋出異常。而排隊等待則是讓所有請求進入一個隊列中,然后按照閾值允許的時間間隔依次執(zhí)行。后來的請求必須等待前面執(zhí)行完成,如果請求預(yù)期的等待時間超出最大時長,則會被拒絕。
例如:QPS = 5,意味著每200ms處理一個隊列中的請求;timeout = 2000,意味著預(yù)期等待超過2000ms的請求會被拒絕并拋出異常

三、熱點參數(shù)限流

之前的限流是統(tǒng)計訪問某個資源的所有請求,判斷是否超過QPS閾值。而熱點參數(shù)限流是分別統(tǒng)計參數(shù)值相同的請求,判斷是否超過QPS閾值。

image.png

代表的含義是:對hot這個資源的0號參數(shù)(第一個參數(shù))做統(tǒng)計,每1秒相同參數(shù)值的請求數(shù)不能超過5

  • 在熱點參數(shù)限流的高級選項中,可以對部分參數(shù)設(shè)置例外配置:


    image.png

    結(jié)合上一個配置,這里的含義是對0號的long類型參數(shù)限流,每1秒相同參數(shù)的QPS不能超過5,有兩個例外:
    1.如果參數(shù)值是100,則每1秒允許的QPS為10
    2.如果參數(shù)值是101,則每1秒允許的QPS為15

  • 注意:熱點參數(shù)限流對默認的SpringMVC資源無效,即必須為用@SentinelResource注解配置的資源才能生效

四、隔離降級

雖然限流可以盡量避免因高并發(fā)而引起的服務(wù)故障,但服務(wù)還會因為其它原因而故障。而要將這些故障控制在一定范圍,避免雪崩,就要靠線程隔離(艙壁模式)和熔斷降級手段了
不管是線程隔離還是熔斷降級,都是對客戶端(調(diào)用方)的保護。
Feign整合Sentinel
SpringCloud中,微服務(wù)調(diào)用都是通過Feign來實現(xiàn)的,因此做客戶端保護必須整合Feign和Sentinel。

  1. 修改OrderService的application.yml文件,開啟Feign的Sentinel功能
feign:
  sentinel:
    enabled: true
  1. 給FeignClient編寫失敗后的降級邏輯
  • 步驟一:在feing-api項目中定義類,實現(xiàn)FallbackFactory:
package cn.itcast.feign.clients.fallback;

import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;

/**
 * @author ylf
 * @version 1.0
 */
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
  @Override
  public UserClient create(Throwable throwable) {
    return new UserClient() {
      @Override
      public User findById(Long id) {
        // 填寫異常后的處理
        log.error("查詢用戶異常", throwable);
        return new User();
      }
    };
  }
}

  • 步驟二:在feing-api項目中的DefaultFeignConfiguration類中將UserClientFallbackFactory注冊為一個Bean:
package cn.itcast.feign.config;

import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfiguration {
  @Bean
  public Logger.Level logLevel() {
    return Logger.Level.BASIC;
  }

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

  • 步驟三:在feing-api項目中的UserClient接口中使用UserClientFallbackFactory:
package cn.itcast.feign.clients;

import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

  @GetMapping("/user/{id}")
  User findById(@PathVariable("id") Long id);
}

線程隔離

  1. 線程池隔離
  • 優(yōu)點:支持主動超時,支持異步調(diào)用。
  • 缺點:線程的額外開銷大
  • 場景: 低扇出
  1. 信號量隔離
  • 優(yōu)點:輕量級,無額外開銷
  • 缺點:不支持主動超時,不支持異步調(diào)用
  • 場景:高扇出

線程隔離(艙壁模式)
在添加限流規(guī)則時,可以選擇兩種閾值類型:


image.png
  • QPS:就是每秒的請求數(shù),在快速入門中已經(jīng)演示過
  • 線程數(shù):是該資源能使用用的tomcat線程數(shù)的最大值。也就是通過限制線程數(shù)量,實現(xiàn)艙壁模式。

熔斷降級
熔斷降級是解決雪崩問題的重要手段。其思路是由斷路器統(tǒng)計服務(wù)調(diào)用的異常比例、慢請求比例,如果超出閾值則會熔斷該服務(wù)。即攔截訪問該服務(wù)的一切請求;而當服務(wù)恢復(fù)時,斷路器會放行訪問該服務(wù)的請求。

image-20210716130958518.png

熔斷策略-慢調(diào)用
斷路器熔斷策略有三種:慢調(diào)用、異常比例、異常數(shù)

  • 慢調(diào)用:業(yè)務(wù)的響應(yīng)時長(RT)大于指定時長的請求認定為慢調(diào)用請求。在指定時間內(nèi),如果請求數(shù)量超過設(shè)定的最小數(shù)量,慢調(diào)用比例大于設(shè)定的閾值,則觸發(fā)熔斷。例如:


    image-20210716145934347.png

    解讀:RT超過500ms的調(diào)用是慢調(diào)用,統(tǒng)計最近10000ms內(nèi)的請求,如果請求量超過10次,并且慢調(diào)用比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。

熔斷策略-異常比例、異常數(shù)

  • 異常比例或異常數(shù):統(tǒng)計指定時間內(nèi)的調(diào)用,如果調(diào)用次數(shù)超過指定請求數(shù),并且出現(xiàn)異常的比例達到設(shè)定的比例閾值(或超過指定異常數(shù)),則觸發(fā)熔斷。例如:


    image.png

    image.png

    解讀:統(tǒng)計最近1000ms內(nèi)的請求,如果請求量超過10次,并且異常比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。

Sentinel熔斷降級的策略有哪些?

  • 慢調(diào)用比例:超過指定時長的調(diào)用為慢調(diào)用,統(tǒng)計單位時長內(nèi)慢調(diào)用的比例,超過閾值則熔斷
  • 異常比例:統(tǒng)計單位時長內(nèi)異常調(diào)用的比例,超過閾值則熔斷
  • 異常數(shù):統(tǒng)計單位時長內(nèi)異常調(diào)用的次數(shù),超過閾值則熔斷

五、授權(quán)規(guī)則

授權(quán)規(guī)則可以對調(diào)用方的來源做控制,有白名單和黑名單兩種方式。
白名單:來源(origin)在白名單內(nèi)的調(diào)用者允許訪問
黑名單:來源(origin)在黑名單內(nèi)的調(diào)用者不允許訪問

  1. 給/order/{orderId} 配置授權(quán)規(guī)則:


    image.png
  2. Sentinel是通過RequestOriginParser這個接口的parseOrigin來獲取請求的來源的。例如,在order-service服務(wù)中我們嘗試從request中獲取一個名為origin的請求頭,作為origin的值:

package cn.itcast.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * @author ylf
 * @version 1.0
 */
@Component
public class HeaderOriginParser implements RequestOriginParser {
  @Override
  public String parseOrigin(HttpServletRequest httpServletRequest) {
    // 1.獲取請求頭
    String origin = httpServletRequest.getHeader("origin");
    System.out.println("xxxx" + origin);
    // 2.非空判斷
    if (StringUtils.isEmpty(origin)) {
      origin = "blank";
    }
    return origin;
  }
}

  1. 我們還需要在gateway服務(wù)中,利用網(wǎng)關(guān)的過濾器添加名為gateway的origin頭:
spring:
  application:
    name: gateway
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway

六、自定義異常結(jié)果

package cn.itcast.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e)
      throws Exception {
    String msg = "未知異常";
    int status = 429;

    if (e instanceof FlowException) {
      msg = "請求被限流了";
    } else if (e instanceof ParamFlowException) {
      msg = "請求被熱點參數(shù)限流";
    } else if (e instanceof DegradeException) {
      msg = "請求被降級了";
    } else if (e instanceof AuthorityException) {
      msg = "沒有權(quán)限訪問";
      status = 401;
    }

    response.setContentType("application/json;charset=utf-8");
    response.setStatus(status);
    response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
  }
}

七、規(guī)則持久化

規(guī)則管理模式

  • 原始模式:保存在內(nèi)存,重啟服務(wù)就會消失
  • pull模式:保存在本地文件或數(shù)據(jù)庫,定時去讀取
  • push模式:保存在nacos,監(jiān)聽變更實時更新

實現(xiàn)push模式

一、修改order-service服務(wù)

修改OrderService,讓其監(jiān)聽Nacos中的sentinel規(guī)則配置。

具體步驟如下:

1.引入依賴

在order-service中引入sentinel監(jiān)聽nacos的依賴:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2.配置nacos地址

在order-service中的application.yml文件配置nacos地址及監(jiān)聽的配置信息:

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 # nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow # 還可以是:degrade、authority、param-flow

二、修改sentinel-dashboard源碼

SentinelDashboard默認不支持nacos的持久化,需要修改源碼。

1. 解壓

解壓課前資料中的sentinel源碼包:

image-20210618201340086.png

或者到GitHub下載GitHub

然后并用IDEA打開這個項目,結(jié)構(gòu)如下:

image-20210618201412878.png

2. 修改nacos依賴

在sentinel-dashboard源碼的pom文件中,nacos的依賴默認的scope是test,只能在測試時使用,這里要去除:

image-20210618201607831.png

將sentinel-datasource-nacos依賴的scope去掉:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

3. 添加nacos支持

在sentinel-dashboard的test包下,已經(jīng)編寫了對nacos的支持,我們需要將其拷貝到main下。

image-20210618201726280.png

4. 修改nacos地址

然后,還需要修改測試代碼中的NacosConfig類:

image-20210618201912078.png

修改其中的nacos地址,讓其讀取application.properties中的配置:

image-20210618202047575.png

在sentinel-dashboard的application.properties中添加nacos地址配置:

nacos.addr=localhost:8848

5. 配置nacos數(shù)據(jù)源

另外,還需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2類:

image-20210618202322301.png

讓我們添加的Nacos數(shù)據(jù)源生效:

image-20210618202334536.png

6. 修改前端頁面

接下來,還要修改前端頁面,添加一個支持nacos的菜單。

修改src/main/webapp/resources/app/scripts/directives/sidebar/目錄下的sidebar.html文件:

image-20210618202433356.png

將其中的這部分注釋打開:

image-20210618202449881.png

修改其中的文本:

image-20210618202501928.png

7. 重新編譯、打包項目

運行IDEA中的maven插件,編譯和打包修改好的Sentinel-Dashboard:

image-20210618202701492.png

8.啟動

啟動方式跟官方一樣:

java -jar sentinel-dashboard.jar

如果要修改nacos地址,需要添加參數(shù):

java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar
最后編輯于
?著作權(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)容