天天看點

sentinel使用ZooKeeper配置動态降級規則

版本

1.8.1

後端

1、建立zk rule配置

修改ZookeeperConfigUtil.java

com/alibaba/csp/sentinel/dashboard/rule/zookeeper/ZookeeperConfigUtil.java

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper;


import org.apache.commons.lang.StringUtils;

public class ZookeeperConfigUtil {
    public static final String RULE_ROOT_PATH = "/sentinel_rule_config";
    public static final String DEGRADE_ROOT_PATH = "/sentinel_degrade_config";

    public static final int RETRY_TIMES = 3;
    public static final int SLEEP_TIME = 1000;

    //流控規則使用 --把之前的流控規則中的getPath改成這個
    public static String getFlowPath(String appName) {
        return getPath(RULE_ROOT_PATH,appName);
    }
    //降級規則使用
    public static String getDegradePath(String appName) {
        return getPath(DEGRADE_ROOT_PATH,appName);
    }


    private static String getPath(String root,String appName) {
        StringBuilder stringBuilder = new StringBuilder(root);

        if (StringUtils.isBlank(appName)) {
            return stringBuilder.toString();
        }
        if (appName.startsWith("/")) {
            stringBuilder.append(appName);
        } else {
            stringBuilder.append("/")
                    .append(appName);
        }
        return stringBuilder.toString();
    }


}
           

建立DegradeRuleZookeeperProvider.java

com/alibaba/csp/sentinel/dashboard/rule/zookeeper/degrade/DegradeRuleZookeeperProvider.java

package com.alibaba.csp.sentinel.dashboard.rule.zookeeper.degrade;


import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.zookeeper.ZookeeperConfigUtil;
import com.alibaba.csp.sentinel.datasource.Converter;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

@Component("degradeRuleZookeeperProvider")
public class DegradeRuleZookeeperProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
    @Autowired
    private CuratorFramework zkClient;
    @Autowired
    private Converter<String, List<DegradeRuleEntity>> converter;
    @Override
    public List<DegradeRuleEntity> getRules(String appName) throws Exception {
        String degradePath = ZookeeperConfigUtil.getDegradePath(appName);
        Stat stat = zkClient.checkExists().forPath(degradePath);
        if (stat==null) {
            return Collections.emptyList();
        }
        byte[] bytes = zkClient.getData().forPath(degradePath);
        if (null == bytes || bytes.length == 0) {
            return Collections.emptyList();
        }
        String s = new String(bytes);
        return converter.convert(s);
    }
}
           

建立DegradeRuleZookeeperPublisher.java

com/alibaba/csp/sentinel/dashboard/rule/zookeeper/degrade/DegradeRuleZookeeperPublisher.java

@Component("degradeRuleZookeeperPublisher")
public class DegradeRuleZookeeperPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
    @Autowired
    private CuratorFramework zkClient;
    @Autowired
    private Converter<List<DegradeRuleEntity>, String> converter;
    @Override
    public void publish(String app, List<DegradeRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        String degradePath = ZookeeperConfigUtil.getDegradePath(app);
        Stat stat = zkClient.checkExists().forPath(degradePath);
        if (stat ==null) {
            zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(degradePath,null);
        }
        byte[] data = CollectionUtils.isEmpty(rules) ? "[]".getBytes() : converter.convert(rules).getBytes();
        zkClient.setData().forPath(degradePath,data);
    }
}

           

2、建立降級規則的Controller

com/alibaba/csp/sentinel/dashboard/controller/v2/DegradeControllerV2.java

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.controller.v2;

import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;

/**
 * Controller regarding APIs of degrade rules. Refactored since 1.8.0.
 *
 * @author Carpenter Lee
 * @author Eric Zhao
 */
@RestController
@RequestMapping("/v2/degrade")
public class DegradeControllerV2 {

    private final Logger logger = LoggerFactory.getLogger(DegradeControllerV2.class);

    @Autowired
    private RuleRepository<DegradeRuleEntity, Long> repository;
    @Autowired
    @Qualifier("degradeRuleZookeeperProvider")
    private DynamicRuleProvider<List<DegradeRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("degradeRuleZookeeperPublisher")
    private DynamicRulePublisher<List<DegradeRuleEntity>> rulePublisher;
    @Autowired
    private SentinelApiClient sentinelApiClient;

    @GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<DegradeRuleEntity>> apiQueryMachineRules(String app) {
        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        try {
            List<DegradeRuleEntity> rules = ruleProvider.getRules(app);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("queryApps error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    @PostMapping("/rule")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<DegradeRuleEntity> apiAddRule(@RequestBody DegradeRuleEntity entity) {
        Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            publishRules(entity.getApp());
        } catch (Throwable t) {
            logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t);
            return Result.ofThrowable(-1, t);
        }
        return Result.ofSuccess(entity);
    }

    @PutMapping("/rule/{id}")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<DegradeRuleEntity> apiUpdateRule(@PathVariable("id") Long id,
                                                     @RequestBody DegradeRuleEntity entity) {
        if (id == null || id <= 0) {
            return Result.ofFail(-1, "id can't be null or negative");
        }
        DegradeRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofFail(-1, "Degrade rule does not exist, id=" + id);
        }
        entity.setApp(oldEntity.getApp());
        entity.setIp(oldEntity.getIp());
        entity.setPort(oldEntity.getPort());
        entity.setId(oldEntity.getId());
        Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }

        entity.setGmtCreate(oldEntity.getGmtCreate());
        entity.setGmtModified(new Date());
        try {
            entity = repository.save(entity);
            publishRules(oldEntity.getApp());
        } catch (Throwable t) {
            logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t);
            return Result.ofThrowable(-1, t);
        }
        return Result.ofSuccess(entity);
    }

    @DeleteMapping("/rule/{id}")
    @AuthAction(PrivilegeType.DELETE_RULE)
    public Result<Long> delete(@PathVariable("id") Long id) {
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        DegradeRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
            publishRules(oldEntity.getApp());
        } catch (Throwable throwable) {
            logger.error("Failed to delete degrade rule, id={}", id, throwable);
            return Result.ofThrowable(-1, throwable);
        }
        return Result.ofSuccess(id);
    }

    private void publishRules(String app) throws Exception {
        List<DegradeRuleEntity> rules = repository.findAllByApp(app);
        rulePublisher.publish(app,rules);
    }

    private <R> Result<R> checkEntityInternal(DegradeRuleEntity entity) {
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be blank");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (entity.getPort() == null || entity.getPort() <= 0) {
            return Result.ofFail(-1, "invalid port: " + entity.getPort());
        }
        if (StringUtil.isBlank(entity.getLimitApp())) {
            return Result.ofFail(-1, "limitApp can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1, "resource can't be null or empty");
        }
        Double threshold = entity.getCount();
        if (threshold == null || threshold < 0) {
            return Result.ofFail(-1, "invalid threshold: " + threshold);
        }
        Integer recoveryTimeoutSec = entity.getTimeWindow();
        if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) {
            return Result.ofFail(-1, "recoveryTimeout should be positive");
        }
        Integer strategy = entity.getGrade();
        if (strategy == null) {
            return Result.ofFail(-1, "circuit breaker strategy cannot be null");
        }
        if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()
            || strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
            return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy);
        }
        if (entity.getMinRequestAmount()  == null || entity.getMinRequestAmount() <= 0) {
            return Result.ofFail(-1, "Invalid minRequestAmount");
        }
        if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) {
            return Result.ofFail(-1, "Invalid statInterval");
        }
        if (strategy == RuleConstant.DEGRADE_GRADE_RT) {
            Double slowRatio = entity.getSlowRatioThreshold();
            if (slowRatio == null) {
                return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy");
            } else if (slowRatio < 0 || slowRatio > 1) {
                return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]");
            }
        } else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
            if (threshold > 1) {
                return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]");
            }
        }
        return null;
    }
}

           

前端

1、建立api Service檔案

resources/app/scripts/services/degrade_service_v2.js

var app = angular.module('sentinelDashboardApp');

app.service('DegradeServiceV2', ['$http', function ($http) {
  this.queryMachineRules = function (app, ip, port) {
    var param = {
      app: app,
      ip: ip,
      port: port
    };
    return $http({
      url: 'v2/degrade/rules',
      params: param,
      method: 'GET'
    });
  };

  this.newRule = function (rule) {
    return $http({
        url: 'v2/degrade/rule',
        data: rule,
        method: 'POST'
    });
  };

  this.saveRule = function (rule) {
    var param = {
      id: rule.id,
      resource: rule.resource,
      limitApp: rule.limitApp,
      grade: rule.grade,
      count: rule.count,
      timeWindow: rule.timeWindow,
        statIntervalMs: rule.statIntervalMs,
        minRequestAmount: rule.minRequestAmount,
        slowRatioThreshold: rule.slowRatioThreshold,
    };
    return $http({
        url: 'v2/degrade/rule/' + rule.id,
        data: param,
        method: 'PUT'
    });
  };

  this.deleteRule = function (rule) {
      return $http({
          url: 'v2/degrade/rule/' + rule.id,
          method: 'DELETE'
      });
  };

  this.checkRuleValid = function (rule) {
      if (rule.resource === undefined || rule.resource === '') {
          alert('資源名稱不能為空');
          return false;
      }
      if (rule.grade === undefined || rule.grade < 0) {
          alert('未知的降級政策');
          return false;
      }
      if (rule.count === undefined || rule.count === '' || rule.count < 0) {
          alert('降級門檻值不能為空或小于 0');
          return false;
      }
      if (rule.timeWindow == undefined || rule.timeWindow === '' || rule.timeWindow <= 0) {
          alert('熔斷時長必須大于 0s');
          return false;
      }
      if (rule.minRequestAmount == undefined || rule.minRequestAmount <= 0) {
          alert('最小請求數目需大于 0');
          return false;
      }
      if (rule.statIntervalMs == undefined || rule.statIntervalMs <= 0) {
          alert('統計視窗時長需大于 0s');
          return false;
      }
      if (rule.statIntervalMs !== undefined && rule.statIntervalMs > 60 * 1000 * 2) {
          alert('統計視窗時長不能超過 120 分鐘');
          return false;
      }
      // 異常比率類型.
      if (rule.grade == 1 && rule.count > 1) {
          alert('異常比率超出範圍:[0.0 - 1.0]');
          return false;
      }
      if (rule.grade == 0) {
          if (rule.slowRatioThreshold == undefined) {
              alert('慢調用比率不能為空');
              return false;
          }
          if (rule.slowRatioThreshold < 0 || rule.slowRatioThreshold > 1) {
              alert('慢調用比率超出範圍:[0.0 - 1.0]');
              return false;
          }
      }
      return true;
  };
}]);

           

2、 建立api Controller檔案

src/main/webapp/resources/app/scripts/controllers/degrade_v2.js

var app = angular.module('sentinelDashboardApp');

app.controller('DegradeCtlV2', ['$scope', '$stateParams', 'DegradeServiceV2', 'ngDialog', 'MachineService',
  function ($scope, $stateParams, DegradeService, ngDialog, MachineService) {
    //初始化
    $scope.app = $stateParams.app;
    $scope.rulesPageConfig = {
      pageSize: 10,
      currentPageIndex: 1,
      totalPage: 1,
      totalCount: 0,
    };
    $scope.macsInputConfig = {
      searchField: ['text', 'value'],
      persist: true,
      create: false,
      maxItems: 1,
      render: {
        item: function (data, escape) {
          return '<div>' + escape(data.text) + '</div>';
        }
      },
      onChange: function (value, oldValue) {
        $scope.macInputModel = value;
      }
    };
    getMachineRules();
    function getMachineRules() {
      if (!$scope.macInputModel) {
        return;
      }
      var mac = $scope.macInputModel.split(':');
      DegradeService.queryMachineRules($scope.app, mac[0], mac[1]).success(
        function (data) {
          if (data.code == 0 && data.data) {
            $scope.rules = data.data;
            $scope.rulesPageConfig.totalCount = $scope.rules.length;
          } else {
            $scope.rules = [];
            $scope.rulesPageConfig.totalCount = 0;
          }
        });
    };
    $scope.getMachineRules = getMachineRules;

    var degradeRuleDialog;
    $scope.editRule = function (rule) {
      $scope.currentRule = angular.copy(rule);
      $scope.degradeRuleDialog = {
        title: '編輯降級規則',
        type: 'edit',
        confirmBtnText: '儲存'
      };
      degradeRuleDialog = ngDialog.open({
        template: '/app/views/dialog/degrade-rule-dialog.html',
        width: 680,
        overlay: true,
        scope: $scope
      });
    };

    $scope.addNewRule = function () {
      var mac = $scope.macInputModel.split(':');
      $scope.currentRule = {
        grade: 0,
        app: $scope.app,
        ip: mac[0],
        port: mac[1],
        limitApp: 'default',
        minRequestAmount: 5,
        statIntervalMs: 1000,
      };
      $scope.degradeRuleDialog = {
        title: '新增降級規則',
        type: 'add',
        confirmBtnText: '新增'
      };
      degradeRuleDialog = ngDialog.open({
        template: '/app/views/dialog/degrade-rule-dialog.html',
        width: 680,
        overlay: true,
        scope: $scope
      });
    };

    $scope.saveRule = function () {
      if (!DegradeService.checkRuleValid($scope.currentRule)) {
        return;
      }
      if ($scope.degradeRuleDialog.type === 'add') {
        addNewRule($scope.currentRule);
      } else if ($scope.degradeRuleDialog.type === 'edit') {
        saveRule($scope.currentRule, true);
      }
    };

    function parseDegradeMode(grade) {
        switch (grade) {
            case 0:
              return '慢調用比例';
            case 1:
              return '異常比例';
            case 2:
              return '異常數';
            default:
              return '未知';
        }
    }

    var confirmDialog;
    $scope.deleteRule = function (rule) {
      $scope.currentRule = rule;
      $scope.confirmDialog = {
        title: '删除降級規則',
        type: 'delete_rule',
        attentionTitle: '請确認是否删除如下降級規則',
        attention: '資源名: ' + rule.resource +
            ', 降級模式: ' + parseDegradeMode(rule.grade) + ', 門檻值: ' + rule.count,
        confirmBtnText: '删除',
      };
      confirmDialog = ngDialog.open({
        template: '/app/views/dialog/confirm-dialog.html',
        scope: $scope,
        overlay: true
      });
    };

    $scope.confirm = function () {
      if ($scope.confirmDialog.type == 'delete_rule') {
        deleteRule($scope.currentRule);
      } else {
        console.error('error');
      }
    };

    function deleteRule(rule) {
      DegradeService.deleteRule(rule).success(function (data) {
        if (data.code == 0) {
          getMachineRules();
          confirmDialog.close();
        } else {
          alert('失敗:' + data.msg);
        }
      });
    };

    function addNewRule(rule) {
      DegradeService.newRule(rule).success(function (data) {
        if (data.code == 0) {
          getMachineRules();
          degradeRuleDialog.close();
        } else {
          alert('失敗:' + data.msg);
        }
      });
    };

    function saveRule(rule, edit) {
      DegradeService.saveRule(rule).success(function (data) {
        if (data.code == 0) {
          getMachineRules();
          if (edit) {
            degradeRuleDialog.close();
          } else {
            confirmDialog.close();
          }
        } else {
          alert('失敗:' + data.msg);
        }
      });
    }
    queryAppMachines();
    function queryAppMachines() {
      MachineService.getAppMachines($scope.app).success(
        function (data) {
          if (data.code == 0) {
            // $scope.machines = data.data;
            if (data.data) {
              $scope.machines = [];
              $scope.macsInputOptions = [];
              data.data.forEach(function (item) {
                if (item.healthy) {
                  $scope.macsInputOptions.push({
                    text: item.ip + ':' + item.port,
                    value: item.ip + ':' + item.port
                  });
                }
              });
            }
            if ($scope.macsInputOptions.length > 0) {
              $scope.macInputModel = $scope.macsInputOptions[0].value;
            }
          } else {
            $scope.macsInputOptions = [];
          }
        }
      );
    };
    $scope.$watch('macInputModel', function () {
      if ($scope.macInputModel) {
        getMachineRules();
      }
    });
  }]);

           

3、修改src/main/webapp/resources/app/scripts/app.js

加入下面代碼

.state('dashboard.degradeV2', {
                    templateUrl: 'app/views/degrade_v2.html',
                    url: '/v2/degrade/:app',
                    controller: 'DegradeCtlV2',
                    resolve: {
                        loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
                            return $ocLazyLoad.load({
                                name: 'sentinelDashboardApp',
                                files: [
                                    'app/scripts/controllers/degrade_v2.js',
                                ]
                            });
                        }]
                    }
                })

           

4、修改src/main/webapp/resources/dist/js/app.js

在.state("dashboard.degrade"後面加入

.state("dashboard.degradeV2", {
        templateUrl: "app/views/degrade_v2.html",
        url: "/v2/degrade/:app",
        controller: "DegradeCtlV2",
        resolve: {
            loadMyFiles: ["$ocLazyLoad", function (e) {
                return e.load({name: "sentinelDashboardApp", files: ["app/scripts/controllers/degrade_v2.js"]})
            }]
        }
    })


           

在 (app = angular.module(“sentinelDashboardApp”)).service(“DegradeService”,後面加入

, (app = angular.module("sentinelDashboardApp")).service("DegradeServiceV2", ["$http", function (a) {
    this.queryMachineRules = function (e, t, r) {
        return a({url: "/V2/degrade/rules", params: {app: e, ip: t, port: r}, method: "GET"})
    }, this.newRule = function (e) {
        return a({url: "/V2/degrade/rule", data: e, method: "POST"})
    }, this.saveRule = function (e) {
        var t = {
            id: e.id,
            resource: e.resource,
            limitApp: e.limitApp,
            grade: e.grade,
            count: e.count,
            timeWindow: e.timeWindow,
            statIntervalMs: e.statIntervalMs,
            minRequestAmount: e.minRequestAmount,
            slowRatioThreshold: e.slowRatioThreshold
        };
        return a({url: "/V2/degrade/rule/" + e.id, data: t, method: "PUT"})
    }, this.deleteRule = function (e) {
        return a({url: "/V2/degrade/rule/" + e.id, method: "DELETE"})
    }, this.checkRuleValid = function (e) {
        if (void 0 === e.resource || "" === e.resource) return alert("資源名稱不能為空"), !1;
        if (void 0 === e.grade || e.grade < 0) return alert("未知的降級政策"), !1;
        if (void 0 === e.count || "" === e.count || e.count < 0) return alert("降級門檻值不能為空或小于 0"), !1;
        if (null == e.timeWindow || "" === e.timeWindow || e.timeWindow <= 0) return alert("熔斷時長必須大于 0s"), !1;
        if (null == e.minRequestAmount || e.minRequestAmount <= 0) return alert("最小請求數目需大于 0"), !1;
        if (null == e.statIntervalMs || e.statIntervalMs <= 0) return alert("統計視窗時長需大于 0s"), !1;
        if (void 0 !== e.statIntervalMs && 12e4 < e.statIntervalMs) return alert("統計視窗時長不能超過 120 分鐘"), !1;
        if (1 == e.grade && 1 < e.count) return alert("異常比率超出範圍:[0.0 - 1.0]"), !1;
        if (0 == e.grade) {
            if (null == e.slowRatioThreshold) return alert("慢調用比率不能為空"), !1;
            if (e.slowRatioThreshold < 0 || 1 < e.slowRatioThreshold) return alert("慢調用比率超出範圍:[0.0 - 1.0]"), !1
        }
        return !0
    }
}])
           

4、建立降級頁面

resources/app/views/degrade_v2.html

<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
  <div class="col-md-6" style="margin-bottom: 10px;">
    <span style="font-size: 30px;font-weight: bold;">{{app}}</span>
  </div>
  <div class="col-md-6">
    <button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
      <i class="fa fa-plus"></i>&nbsp;&nbsp;新增降級規則</button>
    <a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.degrade({app: app})">
      回到單機頁面
    </a>
  </div>
</div>

<div class="separator"></div>

<div class="container-fluid">
  <div class="row" style="margin-top: 20px; margin-bottom: 20px;">
    <div class="col-md-12">
      <div class="card">
        <div class="inputs-header">
          <span class="brand" style="font-size: 13px;">降級規則</span>
          <!--<button class="btn btn-danger" style="float: right;margin-right: 10px;height: 30px;font-size: 12px;" ng-click="disableAll()">全部禁用</button>-->
          <button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getMachineRules()">重新整理</button>
          <input class="form-control witdh-200" placeholder="關鍵字" ng-model="searchKey">
          <<!--          <div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">-->
<!--            <selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"-->
<!--              placeholder="機器"></selectize>-->
<!--          </div>-->
        </div>

        <!--.tools-header -->
        <div class="card-body" style="padding: 0px 0px;">
          <table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
            <thead>
              <tr style="background: #F3F5F7;">
                <td style="width: 40%">
                  資源名
                </td>
                <td style="width: 10%;">
                  降級政策
                </td>
                <td style="width: 10%;">
                  門檻值
                </td>
                <td style="width: 10%;">
                  熔斷時長(s)
                </td>
                <td style="width: 12%;">
                  操作
                </td>
              </tr>
            </thead>
            <tbody>
              <tr dir-paginate="rule in rules | filter : searchKey | itemsPerPage: rulesPageConfig.pageSize " current-page="rulesPageConfig.currentPageIndex"
                pagination-id="entriesPagination">
                <td style="word-wrap:break-word;word-break:break-all;">{{rule.resource}}</td>
                <!--<td style="word-wrap:break-word;word-break:break-all;">{{rule.limitApp }}</td>-->
                <td>
                  <span ng-if="rule.grade == 0">慢調用比例</span>
                  <span ng-if="rule.grade == 1" title="異常比例">異常比例</span>
                  <span ng-if="rule.grade == 2" title="異常數">異常數</span>
                </td>
                <td style="word-wrap:break-word;word-break:break-all;">
                  {{rule.count}}
                </td>
                <td style="word-wrap:break-word;word-break:break-all;">
                  {{rule.timeWindow}}s
                </td>

                <td>
                  <button class="btn btn-xs btn-default" type="button" ng-click="editRule(rule)" style="font-size: 12px; height:25px;">編輯</button>
                  <button class="btn btn-xs btn-default" type="button" ng-click="deleteRule(rule)" style="font-size: 12px; height:25px;">删除</button>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        <!-- .card-body -->
        <div class="pagination-footer">
          <dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
            on-page-change="">
          </dir-pagination-controls>
          <div class="tools" style="">
            <span>共 {{rulesPageConfig.totalCount}} 條記錄, </span>
            <span>
              每頁
              <input class="form-control" ng-model="rulesPageConfig.pageSize"> 條記錄,
            </span>
            <span>第 {{rulesPageConfig.currentPageIndex}} / {{rulesPageConfig.totalPage}} 頁</span>
          </div>
          <!-- .tools -->
        </div>
        <!-- pagination-footer -->
      </div>
      <!-- .card -->
    </div>
    <!-- .col-md-12 -->
  </div>
  <!-- -->
</div>
<!-- .container-fluid -->

           

5、修改sidebar.html

可以把原有的降級注掉,然後加入下面代碼

<li ui-sref-active="active">
            <a ui-sref="dashboard.degradeV2({app: entry.app})">
              <i class="glyphicon glyphicon-flash"></i>&nbsp;&nbsp;降級規則V2</a>
          </li>
           

項目重新開機之後清除一下浏覽器緩存,保證使用到的是最新的

用戶端配置

見文章流控配置配置的用戶端配置 與之類似

繼續閱讀