1. 程式人生 > 程式設計 >Spring Cloud Alibaba-Sentinel(十五)

Spring Cloud Alibaba-Sentinel(十五)

簡介

A lightweight powerful flow control component enabling reliability and monitoring for microservices. (輕量級的流量控制、熔斷降級 Java 庫).中文檔案

雪崩效應

雪崩效應又稱cascading failure(級聯故障),指基礎服務故障導致上層服務故障並且故障像雪球一樣越滾越大。

常見容錯方案

  • 超時
  • 限流
  • 艙壁模式:每個controller獨立配置執行緒池,互不幹擾。
  • 斷路器模式:服務低於閾值(錯誤次數/錯誤率等構成),斷路器開啟,過了一段時間後允許一個服務呼叫,如果成功,斷路器恢復。

整合Sentinel

  • 加依賴
        <!--sentinel-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sentinel</artifactId>
        </dependency>
複製程式碼
  • 寫註解
複製程式碼
  • 寫配置
複製程式碼

actuator/sentinel節點

開啟所有端點檢視是否生效

# actuator
management:
  endpoints:
    web:
      exposure:
        # 生產不能全部放開且需配置安全措施
        include: '*'
複製程式碼

出現以下內容證明整合成功了

Sentinel控制檯

下載我們pom檔案定義的相同版本的Sentinel。下載地址

#啟動
java -jar XXX.jar
複製程式碼
  • 訪問登陸介面http://localhost:8080/#/login

  • 預設賬號密碼sentinel/sentinel
  • 專案整合
spring:
  cloud:
    sentinel:
      transport:
        # 指定sentinel控制檯地址
dashboard: localhost:8080 複製程式碼
  • 懶載入 由於Sentinel預設是懶載入的,所以請求任意一個介面後才會又檢視

功能說明

  • 流控規則

    • 流控模式
      • 直接

      • 關聯:關聯資源達到閾值,限流自己

      • 鏈路:指定鏈路上的流量

        • 程式碼
        // 定義一個common服務
        package com.virgo.user.service;
        
        import com.virgo.entity.TblCar;
        
        /**
         * @author zhaozha
         * @date 2019/10/15 下午4:27
         */
        public interface CommonService {
            TblCar common();
        }
        
        package com.virgo.user.service;
        
        import com.alibaba.csp.sentinel.annotation.SentinelResource;
        import com.virgo.entity.TblCar;
        import org.springframework.stereotype.Service;
        
        /**
         * @author zhaozha
         * @date 2019/10/15 下午4:28
         */
        @Service
        public class CommonServiceImpl implements CommonService {
        
            @Override
            @SentinelResource("common")
            public TblCar common() {
                return TblCar.builder().id(1L).build();
            }
        }
        
        // controller層同時呼叫這個服務
            @GetMapping("/test/a")
        public String testA() {
            commonServiceImpl.common();
            return "testA";
        }
        
        @GetMapping("/test/b")
        public String testB() {
            commonServiceImpl.common();
            return "testB";
        }
        
        複製程式碼
        • 設定:只對/test/a限流,對/test/b無影響

    • 流控效果
      • 快速失敗
        • 直接失敗,丟擲異常
        • 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
      • Warm Up
        • 初始為閾值/codeFactor(預設為3),經過預熱時長,才達到閾值
        • 官方檔案
        • 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
      • 排隊等待
        • 勻速排隊,閾值必須設定為QPS
        • 官方檔案
        • 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
  • 降級規則

    • 降級策略
      • RT(平均響應時間,秒級別)
        • 平均響應時間超出閾值且在時間視窗內通過的請求>=5
        • 視窗期過後關閉斷路器
        • RT最大4900(更大的需要通過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
      • 異常比列(秒級別)
      • 異常數(分鐘級別)
  • 熱點規則

    • 引數級別的流控規則
    • 可以對指定引數/指定引數的指定值限流
    • 需要帶 @SentinelResource
  • 系統規則

    • LOAD
      • 當系統load1(1分鐘的load)超過閥值,且併發執行緒數超過系統容量時觸發,建議設定為cpu核心數*2(Linux/Unix-like機器生效)
      • 系統容量=maxQps(秒級最大QPS)*minRt(秒級最小響應時間)
      • uptime命令-> 1 5 15(系統平均負載/load)
    • RT
      • 所有入口流量的平均RT達到閾值
    • 執行緒數
      • 所有入口流量的併發執行緒數達到閾值
    • 入口QPS
      • 所有入口流量的QPS達到閾值
  • 授權規則

    • 對訪問的微服務設定黑白名單

微服務和控制檯通訊原理

微服務註冊/心跳到控制檯,控制檯通過api獲取/推送訊息

yml配置

spring:
  cloud:
    sentinel:
      transport:
        控制檯地址
        dashboard:
        和控制檯通訊的ip
        client-ip:
        和控制通訊埠(預設8719,如果已經佔用,+1直到找到)
        port:
        心跳毫秒
        heartbeat-interval-ms:
複製程式碼

控制檯啟動引數

配置項 預設值 描述
server.port 8080 指定埠
csp.sentinel.dashboard.server localhost:8080 指定地址
project.name - -
sentinel.dashboard.auth.username[1.6] sentinel Dashboard登陸賬號
sentinel.dashboard.auth.password[1.6] sentinel Dashboard登陸密碼
server.servlet.session.timeout[1.6] 30分鐘 登陸session過期時間(7200:7200秒/60m:60分鐘)

API(可以保護任意資源)

    @GetMapping("/test/sentinel/api")
    public String testSentinelApi(@RequestParam(required = false) String a) {
        String resourceName = "test-sentinel-api";
        ContextUtil.enter(resourceName,"test");
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName);
            if (StringUtils.isBlank(a)) {
                throw new IllegalArgumentException("引數不可為空");
            }
            return a;
        } catch (BlockException e) {
            return "限流/降級了";
        } catch (IllegalArgumentException e) {
            Tracer.trace(e);
            return "引數非法";
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }
複製程式碼

@SentinelResource

通過@SentinelResource簡化程式碼

    package com.virgo.user.sentinel;

    import com.alibaba.csp.sentinel.slots.block.BlockException;
    
    /**
     * @author zhaozha
     * @date 2019/10/16 下午3:11
     */
    public class ControllerBlockHandlerClass {
        /**
         * 處理限流/降級
         *
         * @param a
         * @param e
         * @return
         */
        public static String block(String a,BlockException e) {
            return "降級/限流了";
        }
    }

    @GetMapping("/test/sentinel/api")
    @SentinelResource(
            value = "test-sentinel-api",blockHandler = "block",blockHandlerClass = com.virgo.user.sentinel.ControllerBlockHandlerClass.class
    )
    public String testSentinelApi(@RequestParam(required = false) String a) {
        if (StringUtils.isBlank(a)) {
            throw new IllegalArgumentException("引數不可為空");
        }
        return a;
    }
複製程式碼

整合RestTemplate

  • 註解
    @Bean
    @LoadBalanced
    @SentinelRestTemplate
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
複製程式碼
  • 開關
resttemplate:
  sentinel:
    enabled: 
複製程式碼

Feign整合Sentinel

feign:
  #feign整合sentinel
  sentinel
    enable: true
複製程式碼

自定義返回報文

  • fallback
package com.virgo.user.feignclient;

import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallback.LockCenterFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author zhaozha
 * @date 2019/10/11 下午12:52
 */
@FeignClient(name = "lock-center",configuration = GlobalFeignConfiguration.class,fallback = LockCenterFeignClientFallback.class)
public interface LockCenterFeignClient {

    @GetMapping("/lock/test/{id}")
    TblCar findById(@PathVariable(value="id") Long id);
}


package com.virgo.user.feignclient.fallback;

import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import org.springframework.stereotype.Component;

/**
 * @author zhaozha
 * @date 2019/10/16 下午3:36
 */
@Component
public class LockCenterFeignClientFallback implements LockCenterFeignClient {
    @Override
    public TblCar findById(Long id) {
        return TblCar.builder().level(1).build();
    }
}
複製程式碼
  • fallbackFactory(可以捕獲異常)
package com.virgo.user.feignclient;

import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallbackFactory.LockCenterFeignClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author zhaozha
 * @date 2019/10/11 下午12:52
 */
@FeignClient(name = "lock-center",fallbackFactory = LockCenterFeignClientFallbackFactory.class)
public interface LockCenterFeignClient {

    @GetMapping("/lock/test/{id}")
    TblCar findById(@PathVariable(value="id") Long id);
}

package com.virgo.user.feignclient.fallbackFactory;

import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author zhaozha
 * @date 2019/10/16 下午4:02
 */
@Component
@Slf4j
public class LockCenterFeignClientFallbackFactory implements FallbackFactory<LockCenterFeignClient> {
    @Override
    public LockCenterFeignClient create(Throwable throwable) {
        return new LockCenterFeignClient() {
            @Override
            public TblCar findById(Long id) {
                log.info("限流/降級",throwable);
                return TblCar.builder().level(1).build();
            }
        };
    }
}

複製程式碼

使用方式

使用方式 使用方式 使用方法
編碼方式 API try...catch...finally
註解方式 @SentinelResource blockHandler/fallback
RestTemplate @SentinelRestTemplate blockHandler/fallback
Feign feign.sentinel.enabled fallback/fallbackFactory

配置持久化

叢集流控

官方檔案

優化

  • 錯誤頁優化(UrlBlockHandler)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.virgo.dto.CommonResult;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author zhaozha
 * @date 2019/10/17 上午9:49
 */
@Component
public class VirgoUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,BlockException e) throws IOException {
        // 統一返回物件
        CommonResult commonResult = null;
        // 流控
        if(e instanceof FlowException){
            commonResult = CommonResult.builder().status(100).msg("流控異常").build();
        }
        // 降級
        else if(e instanceof DegradeException){
            commonResult = CommonResult.builder().status(101).msg("降級異常").build();
        }
        // 熱點
        else if(e instanceof ParamFlowException){
            commonResult = CommonResult.builder().status(102).msg("熱點異常").build();
        }
        // 系統
        else if(e instanceof SystemBlockException){
            commonResult = CommonResult.builder().status(102).msg("系統異常").build();
        }
        // 授權
        else if (e instanceof AuthorityException){
            commonResult = CommonResult.builder().status(102).msg("授權異常").build();
        }

        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        new ObjectMapper()
                .writeValue(
                        httpServletResponse.getWriter(),commonResult
                );

    }
}

複製程式碼
  • 區分來源(RequestOriginParser)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @author zhaozha
 * @date 2019/10/17 上午11:51
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        // todo 改成header
        String origin = httpServletRequest.getParameter("origin");
        if (StringUtils.isBlank(origin)) {
            throw new IllegalArgumentException("origin must be specified");
        }
        return origin;
    }
}

複製程式碼
  • Restful url支援(UrlCleaner)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author zhaozha
 * @date 2019/10/17 下午12:05
 */
@Component
public class VirgoUrlCleaner implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        // todo 多引數
        String[] split = originUrl.split("/");

        return Arrays.stream(split)
                .map(string -> {
                    if (NumberUtils.isNumber(string)) {
                        return "{number}";
                    }
                    return string;
                })
                .reduce((a,b) -> a + "/" + b)
                .orElse("");
    }
}

複製程式碼

配置

官方檔案

生產環境

  • 阿里AHAS