整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          Sentinel熔斷降級/流控生產(chǎn)級改造

          光鬧鐘app開發(fā)者,請關(guān)注我,后續(xù)分享更精彩!

          堅持原創(chuàng),共同進(jìn)步!

          前言

          阿里開源Sentinel框架功能強大,實現(xiàn)了對服務(wù)的流控、熔斷降級,并支持通過管理頁面進(jìn)行配置和監(jiān)控。Client集成支持Java、Go等語言。但管理后臺Dashboard,默認(rèn)只適合開發(fā)和演示,要生產(chǎn)環(huán)境使用需要做相應(yīng)源代碼級改造。本文將介紹具體改造步驟,幫助大家更快實現(xiàn)在生產(chǎn)環(huán)境的部署應(yīng)用。當(dāng)然,如果預(yù)算充足,也可以直接使用阿里云開箱即用的Sentinel Dashboard服務(wù)。

          整體架構(gòu)

          Sentinel Dashboard規(guī)則默認(rèn)存儲在內(nèi)存中,一旦服務(wù)重啟,規(guī)則將丟失。要實現(xiàn)生產(chǎn)環(huán)境的大規(guī)模應(yīng)用,需要把規(guī)則做持久化存儲,Sentinel支持把nacos/zookeeper/apollo/redis等中間件作為存儲介質(zhì)。本文以Nacos作為規(guī)則存儲端進(jìn)行介紹。

          整體架構(gòu)如上圖,Sentinel Dashboard通過界面操作,添加資源的規(guī)則,保存時將信息推送到nacos中。nacos收到數(shù)據(jù)變更,并實時推送到應(yīng)用中的Sentinel SDK客戶端,Sentinel SDK應(yīng)用規(guī)則實現(xiàn)對服務(wù)資源的流控/熔斷/降級管控。

          代碼改造

          Sentinel 使用nacos作為存儲端,需要修改源代碼。

          1.源代碼下載

          git clone git@github.com:hcq0514/Sentinel.git

          IDE工具打開sentinel-dashboard項目模塊

          注意:將項目切換到使用的sentinel tag穩(wěn)定版本,這里為1.8.6版本

          2.pom.xml添加依賴

          sentinel-dashboard模塊pom.xml文件中,把sentinel-datasource-nacos依賴的注釋掉。

          <!-- for Nacos rule publisher sample -->
          <dependency>
              <groupId>com.alibaba.csp</groupId>
              <artifactId>sentinel-datasource-nacos</artifactId>
              <!--<scope>test</scope>-->
          </dependency>

          3.前端頁面修改

          resources/app/scripts/directives/sidebar/sidebar.html文件,將dashboard.flowV1改成dashboard.flow

          <li ui-sref-active="active" ng-if="!entry.isGateway">
            <!--<a ui-sref="dashboard.flowV1({app: entry.app})">-->
            <a ui-sref="dashboard.flow({app: entry.app})">
              <i class="glyphicon glyphicon-filter"></i>  流控規(guī)則</a>
          </li>

          4.Java代碼修改

          com.alibaba.csp.sentinel.dashboard.rule包中創(chuàng)建一個nacos包,用來存放Nacos相關(guān)代碼類。

          nacos包下創(chuàng)建NacosPropertiesConfiguration類,用于nacos server配置屬性封裝

          package com.alibaba.csp.sentinel.dashboard.rule.nacos;
          
          import org.springframework.boot.context.properties.ConfigurationProperties;
          
          @ConfigurationProperties(prefix = "sentinel.nacos")
          public class NacosPropertiesConfiguration {
              private String namespace;
              private String serverAddr;
              private String username;
              private String password;
              //省略 屬性 set/get 方法......
          }

          nacos包下創(chuàng)建NacosConfigUtil工具類

          public final class NacosConfigUtil {
          
              public static final String GROUP_ID = "SENTINEL_GROUP";
              
              public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";
              public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";
              public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules";
              public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";
          
              /**
               * cc for `cluster-client`
               */
              public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config";
              /**
               * cs for `cluster-server`
               */
              public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config";
              public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config";
              public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set";
          
              private NacosConfigUtil() {}
          }

          nacos包創(chuàng)建NacosConfiguration bean配置類

          @EnableConfigurationProperties(NacosPropertiesConfiguration.class)
          @Configuration
          public class NacosConfiguration {
          
              @Bean
              @Qualifier("degradeRuleEntityEncoder")
              public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
                  return JSON::toJSONString;
              }
          
              @Bean
              @Qualifier("degradeRuleEntityDecoder")
              public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {
                  return s -> JSON.parseArray(s, DegradeRuleEntity.class);
              }
          
              @Bean
              @Qualifier("flowRuleEntityEncoder")
              public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
                  return JSON::toJSONString;
              }
          
              @Bean
              @Qualifier("flowRuleEntityDecoder")
              public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
                  return s -> JSON.parseArray(s, FlowRuleEntity.class);
              }
          
              @Bean
              public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
                  Properties properties = new Properties();
                  properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
          
                  if(StringUtils.isNotBlank(nacosPropertiesConfiguration.getNamespace())){
                      properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
                  }
          
                  if(StringUtils.isNotBlank(nacosPropertiesConfiguration.getUsername())){
                      properties.put(PropertyKeyConst.USERNAME, nacosPropertiesConfiguration.getUsername());
                  }
                  if(StringUtils.isNotBlank(nacosPropertiesConfiguration.getPassword())){
                      properties.put(PropertyKeyConst.PASSWORD, nacosPropertiesConfiguration.getPassword());
                  }
                  return ConfigFactory.createConfigService(properties);
              }
          }

          nacos包下創(chuàng)建流控的provider和publisher實現(xiàn)類

          @Component("flowRuleNacosProvider")
          public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
          
              @Autowired
              private ConfigService configService;
              @Autowired
              @Qualifier("flowRuleEntityDecoder")
              private Converter<String, List<FlowRuleEntity>> converter;
          
              @Override
              public List<FlowRuleEntity> getRules(String appName) throws Exception {
                  String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
                      NacosConfigUtil.GROUP_ID, 3000);
                  if (StringUtil.isEmpty(rules)) {
                      return new ArrayList<>();
                  }
                  return converter.convert(rules);
              }
          }
          @Component("flowRuleNacosPublisher")
          public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
          
              @Autowired
              private ConfigService configService;
              @Autowired
              @Qualifier("flowRuleEntityEncoder")
              private Converter<List<FlowRuleEntity>, String> converter;
          
              @Override
              public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
                  AssertUtil.notEmpty(app, "app name cannot be empty");
                  if (rules == null) {
                      return;
                  }
                  configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
                      NacosConfigUtil.GROUP_ID, converter.convert(rules));
              }
          }

          nacos包下創(chuàng)建熔斷/降級的provider和publisher實現(xiàn)類

          @Component("degradeRuleNacosProvider")
          public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
          
              @Autowired
              private ConfigService configService;
              @Autowired
              @Qualifier("degradeRuleEntityDecoder")
              private Converter<String, List<DegradeRuleEntity>> converter;
          
              @Override
              public List<DegradeRuleEntity> getRules(String appName) throws Exception {
                  String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
                      NacosConfigUtil.GROUP_ID, 3000);
                  if (StringUtil.isEmpty(rules)) {
                      return new ArrayList<>();
                  }
                  return converter.convert(rules);
              }
          }
          @Component("degradeRuleNacosPublisher")
          public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
          
              @Autowired
              private ConfigService configService;
              @Autowired
              @Qualifier("degradeRuleEntityEncoder")
              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");
                  if (rules == null) {
                      return;
                  }
                  configService.publishConfig(app + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
                      NacosConfigUtil.GROUP_ID, converter.convert(rules));
              }
          }

          修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2類。將注入的bean標(biāo)識由下圖右邊替換左邊的值。將原有的服務(wù)引用基于內(nèi)存存儲,替換為nacos存儲的服務(wù)引用。

          # 上面截圖紅框替換值
          flowRuleDefaultProvider 替換為 flowRuleNacosProvider
          flowRuleDefaultPublisher 替換為 flowRuleNacosPublisher

          刪除com.alibaba.csp.sentinel.dashboard.controller.DegradeController類, com.alibaba.csp.sentinel.dashboard.controller.v2包下新增DegradeControllerV2類

          @RestController
          @RequestMapping("/degrade")
          public class DegradeControllerV2 {
          
              private final Logger logger = LoggerFactory.getLogger(DegradeControllerV2.class);
          
              @Autowired
              private RuleRepository<DegradeRuleEntity, Long> repository;
          
              @Autowired
              @Qualifier("degradeRuleNacosProvider")
              private DynamicRuleProvider<List<DegradeRuleEntity>> ruleProvider;
              @Autowired
              @Qualifier("degradeRuleNacosPublisher")
              private DynamicRulePublisher<List<DegradeRuleEntity>> rulePublisher;
          
              @GetMapping("/rules.json")
              @AuthAction(PrivilegeType.READ_RULE)
              public Result<List<DegradeRuleEntity>> apiQueryMachineRules(String app, String ip, Integer port) {
                  if (StringUtil.isEmpty(app)) {
                      return Result.ofFail(-1, "app can't be null or empty");
                  }
                  if (StringUtil.isEmpty(ip)) {
                      return Result.ofFail(-1, "ip can't be null or empty");
                  }
                  if (port == null) {
                      return Result.ofFail(-1, "port can't be null");
                  }
                  try {
          //            List<DegradeRuleEntity> rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);
                      List<DegradeRuleEntity> rules = ruleProvider.getRules(app);
                      if (rules != null && !rules.isEmpty()) {
                          for (DegradeRuleEntity entity : rules) {
                              entity.setApp(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);
                  } catch (Throwable t) {
                      logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t);
                      return Result.ofThrowable(-1, t);
                  }
                  if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
                      logger.warn("Publish degrade rules failed, app={}", entity.getApp());
                  }
                  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);
                  } catch (Throwable t) {
                      logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t);
                      return Result.ofThrowable(-1, t);
                  }
                  if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
                      logger.warn("Publish degrade rules failed, app={}", entity.getApp());
                  }
                  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);
                  } catch (Throwable throwable) {
                      logger.error("Failed to delete degrade rule, id={}", id, throwable);
                      return Result.ofThrowable(-1, throwable);
                  }
                  if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
                      logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp());
                  }
                  return Result.ofSuccess(id);
              }
          
              private boolean publishRules(String app, String ip, Integer port) {
                  /*List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
                  return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);*/
                  List<DegradeRuleEntity> rules = repository.findAllByApp(app);
                  try {
                      rulePublisher.publish(app, rules);
                  } catch (Exception e) {
                      logger.error("Failed to publish nacos, app={}", app);
                      logger.error("error is : ",e);
                  }
                  return true;
              }
          
              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;
              }
          }

          5.maven打包

          重新打包sentinel-dashboard模塊

          mvn clean package

          6.配置文件修改

          application.properties添加nacos配置項

          #nacos服務(wù)地址
          sentinel.nacos.serverAddr=127.0.0.1:8848
          #nacos命名空間
          sentinel.nacos.namespacec=
          #sentinel.nacos.username=
          #sentinel.nacos.password=

          應(yīng)用端集成

          1.引入依賴

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

          如果是maven多模塊工程,父pom中添加alibaba的父pom依賴

          <dependencyManagement>
              <dependencies>
                  <!-- https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E   -->
                  <dependency>
                      <groupId>com.alibaba.cloud</groupId>
                      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                      <version>2.2.6.RELEASE</version>
                      <type>pom</type>
                      <scope>import</scope>
                  </dependency>
              </dependencies>
          </dependencyManagement>    

          2.注解支持

          Sentinel 提供了 @SentinelResource 注解用于定義資源,實現(xiàn)資源的流控/熔斷降級處理。

          注意:注解方式埋點不支持 private 方法。

          @Service
          @Slf4j
          public class TestService {
              /**
               * 定義sayHello資源的方法,流控/熔斷降級處理邏輯
               * @param name
               * @return
               */
              @SentinelResource(value = "sayHello",blockHandler = "sayHelloBlockHandler",fallback = "sayHelloFallback")
              public String sayHello(String name) {
                  if("fallback".equalsIgnoreCase(name)){
                      throw new RuntimeException("fallback");
                  }
                  return "Hello, " + name;
              }
          
              /**
               * 被流控后處理方法。
               * 1. @SentinelResource 注解blockHandler設(shè)置,值為函數(shù)方法名
               * 2. blockHandler 函數(shù)訪問范圍需要是 public,返回類型需要與原方法相匹配,參數(shù)類型需要和原方法相匹配并且最后加一個額外的參數(shù),類型為 BlockException。
               * 3. blockHandler 函數(shù)默認(rèn)需要和原方法在同一個類中。若希望使用其他類的函數(shù),則可以指定 blockHandlerClass 為對應(yīng)的類的 Class 對象,注意對應(yīng)的函數(shù)必需為 static 函數(shù),否則無法解析。
               * @param name
               * @param blockException
               * @return
               */
              public String sayHelloBlockHandler(String name, BlockException blockException){
                  log.warn("已開啟流控",blockException);
                  return "流控后的返回值";
              }
          
              /**
               * 被降級后處理方法
               * 注意!!!:如果blockHandler和fallback都配置,熔斷降級規(guī)則生效,觸發(fā)熔斷,在熔斷時長定義時間內(nèi),只有blockHandler生效
               * 1. @SentinelResource 注解fallback設(shè)置,值為函數(shù)方法名
               * 2. 用于在拋出異常的時候提供 fallback 處理邏輯。fallback 函數(shù)可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進(jìn)行處理
               * 3. 返回值類型必須與原函數(shù)返回值類型一致;
               * 4. 方法參數(shù)列表需要和原函數(shù)一致,或者可以額外多一個 Throwable 類型的參數(shù)用于接收對應(yīng)的異常。
               * 5. allback 函數(shù)默認(rèn)需要和原方法在同一個類中。若希望使用其他類的函數(shù),則可以指定 fallbackClass 為對應(yīng)的類的 Class 對象,注意對應(yīng)的函數(shù)必需為 static 函數(shù),否則無法解析。
               * @param name
               * @param throwable
               * @return
               */
              public String sayHelloFallback(String name, Throwable throwable){
                  log.warn("已降級",throwable);
                  return "降級后的返回值";
              }
          }

          流控和降級處理邏輯定義詳見代碼注釋。

          @SentinelResource還包含其他可能使用的屬性:

          • exceptionsToIgnore(since 1.6.0):用于指定哪些異常被排除掉,不會計入異常統(tǒng)計中,也不會進(jìn)入 fallback 邏輯中,而是會原樣拋出。
          • exceptionsToTrace:用于指定處理的異常。默認(rèn)為Throwable.class。可設(shè)置自定義需要處理的其他異常類。

          3.添加配置

          application.yml文件添加以下配置

          spring:
            application:
              name: demo-sentinel
            cloud:
              sentinel: #阿里sentinel配置項
                transport:
                  # spring.cloud.sentinel.transport.port 端口配置會在應(yīng)用對應(yīng)的機器上啟動一個 Http Server,
                  # 該 Server 會與 Sentinel 控制臺做交互。比如 Sentinel 控制臺添加了一個限流規(guī)則,會把規(guī)則數(shù)據(jù) push 給這個 Http Server 接收,
                  # Http Server 再將規(guī)則注冊到 Sentinel 中。
          #        port: 8719
                  dashboard: localhost:8080
                datasource:
                  ds1: # 數(shù)據(jù)源標(biāo)識key,可以隨意命名,保證唯一即可
                    nacos: #nacos 數(shù)據(jù)源-流控規(guī)則配置
                      server-addr: localhost:8848
          #            username: nacos
          #            password: nacos
                      data-id: ${spring.application.name}-flow-rules
                      group-id: SENTINEL_GROUP
                      data-type: json
                      #flow、degrade、param-flow
                      rule-type: flow
                  ds2:
                    nacos: #nacos 數(shù)據(jù)源-熔斷降級規(guī)則配置
                      server-addr: localhost:8848
          #            username: nacos
          #            password: nacos
                      data-id: ${spring.application.name}-degrade-rules
                      group-id: SENTINEL_GROUP
                      data-type: json
                      #flow、degrade、param-flow
                      rule-type: degrade

          驗證

          啟動sentinel-dashboard。

          應(yīng)用端集成Sentinel SDK,訪問添加了降級邏輯處理的接口地址。

          訪問sentinel-dashboard web頁面: http://localhost:8080/ 。在界面,添加相應(yīng)流控和熔斷規(guī)則。

          Nacos管理界面查看:http://localhost:8848/nacos 。 可以看到自動生成了兩個配置界面,分別對應(yīng)流控和熔斷配置

          點擊規(guī)則詳情,查看規(guī)則信息

          規(guī)則json對象信息如下:

          entinel Dashboard(控制臺)默認(rèn)情況下,只能將配置規(guī)則保存到內(nèi)存中,這樣就會導(dǎo)致 Sentinel Dashboard 重啟后配置規(guī)則丟失的情況,因此我們需要將規(guī)則保存到某種數(shù)據(jù)源中,Sentinel 支持的數(shù)據(jù)源有以下這些:

          然而,默認(rèn)情況下,Sentinel 和數(shù)據(jù)源之間的關(guān)系是單向數(shù)據(jù)通訊的,也就是只能先在數(shù)據(jù)源中配置規(guī)則,然后數(shù)據(jù)源會被規(guī)則推送至 Sentinel Dashboard 和 Sentinel 客戶端,但是在 Sentinel Dashboard 中修改規(guī)則或新增規(guī)則是不能反向同步到數(shù)據(jù)源中的,這就是單向通訊。


          所以,今天我們就該修改一下 Sentinel 的源碼,讓其可以同步規(guī)則至數(shù)據(jù)源,改造之后的交互流程如下圖所示:

          Sentinel 同步規(guī)則至數(shù)據(jù)源,例如將 Sentinel 的規(guī)則,同步規(guī)則至 Nacos 數(shù)據(jù)源的改造步驟很多,但整體實現(xiàn)難度不大,下面我們一起來看吧。

          1.下載Sentinel源碼

          下載地址:https://github.com/alibaba/Sentinel

          PS:本文 Sentinel 使用的版本是 1.8.6。

          下載源碼之后,使用 idea 打開里面的 sentinel-dashboard 項目,如下圖所示:

          2.修改pom.xml

          將 sentinel-datasource-nacos 底下的 scope 注釋掉,如下圖所示:

          PS:因為官方提供的 Nacos 持久化實例,是在 test 目錄下進(jìn)行單元測試的,而我們是用于生產(chǎn)環(huán)境,所以需要將 scope 中的 test 去掉。

          3.移動單元測試代碼

          將 test/com.alibaba.csp.sentinel.dashboard.rule.nacos 下所有文件復(fù)制到 src/main/java/com.alibaba.csp.sentinel.dashboard.rule 目錄下,如下圖所示:

          4.新建NacosPropertiesConfiguration文件

          在 com.alibaba.csp.sentinel.dashboard.rule 下創(chuàng)建 Nacos 配置文件的讀取類,實現(xiàn)代碼如下:

          package com.alibaba.csp.sentinel.dashboard.rule;
          
          import org.springframework.boot.context.properties.ConfigurationProperties;
          import org.springframework.context.annotation.Configuration;
          
          @ConfigurationProperties(prefix = "sentinel.nacos")
          @Configuration
          public class NacosPropertiesConfiguration {
              private String serverAddr;
              private String dataId;
              private String groupId;
              private String namespace;
              private String username;
              private String password;
              // 省略 Getter/Setter 代碼
          }

          5.修改NacosConfig文件

          只修改 NacosConfig 中的 nacosConfigService 方法,修改后的代碼如下:

          @Bean
          public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws Exception {
              Properties properties = new Properties();
              properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
              properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
              properties.put(PropertyKeyConst.USERNAME,nacosPropertiesConfiguration.getUsername());
              properties.put(PropertyKeyConst.PASSWORD,nacosPropertiesConfiguration.getPassword());
              return ConfigFactory.createConfigService(properties);
          //        return ConfigFactory.createConfigService("localhost"); // 原代碼
          }

          6.修改FlowControllerV2文件

          修改 com.alibaba.csp.sentinel.dashboard.controller.v2 目錄下的 FlowControllerV2 文件:

          修改后代碼:

          @Autowired
          @Qualifier("flowRuleNacosProvider")
          private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
          @Autowired
          @Qualifier("flowRuleNacosPublisher")
          private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

          PS:此操作的目的是開啟 Controller 層操作 Nacos 的開關(guān)。

          如下圖所示:

          7.修改配置信息

          在 application.properties 中設(shè)置 Nacos 連接信息,配置如下:

          sentinel.nacos.serverAddr=localhost:8848
          sentinel.nacos.username=nacos
          sentinel.nacos.password=nacos
          sentinel.nacos.namespace=
          sentinel.nacos.groupId=DEFAULT_GROUP
          sentinel.nacos.dataId=sentinel-dashboard-demo-sentinel

          8.修改sidebar.html

          修改 webapp/resources/app/scripts/directives/sidebar/sidebar.html 文件:

          搜索“dashboard.flowV1”改為“dashboard.flow”,如下圖所示:

          9.修改identity.js

          identity.js 文件有兩處修改,它位于 webapp/resources/app/scripts/controllers/identity.js 目錄。

          9.1 第一處修改

          將“FlowServiceV1”修改為“FlowServiceV2”,如下圖所示:

          9.2 第二處修改

          搜索“/dashboard/flow/”修改為“/dashboard/v2/flow/”,如下圖所示:

          PS:修改 identity.js 文件主要是用于在 Sentinel 點擊資源的“流控”按鈕添加規(guī)則后將信息同步給 Nacos。

          小結(jié)

          Sentinel Dashboard 默認(rèn)情況下,只能將配置規(guī)則保存到內(nèi)存中,這樣就會程序重啟后配置規(guī)則丟失的情況,因此我們需要給 Sentinel 設(shè)置一個數(shù)據(jù)源,并且要和數(shù)據(jù)源之間實現(xiàn)雙向通訊,所以我們需要修改 Sentinel 的源碼。源碼的改造步驟雖然很多,但只要逐一核對和修改就可以實現(xiàn) Sentinel 生成環(huán)境的配置了。看完記得收藏哦,防止以后用的時候找不到。

          本文已收錄到我的面試小站 www.javacn.site,其中包含的內(nèi)容有:Redis、JVM、并發(fā)、并發(fā)、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設(shè)計模式、消息隊列等模塊。

          好,這里是codetrend專欄“SpringCloud2023實戰(zhàn)”。

          本文簡單介紹SpringCloud2023中使用Sentinel進(jìn)行限流管理。

          前言

          隨著微服務(wù)的流行,服務(wù)和服務(wù)之間的穩(wěn)定性變得越來越重要。

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

          Sentinel 同時提供系統(tǒng)維度的自適應(yīng)保護(hù)能力。

          防止雪崩,是系統(tǒng)防護(hù)中重要的一環(huán)。當(dāng)系統(tǒng)負(fù)載較高的時候,如果還持續(xù)讓請求進(jìn)入,可能會導(dǎo)致系統(tǒng)崩潰,無法響應(yīng)。在集群環(huán)境下,網(wǎng)絡(luò)負(fù)載均衡會把本應(yīng)這臺機器承載的流量轉(zhuǎn)發(fā)到其它的機器上去。

          如果這個時候其它的機器也處在一個邊緣狀態(tài)的時候,這個增加的流量就會導(dǎo)致這臺機器也崩潰,最后導(dǎo)致整個集群不可用。

          Spring Cloud Alibaba 默認(rèn)為 Sentinel 整合了 Servlet、RestTemplate、FeignClient 和 Spring WebFlux。

          Sentinel 在 Spring Cloud 生態(tài)中,不僅補全了 Hystrix 在 Servlet 和 RestTemplate 這一塊的空白,而且還完全兼容了 Hystrix 在 FeignClient 中限流降級的用法,并且支持運行時靈活地配置和調(diào)整限流降級規(guī)則。

          Sentinel工作機制

          Sentinel 的使用可以分為兩個部分:

          • 核心庫(Java 客戶端):不依賴任何框架/庫,能夠運行于 Java 8 及以上的版本的運行時環(huán)境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持(見 主流框架適配)。
          • 控制臺(Dashboard):Dashboard 主要負(fù)責(zé)管理推送規(guī)則、監(jiān)控、管理機器信息等。

          Sentinel 的主要工作機制如下:

          • 對主流框架提供適配或者顯示的 API,來定義需要保護(hù)的資源,并提供設(shè)施對資源進(jìn)行實時統(tǒng)計和調(diào)用鏈路分析。
          • 根據(jù)預(yù)設(shè)的規(guī)則,結(jié)合對資源的實時統(tǒng)計信息,對流量進(jìn)行控制。同時,Sentinel 提供開放的接口,方便您定義及改變規(guī)則。
          • Sentinel 提供實時的監(jiān)控系統(tǒng),方便您快速了解目前系統(tǒng)的狀態(tài)。

          Sentinel特點

          Sentinel 具有以下特征:

          • 豐富的應(yīng)用場景: Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發(fā)流量控制在系統(tǒng)容量可以承受的范圍)、消息削峰填谷、實時熔斷下游不可用應(yīng)用等。
          • 完備的實時監(jiān)控: Sentinel 同時提供實時的監(jiān)控功能。您可以在控制臺中看到接入應(yīng)用的單臺機器秒級數(shù)據(jù),甚至 500 臺以下規(guī)模的集群的匯總運行情況。
          • 廣泛的開源生態(tài): Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應(yīng)的依賴并進(jìn)行簡單的配置即可快速地接入 Sentinel。
          • 完善的 SPI 擴(kuò)展點: Sentinel 提供簡單易用、完善的 SPI 擴(kuò)展點。您可以通過實現(xiàn)擴(kuò)展點,快速的定制邏輯。例如定制規(guī)則管理、適配數(shù)據(jù)源等。

          Sentinel集成之控制臺(Dashboard)

          Sentinel 控制臺提供一個輕量級的控制臺,它提供機器發(fā)現(xiàn)、單機資源實時監(jiān)控、集群資源匯總,以及規(guī)則管理的功能。您只需要對應(yīng)用進(jìn)行簡單的配置,就可以使用這些功能。

          • 獲取控制臺。從 release 頁面 - https://github.com/alibaba/Sentinel/releases 下載最新版本的控制臺 jar 包。
          • 啟動控制臺。 Sentinel 控制臺是一個標(biāo)準(zhǔn)的 Spring Boot 應(yīng)用,以 Spring Boot 的方式運行 jar 包即可。
          java -Dserver.port=10300 -Dcsp.sentinel.dashboard.server=localhost:10300 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
          # 如若8080端口沖突,可使用 -Dserver.port=新端口 進(jìn)行設(shè)置。
          
          • 配置控制臺信息,application.yml 。
          spring:
            cloud:
              sentinel:
                transport:
                  port: 10201
                  dashboard: localhost:10300
          

          這里的 spring.cloud.sentinel.transport.port 端口配置會在應(yīng)用對應(yīng)的機器上啟動一個 Http Server,該 Server 會與 Sentinel 控制臺做交互。比如 Sentinel 控制臺添加了一個限流規(guī)則,會把規(guī)則數(shù)據(jù) push 給這個 Http Server 接收,Http Server 再將規(guī)則注冊到 Sentinel 中。

          Sentinel集成之普通服務(wù)

          引入pom.xml

          • 引入Sentinel主要是引入 spring-cloud-starter-alibaba-sentinel 。
          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          
              <dependencies>
                  <!-- sentinel核心包引入 -->
                  <dependency>
                      <groupId>com.alibaba.csp</groupId>
                      <artifactId>sentinel-core</artifactId>
                  </dependency>
                  <!-- sentinel-springboot引入 -->
                  <dependency>
                      <groupId>com.alibaba.cloud</groupId>
                      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                  </dependency>
              </dependencies>
          </project>
          

          修改配置

          • 新增配置文件 application.yml,配置主要是 spring.cloud.sentinel 下面的配置。
          spring.application.name: banana-client4-sentinel
          spring:
            cloud:
              sentinel:
                transport:
                  port: 10201
                  dashboard: localhost:10300
              zookeeper:
                connect-string: localhost:2181
          

          接口demo

          • 通過訪問 http://localhost:10200/client4/swagger-ui.html ,輸入賬號密碼 yulin/123yl. 就可以訪問到最新的接口文檔,驗證限流是否開啟成功。
          @Service
          public class TestService {
          
              @SentinelResource(value = "sayHello")
              public String sayHello(String name) {
                  return "Hello, " + name;
              }
          }
          
          @RestController
          public class TestController {
          
              @Autowired
              private TestService service;
          
              @GetMapping(value = "/hello/{name}")
              public String apiHello(@PathVariable String name) {
                  return service.sayHello(name);
              }
          }
          
          • 在控制面板設(shè)置 http://localhost:10200/client4/hello 的QPS為5。通過如下腳本驗證限流是否成功:
          import requests
          token_list =[]
          for j in range(10):
              token_list.append(str(j)+'hello')
              response = requests.get('http://localhost:10200/client4/hello/'+str(j)+'hello','hello')
              print(response.text)
          

          輸出文檔如下:

          Hello, 0hello
          Hello, 1hello
          Hello, 2hello
          Hello, 3hello
          Hello, 4hello
          Blocked by Sentinel (flow limiting)
          Blocked by Sentinel (flow limiting)
          Blocked by Sentinel (flow limiting)
          Blocked by Sentinel (flow limiting)
          Blocked by Sentinel (flow limiting)
          

          Sentinel集成之OpenFeign

          pom.xml和配置修改

          Feign 適配整合在 Spring Cloud Alibaba 中,可以參考 Spring Cloud Alibaba Sentinel 文檔 進(jìn)行接入。

          Sentinel 適配了 Feign 組件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依賴外還需要 2 個步驟:

          • 配置文件打開 Sentinel 對 Feign 的支持:feign.sentinel.enabled=true
          • 加入 spring-cloud-starter-openfeign 依賴使 Sentinel starter 中的自動化配置類生效:
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-openfeign</artifactId>
          </dependency>
          

          demo驗證代碼

          @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
          public interface EchoService {
              @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
              String echo(@PathVariable("str") String str);
          }
          
          class FeignConfiguration {
              @Bean
              public EchoServiceFallback echoServiceFallback() {
                  return new EchoServiceFallback();
              }
          }
          
          class EchoServiceFallback implements EchoService {
              @Override
              public String echo(@PathVariable("str") String str) {
                  return "echo fallback";
              }
          }
          
          • EchoService 接口中方法 echo 對應(yīng)的資源名為 GET:http://service-provider/echo/{str}。 通過對該資源限流配置即可。
          • 實際驗證失敗,可能是沒有開發(fā)到springcloud2023。

          Sentinel集成之Gateway

          若想跟 Sentinel Starter 配合使用,需要加上 spring-cloud-alibaba-sentinel-gateway 依賴,同時需要添加 spring-cloud-starter-gateway 依賴來讓 spring-cloud-alibaba-sentinel-gateway 模塊里的 Spring Cloud Gateway 自動化配置類生效:

          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
          </dependency>
          
          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
          </dependency>
          
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-gateway</artifactId>
          </dependency>
          

          同時請將 spring.cloud.sentinel.filter.enabled 配置項置為 false(若在網(wǎng)關(guān)流控控制臺上看到了 URL 資源,就是此配置項沒有置為 false)。Sentinel 網(wǎng)關(guān)流控默認(rèn)的粒度是 route 維度以及自定義 API 分組維度,默認(rèn)不支持 URL 粒度。

          文檔來自Sentinel文檔。這里不仔細(xì)展開開發(fā)和說明,后續(xù)在網(wǎng)關(guān)業(yè)務(wù)層進(jìn)行配置和說明。

          完整源碼信息查看可以在gitee或者github上搜索r0ad。

          關(guān)于作者

          來自一線全棧程序員nine的探索與實踐,持續(xù)迭代中。

          歡迎關(guān)注或者點贊~


          主站蜘蛛池模板: 亚洲无圣光一区二区| 中文字幕Av一区乱码| 国产婷婷一区二区三区| 末成年女AV片一区二区| 国产凸凹视频一区二区| 国产精品高清一区二区三区不卡| 精品视频一区在线观看| 怡红院一区二区在线观看| 亚洲一区在线观看视频| 国产成人无码一区二区在线观看| 国产一区二区三区福利| 亚洲欧美日韩一区二区三区在线| 人妻体内射精一区二区三区| 亚洲午夜福利AV一区二区无码| 免费人妻精品一区二区三区| 成人毛片无码一区二区| 在线精品视频一区二区| 极品人妻少妇一区二区三区| 亚洲福利一区二区三区| 自拍日韩亚洲一区在线| 国产一区在线mmai| 亚洲AV无码一区二区三区性色 | 国产精品久久久久一区二区三区| 亚洲av福利无码无一区二区| 一区二区精品在线| 麻豆果冻传媒2021精品传媒一区下载| 亚洲无线码在线一区观看| 亚洲av无码不卡一区二区三区| 国产亚洲3p无码一区二区| 一区二区在线视频| 3D动漫精品一区二区三区| 欧美日韩一区二区成人午夜电影| 偷拍激情视频一区二区三区| 亚洲丶国产丶欧美一区二区三区| 国内精品视频一区二区三区八戒| 国产在线无码视频一区| 色妞AV永久一区二区国产AV | 末成年女A∨片一区二区| 亚洲日韩国产一区二区三区在线| av一区二区三区人妻少妇| 一区二区三区视频观看|