sentinel接入指南

寫在前面

主要介紹sentinel的限流熔斷如何使用,如何與api網關進行整合,以及其dashboard的使用等;
sentinel 地址 https://github.com/alibaba/Sentinel
sentinel dashboard地址 https://github.com/alibaba/Sentinel/tree/master/sentinel-dashboard
sentinel wiki地址 https://github.com/alibaba/Sentinel/wikijava

sentinel在普通項目中使用

  • 須要引入其依賴包(此處以version=1.6.1舉例)
<properties>
        <sentinel.version>1.6.1</sentinel.version>
    </properties>
    
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-core</artifactId>
        <version>${sentinel.version}</version>
    </dependency>
複製代碼

限流使用

限流詳細文檔地址:github.com/alibaba/Sen…git

下面舉個簡單的使用示例:
github

  • 限流簡單配置代碼
/** * 基於QPS的資源限流 * @param name -- 資源名稱 * @param count -- 數量 */
    public static void flowRule(String name, int count) {
        //限流規則,能夠多個flow rule,該規則支持QPS和併發線程數的限流
        //FlowRuleManager.getRules()能夠獲取到已經設置的限流規則
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //設置資源名稱,sentinel限流都是以資源爲單位進行
        rule.setResource(name);
        //使用QPS限流
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //QPS達到的數量閾值
        rule.setCount(count);
        rules.add(rule);
        //從新加載限流規則,此處將覆蓋原有的限流,因此若是想要不覆蓋
        //請使用FlowRuleManager.getRules()獲取到的加入到rules中
        FlowRuleManager.loadRules(rules);
    }
複製代碼
  • 限流使用示例以下:
public static void main(String[] args) {
        //執行限流
        flow("sayHello", 3);
    }

    public static void flow(String resourceName, int count) {
        // 配置規則,每秒能夠過3個hello world的請求
        flowRule(resourceName, count);

        while (true) {
            for (int i = 0; i <= count; i++) {
                //entry這裏是關鍵邏輯,它會基於滑動時間窗口來進行限流統計;
                //因此此處纔是限流的關鍵
                try (Entry entry = SphU.entry(resourceName)) {
                    sayHello("" + (int)entry.getCurNode().successQps());
                } catch (BlockException ex) {
                    // 處理被流控的邏輯
                    System.out.println("blocked!");
                }
            }
            sleepSeconds(1);
        }
    }

    //須要限流的方法
    public static void sayHello(String info) {
        System.out.println("hello world! before success count = " + info);
    }

    //按秒睡眠
    private static void sleepSeconds(long time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
複製代碼

運行上面的代碼後,輸出結果以下(從結果能夠發現,程序以恆定每秒4次來調用被限流的方法,而配置中方法能支持的QPS爲3次,因此每次調用都會有一次被保護,從而走限流分支,在限流分支中能夠寫本身的業務邏輯):spring

hello world! before success count = 0
hello world! before success count = 1
hello world! before success count = 2
blocked!
hello world! before success count = 0
hello world! before success count = 1
hello world! before success count = 2
blocked!
hello world! before success count = 0
hello world! before success count = 1
hello world! before success count = 2api

熔斷使用

  • 熔斷規則配置代碼:
/** * 熔斷降級規則配置 * @param name -- 資源名稱 * @param count -- 數量 */
    public static void degradeRule(String name, int count) {
        //降級規則,能夠多個degradeRule rule
        //DegradeRuleManager.getRules()能夠獲取到已經設置的降級規則
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        //設置資源名稱,sentinel降級都是以資源爲單位進行
        rule.setResource(name);
        //使用異常統計降級,分鐘統計,滑動時間窗口
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        //異常數達到的數量閾值
        rule.setCount(count);
        //秒級時間窗口,該值必須有且必須大於零,不然降級將沒法生效
        rule.setTimeWindow(10);
        rules.add(rule);
        //從新加載限流規則,此處將覆蓋原有的限流,因此若是想要不覆蓋
        //請使用DegradeRuleManager.getRules()獲取到的加入到rules中
        DegradeRuleManager.loadRules(rules);
    }
複製代碼
  • 熔斷測試調用代碼:

限流代碼中的main調用degrade方法便可springboot

public static void degrade(String resourceName, int count) {
        // 配置熔斷規則,發生count個異常即將進行熔斷
        degradeRule(resourceName, count * 2);
        int sum = 0;
        while (true) {
            for (int i = 0; i <= count; i++) {
                //因此此處纔是限流的關鍵
                Entry entry = null;
                try {
                    entry = SphU.entry(resourceName);
                    sayHello(++sum + "");
                } catch (BlockException e) {
                    System.out.println("被降級了:" + e);
                } catch (Exception ex) {
                    // 處理業務異常,調用tracer才能統計異常數
                    Tracer.trace(ex);
                } finally {
                    if (entry != null) {
                        entry.exit();
                    }
                }
            }
            System.out.println("===============分割線================");
            sleepSeconds(1);
        }
    }
    
    public static void sayHello(String info) {
        System.out.println("hello world! before success count = " + info);
        throw new RuntimeException("test degrade");
    }
複製代碼

運行代碼後得到結果以下(在一分鐘的滑動時間窗口下,異常超過6次則進行降級處理,直到時間窗口滑到小於6次異常纔會繼續被調用):併發

hello world! before success count = 1
hello world! before success count = 2
hello world! before success count = 3
hello world! before success count = 4
===============分割線================
hello world! before success count = 5
hello world! before success count = 6
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
===============分割線================
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
被降級了:com.alibaba.csp.sentinel.slots.block.degrade.DegradeException
maven

使用sentinel提供的aop集成限流降級

註解文檔地址:github.com/alibaba/Sen…
須要依賴springaop,使用springboot項目集成便可,前面的maven依賴不變,新增maven依賴以下:ide

<dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-annotation-aspectj</artifactId>
        <version>${sentinel.version}</version>
    </dependency>
複製代碼
  • 簡單展現使用註解方式來進行限流降級(能夠動態調整限流方案,也能夠全局設置降級和限流處理類等)函數

  • 待代用的測試服務:

其中fallback函數必須和原函數參數一致,handler函數能夠增長一個異常的參數,其餘參數保持不變。

@Service
public class SentinelAnnotationService {

    @SentinelResource(value = "helloService", blockHandler = "myHandler", fallback = "myFallback")
    public String sayHello(String name) {
        if (System.currentTimeMillis() % 2 == 0) {
            throw new RuntimeException("test");
        }
        return "return hello " + name;
    }

    public String myHandler(String name, BlockException ex) {
        return "return handler " + name;
    }

    public String myFallback(String name) {
        return "return fallback " + name;
    }
}
複製代碼
  • springboot調用類
@SpringBootApplication
public class Run {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Run.class, args);
        test(context.getBean(SentinelAnnotationService.class));
    }

    /** * 每10秒訪問5次以上次 */
    private static void test(SentinelAnnotationService testService) {
        //限流規則生成中
        AddRule.flowRule("helloService", 3);
        int sum = 10, total = sum;
        while (sum-- > 0) {
            for (int i = 1; i <= 5; i++) {
                String result = testService.sayHello("littlehow" + i);
                System.out.println("the " + (total - sum) + " result---->" + result);
            }
            sleepSeconds(10);
        }
    }

    //按秒睡眠
    private static void sleepSeconds(long time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /** * 這裏很重要,這裏是sentinel實現註解限流降級最關鍵的地方 * @return */
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}
複製代碼
  • 執行後結果以下:

執行結果的前三行輸出是不肯定的,由於異常是隨機拋出的,機率爲50%,拋出異常後,將執行fallback,後兩行的輸出證實每秒限流3次效果達到了;

the 1 result---->return hello littlehow1
the 1 result---->return hello littlehow2
the 1 result---->return fallback littlehow3
the 1 result---->return handler littlehow4
the 1 result---->return handler littlehow5

sentinel與api網關的集成

與api網關集成文檔地址:github.com/alibaba/Sen…

sentinel與spring cloud gateway集成

由於當前springboot與springcloud版本爲1.x,因此暫時集成不了spring cloud gateway;之後版本升級後能夠考慮進行集成;

sentinel與zuul1.x的集成

待續...

sentinel與zuul2.x的集成

目前sentinel官方還沒實現與zuul2.x集成

sentinel dashboard使用

dashboard的使用文檔地址:github.com/alibaba/Sen…

sentinel規則管理

dashboard的生產實踐地址:github.com/alibaba/Sen…

結合當前項目管理規則

sentinel提供了基於zk、apollo等配置中心來管理規則,基於apollo的規則須要項目內部寫入本身的namespace,因此基於sentinel的擴展,自定義規則管理;
tips:也能夠直接基於RuleManage進行改變

自定義規則改變不須要添加任何其餘依賴,核心是sentinel提供的datasource接口,實現以下:

  • 數據源公共處理類
@Slf4j
public abstract class CommonDataSource<T> extends AbstractDataSource<String, List<T>> {

    private String typeName;

    public CommonDataSource(RuleConverter<T> ruleConverter, String typeName) {
        super(ruleConverter);
        this.typeName = typeName;
    }

    @Override
    public void close() throws Exception {
        log.info("data source close");
    }

    /** * 修改規則 * @throws Exception */
    public void updateRules() {
        String rules = null;
        try {
            //加載改變後的規則值
            rules = this.readSource();
            log.info("update rules {}, typeName={}", rules, typeName);
            //將改變寫入RuleManage中
            this.getProperty().updateValue(this.parser.convert(rules));
        } catch (Exception e) {
            log.error("update rules {} fail, typeName={}", rules, typeName, e);
        }
    }
}
複製代碼
  • 公共規則轉換器
abstract class RuleConverter<T> implements Converter<String, List<T>> {
    private static final List EMPTY_LIST = new ArrayList<>(0);

    /** * 判斷是否爲空 * @param value */
    protected boolean isEmpty(String value) {
        return null == value || "".equals(value) || "0".equals(value);
    }

    /** * 建立flow規則 * @param info * @return */
    protected List<FlowRule> createFlowRule(String info) {
        if (isEmpty(info)) {
            return EMPTY_LIST;
        }
        List<FlowRule> flowRules = new ArrayList<>();
        //解析出錯將異常拋往上層,只簡單配置resourceName,grade,count
        for (String s : getRulesInfo(info)) {
            String[] ruleInfo = getRuleInfo(s);
            FlowRule rule = new FlowRule(ruleInfo[0].trim())
                    .setGrade(Integer.parseInt(ruleInfo[1]))
                    .setCount(Double.parseDouble(ruleInfo[2]));
            flowRules.add(rule);
        }
        return flowRules;
    }

    protected List<DegradeRule> createDegradeRule(String info) {
        if (isEmpty(info)) {
            return EMPTY_LIST;
        }
        List<DegradeRule> degradeRules = new ArrayList<>();
        //解析出錯將異常拋往上層,只簡單配置resourceName,grade,count,timeWindow
        for (String s : getRulesInfo(info)) {
            String[] ruleInfo = getRuleInfo(s);
            DegradeRule rule = new DegradeRule(ruleInfo[0].trim())
                    .setGrade(Integer.parseInt(ruleInfo[1]))
                    .setCount(Double.parseDouble(ruleInfo[2]))
                    .setTimeWindow(Integer.parseInt(ruleInfo[3]));
            degradeRules.add(rule);
        }
        return degradeRules;
    }


    private String[] getRulesInfo(String info) {
        return info.trim().split("#");
    }

    private String[] getRuleInfo(String info) {
        return info.trim().split(",");
    }

}
複製代碼
  • 限流規則轉換器
@Slf4j
public class FlowRuleConverter extends RuleConverter<FlowRule> {
    @Override
    public List<FlowRule> convert(String s) {
        log.info("update flow rule to {}", s);
        return createFlowRule(s);
    }

    public static FlowRuleConverter create() {
        return new FlowRuleConverter();
    }
}
複製代碼
  • 配置管理器
@Configuration
public class SentinelRuleConfig {
    private static final String RULE_FLOW_KEY = "flow";
    private static final String RULE_DEGRADE_KEY = "degrade";
    private Map<String, String> rulesConfig = new HashMap<>();
    private CommonDataSource<FlowRule> flowDataSource;
    private CommonDataSource<DegradeRule> degradeDataSource;

    public SentinelRuleConfig() {
        super();
        init();
    }

    /** * 多個規則用;隔開,一個規則內部用,隔開 * 順序爲resourceName,grade,count * 如helloService,1,3;sayBye,0,5 * grade參考以下 * @see com.alibaba.csp.sentinel.slots.block.RuleConstant * @param rules */
    @Value("${rule.flow:helloService,1,3}")
    private void setFlowRules(String rules) {
        rulesConfig.put(RULE_FLOW_KEY, rules);
        flowDataSource.updateRules();
    }

    /** * 多個規則用;隔開,一個規則內部用,隔開 * 順序爲resourceName,grade,count,timeWindow * 如helloService,1,3,10;sayBye,0,5,10 * grade參考以下 * @see com.alibaba.csp.sentinel.slots.block.RuleConstant * @param rules */
    @Value("${rule.degrade:}")
    private void setDegradeRules(String rules) {
        rulesConfig.put(RULE_DEGRADE_KEY, rules);
        degradeDataSource.updateRules();
    }


    public void init() {
        flowDataSource = new CommonDataSource<FlowRule>(FlowRuleConverter.create(), RULE_FLOW_KEY) {
            @Override
            public String readSource() {
                return rulesConfig.get(RULE_FLOW_KEY);
            }
        };
        //將新的數據源配置注入到ruleManager
        FlowRuleManager.register2Property(flowDataSource.getProperty());

        degradeDataSource = new CommonDataSource<DegradeRule>(DegradeRuleConverter.create(), RULE_DEGRADE_KEY) {
            @Override
            public String readSource() {
                return rulesConfig.get(RULE_DEGRADE_KEY);
            }
        };
        DegradeRuleManager.register2Property(degradeDataSource.getProperty());
    }
}
複製代碼

apollo只需配置相應的rule.flow和rule.degrade的值便可

相關文章
相關標籤/搜索