光阴闹钟app开发者,请存眷我,后续分享更出色!

对峙原创,配合前进!

媒介

阿里开源Sentinel框架功用强大,实现了对办事的流控、熔断降级,并撑持通过办理页面停止设置装备摆设和监控。Client集成撑持Java、Go等语言。但办理后台Dashboard,默认只合适开发和演示,要消费情况利用需要做响应源代码级革新。本文将介绍详细革新步调,帮忙各人更快实如今消费情况的摆设应用。当然,若是预算充沛,也能够间接利用阿里云开箱即用的Sentinel Dashboard办事。

整体架构Sentinel熔断降级/流控生产级改造  第1张

Sentinel Dashboard规则默认存储在内存中,一旦办事重启,规则将丧失。要实现消费情况的大规模应用,需要把规则做耐久化存储,Sentinel撑持把nacos/zookeePEr/apollo/redis等中间件做为存储介量。本文以Nacos做为规则存储端停止介绍。

整体架构如上图,Sentinel Dashboard通过界面操做,添加资本的规则,保留时将信息推送到nacos中。nacos收到数据变动,并实时推送到应用中的Sentinel SDK客户端,Sentinel SDK应用规则实现对办事资本的流控/熔断/降级管控。

代码革新

Sentinel 利用nacos做为存储端,需要修改源代码。

1.源代码下载

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

IDE东西翻开sentinel-dashboard项目模块

留意:将项目切换到利用的sentinel tag不变版本,那里为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> 流控规则</a></li>

4.Java代码修改

com.alibaba.csp.sentinel.dashboard.rule包中创建一个nacos包,用来存放Nacos相关代码类。

nacos包下创建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包下创建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包创建NacosConfiguration bean设置装备摆设类

@EnableConfigurationProperties(NacosPropertiesConfiguration.class)@Configurationpublic 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包下创建流控的provider和publisher实现类

@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包下创建熔断/降级的provider和publisher实现类

@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标识由下图右边替代右边的值。将原有的办事引用基于内存存储,替代为nacos存储的办事引用。

# 上面截图红框替代值flowRuleDefaultProvider 替代为 flowRuleNacosProviderflowRuleDefaultPublisher 替代为 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办事地址sentinel.nacos.serverAddr=127.0.0.1:8848#nacos定名空间sentinel.nacos.namespacec=#sentinel.nacos.username=#sentinel.nacos.password=应用端集成

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 注解用于定义资本,实现资本的流控/熔断降级处置。

留意:注解体例埋点不撑持 private 办法。

@Service@Slf4jpublic 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设置,值为函数办法名 * 2. blockHandler 函数拜候范畴需如果 public,返回类型需要与原办法相婚配,参数类型需要和原办法相婚配而且最初加一个额外的参数,类型为 BlockException。 * 3. blockHandler 函数默认需要和原办法在统一个类中。若希望利用其他类的函数,则能够指定 blockHandlerClass 为对应的类的 Class 对象,留意对应的函数必须为 static 函数,不然无法解析。 * @param name * @param blockException * @return */ public String sayHelloBlockHandler(String name, BlockException blockException){ log.warn("已开启流控",blockException); return "流控后的返回值"; } /** * 被降级后处置办法 * 留意!!!:若是blockHandler和fallback都设置装备摆设,熔断降级规则生效,触发熔断,在熔断时长定义时间内,只要blockHandler生效 * 1. @SentinelResource 注解fallback设置,值为函数办法名 * 2. 用于在抛出异常的时候供给 fallback 处置逻辑。fallback 函数能够针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)停止处置 * 3. 返回值类型必需与原函数返回值类型一致; * 4. 办法参数列表需要和原函数一致,或者能够额外多一个 Throwable 类型的参数用于领受对应的异常。 * 5. allback 函数默认需要和原办法在统一个类中。若希望利用其他类的函数,则能够指定 fallbackClass 为对应的类的 Class 对象,留意对应的函数必须为 static 函数,不然无法解析。 * @param name * @param throwable * @return */ public String sayHelloFallback(String name, Throwable throwable){ log.warn("已降级",throwable); return "降级后的返回值"; }}

流控和降级处置逻辑定义详见代码正文。

@SentinelResource还包罗其他可能利用的属性:

exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不管帐入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。exceptionsToTrace:用于指定处置的异常。默认为Throwable.class。可设置自定义需要处置的其他异常类。

3.添加设置装备摆设

application.yml文件添加以下设置装备摆设

spring: application: name: demo-sentinel cloud: sentinel: #阿里sentinel设置装备摆设项 transport: # spring.cloud.sentinel.transport.port 端口设置装备摆设会在应用对应的机器上启动一个 Http Server, # 该 Server 会与 Sentinel 控造台做交互。好比 Sentinel 控造台添加了一个限流规则,会把规则数据 push 给那个 Http Server 领受, # Http Server 再将规则注册到 Sentinel 中。# port: 8719 dashboard: localhost:8080 datasource: ds1: # 数据源标识key,能够随意定名,包管独一即可 nacos: #nacos 数据源-流控规则设置装备摆设 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 数据源-熔断降级规则设置装备摆设 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。

应用端集成Sentinel SDK,拜候添加了降级逻辑处置的接口地址。

拜候sentinel-dashboard web页面: http://localhost:8080/ 。在界面,添加响应流控和熔断规则。

Nacos办理界面查看:http://localhost:8848/nacos 。 能够看到主动生成了两个设置装备摆设界面,别离对应流控和熔断设置装备摆设

点击规则详情,查看规则信息

规则json对象信息如下:

[ { "app": "***-sentinel", "clusterConfig": { "acquireRefuseStrategy": 0, "clientOfflineTime": 2000, "fallbackToLocalWhenFail": true, "resourceTimeout": 2000, "resourceTimeoutStrategy": 0, "sampleCount": 10, "strategy": 0, "thresholdType": 0, "windowIntervalMs": 1000 }, "clusterMode": false, "controlBehavior": 0, "count": 4000, "gmtCreate": 1677065845653, "gmtModified": 1677065845653, "grade": 1, "id": 1, "ip": "192.168.56.1", "limitApp": "default", "port": 8720, "resource": "sayHello2", "strategy": 0 }, { "app": "***-sentinel", "clusterConfig": { "acquireRefuseStrategy": 0, "clientOfflineTime": 2000, "fallbackToLocalWhenFail": true, "resourceTimeout": 2000, "resourceTimeoutStrategy": 0, "sampleCount": 10, "strategy": 0, "thresholdType": 0, "windowIntervalMs": 1000 }, "clusterMode": false, "controlBehavior": 0, "count": 1000, "gmtCreate": 1677057866855, "gmtModified": 1677065997762, "grade": 1, "id": 3, "ip": "192.168.56.1", "limitApp": "default", "port": 8720, "resource": "sayHello", "strategy": 0 }, { "app": "***-sentinel", "clusterConfig": { "acquireRefuseStrategy": 0, "clientOfflineTime": 2000, "fallbackToLocalWhenFail": true, "resourceTimeout": 2000, "resourceTimeoutStrategy": 0, "sampleCount": 10, "strategy": 0, "thresholdType": 0, "windowIntervalMs": 1000 }, "clusterMode": false, "controlBehavior": 0, "count": 2, "gmtCreate": 1677117433499, "gmtModified": 1677117433499, "grade": 1, "id": 6, "ip": "192.168.56.1", "limitApp": "default", "port": 8720, "resource": "/ping", "strategy": 0 }]