内容简介:文章包含了springcloud alibaba 接入1.6.3版本的sentinel,sentinel-dashboard与nacos的规则推拉(控制台规则保存至nacos,client应用启动读取nacos规则配置)。仅包含以上内容。
sentinel简介:
- sentinel是阿里开源的一款用来统计限制熔断降级的中间件。
- 如果想统计接口调用失败率,高并发下限流,以及熔断等可以使用
sentinel的使用(通用版简单使用):
PS:如果不知道自己当前是哪个版本的sentinel,在pom里面引入相应以来后查看依赖包版本号
- sentinel使用需要在client端(应用端)的pom.xml加入配置
<!-- 告诉cloud alibaba ,这项目要用sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel</artifactId> </dependency> <!-- 告诉cloud alibaba ,这项目要用nacos 来持久化sentinel 配置的规则 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- 告诉cloud alibaba ,这项目要与sentinel dashboard 通信 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> </dependency>
- 下载sentinel-dashboard 1.6.3源码 (alibaba/Sentinel at release-1.6 (github.com))
- 启动sentinel-dashboard修改配置,将其注册到自己的nacos
spring.cloud.nacos.config.server-addr=localhost:8848 spring.cloud.nacos.config.namespace=02fa0369-65ed-42b6-97a9-747c29d19bf5
- client端的bootstrap或application添加以下配置
spring.cloud.sentinel.transport.port=8721 spring.cloud.sentinel.transport.dashboard=localhost:8080
- 启动应用(即可)
sentinel进阶:(只针对sentinel 1.6.3的版本)完成后属于半成品
- 修改sentinel-dashboard的规则持久化到nacos,拉取sentinel-dashbaord 1.6.3的代码
- 在com/alibaba/csp/sentinel/dashboard/rule路径下新建nacos,再在其下建一个flow
- 在com/alibaba/csp/sentinel/dashboard/rule/nacos/flow创建类 FlowRuleNacosProvider
@Component("flowRuleNacosProvider") public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>>{ @Autowired private ConfigService configService; @Autowired private NacosConfig<FlowRuleEntity> nacosConfig; @Override public List<FlowRuleEntity> getRules (String appName) throws Exception{ //这里的一长串的名字(nacos配置文件名)需要与你client的配置文件里需要读取的规则名字一致 // 我这里拼接出来是 ${spring.application.name}-sentinel-flow String rules = configService.getConfig(appName + NacosConsantTxt.SENTINEL_POSTFIX + RuleTypeEnum.FLOW.getPostfix(), NacosConsantTxt.GROUP_ID, 3000); if (StringUtil.isEmpty(rules)){ return new ArrayList<>(); } //这个方法是从nacos拉去规则json转成规则对象的 return nacosConfig.getRuleEntityDecoder(FlowRuleEntity.class).convert(rules); } }
- 在com/alibaba/csp/sentinel/dashboard/rule/nacos/flow创建类 FlowRuleNacosPublisher
@Component("flowRuleNacosPublisher") public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>>{ @Autowired private ConfigService configService; @Autowired private NacosConfig<FlowRuleEntity> nacosConfig; @Override public void publish (String app, List<FlowRuleEntity> rules) throws Exception{ AssertUtil.notEmpty(app, NacosConsantTxt.APP_NAME_NULL_ERROR); if (rules == null){ return; } //这里的一长串名字(nacos配置文件名)规则需要与前面的规则保持一致,否则会无法发布到对应的nacos配置文件中 configService.publishConfig(app + NacosConsantTxt.SENTINEL_POSTFIX + RuleTypeEnum.FLOW.getPostfix(), NacosConsantTxt.GROUP_ID, nacosConfig.ruleEntityEncoder().convert(rules)); } }
-
在com/alibaba/csp/sentinel/dashboard/rule/nacos创建类 NacosConfig
@Configuration public class NacosConfig<T>{ @Value("${spring.cloud.nacos.config.server-addr}") private String serverAddr; @Value("${spring.cloud.nacos.config.namespace}") private String namespace; /** * @return 规则编码器 */ @Bean public Converter<List<T>, String> ruleEntityEncoder() { return JSON::toJSONString; } /** * @return 规则解码器 */ public Converter<String, List<T>> getRuleEntityDecoder (Class<T> clz){ return s -> JSON.parseArray(s, clz); } @Bean public ConfigService nacosConfigService() throws Exception { Properties properties = new Properties(); String address = "localhost"; if(StringUtil.isNotEmpty(serverAddr)){ address = serverAddr.split(":")[0]; } properties.put(PropertyKeyConst.SERVER_ADDR, address); properties.put(PropertyKeyConst.NAMESPACE, namespace); return ConfigFactory.createConfigService(properties); } }
- 修改com/alibaba/csp/sentinel/dashboard/controller/v2下的FlowControllerV2,将ruleProvider与rulePublisher 修改成我们上面添加的两个类
@Autowired @Qualifier("flowRuleNacosProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired @Qualifier("flowRuleNacosPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
- 修改sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
//修改前 <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li> // || // \/ //修改成 <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>
- 修改sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html将被注释的代码释放出来
<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> 新增流控规则</button> <a class="btn btn-outline-success" style="float: right; margin-right: 10px;" ui-sref="dashboard.flow({app: app})"> 回到集群页面</a> </div> </div>
- 这里基本完成了流控规则sentinel-dashboard与nacos的推拉。
- 至于其他热点规则,系统规则,降级规则等,参考限流规则写,记得注入到对应controller的pubilsher和provider即可。单机的限流规则controller中需要将发布方法改成以下根据applyAll和ip过滤本机规则
private boolean publishRules(String app, String ip, Integer port) { List<FlowRuleEntity> rulesToPublish = new ArrayList<>(); try{ List<FlowRuleEntity> rules = ruleProvider.getRules(app); for (FlowRuleEntity rule : rules){ if(rule.getApplyAll()){ rulesToPublish.add(rule); continue; } if(rule.getIp().equals(ip)){ rulesToPublish.add(rule); } } rulePublisher.publish(app,rules); } catch (Exception exception){ logger.error("规则发布到Ncaos出现异常,原因:【{}】",exception.getMessage(),exception); exception.printStackTrace(); } return sentinelApiClient.setFlowRuleOfMachine(app, ip, port, rulesToPublish); }
这里只有流控规则有单机与集群之分,流控的集群配置才会持久化到nacos中,在单机页针对单台机器的配置只会在应用的内存中,重启失效 :所以还需要改造 FlowControllerV1 将这里的消息 ruleProvider与rulePublisher修改成上面添加的 。但是这依然会带出其他的问题,就是1.6.3这个版本的sneitnel加载规则的时候,是不会区分单机规则和集群规则的,只要在对应的配置文件里出现了就会进行加载。所以需要进行更高级版的改造。
注:client配置文件示例
//规则配置的dataId名需要与nacos,sentinel-dashboard源码的configService写的一致,才可
spring.cloud.sentinel.transport.port=8720
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.datasource.ds.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds.nacos.namespace=${spring.cloud.nacos.config.namespace}
spring.cloud.sentinel.datasource.ds.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds.nacos.dataId=${spring.application.name}-sentinel-flow
spring.cloud.sentinel.datasource.ds.nacos.rule-type=flow
spring.cloud.sentinel.datasource.ds.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds1.nacos.namespace=${spring.cloud.nacos.config.namespace}
spring.cloud.sentinel.datasource.ds1.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.dataId=${spring.application.name}-sentinel-degrade
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=degrade
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds2.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds2.nacos.namespace=${spring.cloud.nacos.config.namespace}
spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.dataId=${spring.application.name}-sentinel-params
spring.cloud.sentinel.datasource.ds2.nacos.rule-type=param_flow
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
spring.cloud.sentinel.datasource.ds3.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds3.nacos.namespace=${spring.cloud.nacos.config.namespace}
spring.cloud.sentinel.datasource.ds3.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds3.nacos.dataId=${spring.application.name}-sentinel-system
spring.cloud.sentinel.datasource.ds3.nacos.rule-type=system
spring.cloud.sentinel.datasource.ds3.nacos.data-type=json
spring.cloud.sentinel.datasource.ds4.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds4.nacos.namespace=${spring.cloud.nacos.config.namespace}
spring.cloud.sentinel.datasource.ds4.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds4.nacos.dataId=${spring.application.name}-sentinel-authority
spring.cloud.sentinel.datasource.ds4.nacos.rule-type=authority
spring.cloud.sentinel.datasource.ds4.nacos.data-type=json
sentinel-core的修改:(完整版,并解决以上问题)
针对以上问题:单机添加的规则会应用到该服务下所有的机器。进行如下修改,使得单机上的一种服务A只会加载该ip的服务,例如:A服务(A1,A2)部署在ip1(A1)和ip2(A2)下,当针对A1添加了R1规则后,规则会保存到nacos配置中,当应用重启是,只有在ip1下的A服务才会加载R1规则.集群的规则则不分ip,会对A服务下的所有ip生效。这里总结下来是单机规则针对服务名下的ip来限制规则,集群是针对服务名的。注:这里不根据ip+端口来区分的原因是客户端的sentinel通信端口是有可能被占用的,一旦被占用端口号会沿用下一个端口。所以不采用端口号,作为区分标志。
- 修改sentinel-dashboard 中的 FlowRuleEntity 新增属性
private boolean applyAll; public boolean getApplyAll (){ return applyAll; } public void setApplyAll (boolean applyAll){ this.applyAll = applyAll; }
在 FlowControllerV2 的 apiAddFlowRule 与 apiUpdateFlowRule 方法中将此属性设置成true,FlowControllerV1的apiAddFlowRule 与 apiUpdateFlowRule 方法中将此属性设置成false
-
拉取sentinel-core 1.6.3
找到com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java修改成以下即可。
package com.alibaba.csp.sentinel.property; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.util.StringUtil; public class DynamicSentinelProperty<T> implements SentinelProperty<T> { protected Set<PropertyListener<T>> listeners = Collections.synchronizedSet(new HashSet<PropertyListener<T>>()); private T value = null; public DynamicSentinelProperty() { } public DynamicSentinelProperty(T value) { super(); this.value = value; } @Override public void addListener(PropertyListener<T> listener) { listeners.add(listener); listener.configLoad(value); } @Override public void removeListener(PropertyListener<T> listener) { listeners.remove(listener); } @Override public boolean updateValue(T newValue) { if (isEqual(value, newValue)) { return false; } //根据ip进行规则匹配,如果applyAll为true的不管ip,直接加载,否则判断当前机器ip是否与规则里面的ip一致,一致方可进行加载 newValue = getFlowRulesWithIp(newValue); RecordLog.info("[DynamicSentinelProperty] Config will be updated to: " + newValue); value = newValue; for (PropertyListener<T> listener : listeners) { listener.configUpdate(newValue); } return true; } private T getFlowRulesWithIp (T newValue){ if(null == newValue){ return null; } String ipAddress = getIpAddress(); List rules = (List) newValue; List<FlowRule> flowRules = new ArrayList<>(); for (Object rule : rules){ if(rule instanceof FlowRule){ FlowRule flowRule = (FlowRule)rule; if(flowRule.isApplyAll()){ flowRules.add(flowRule); continue; } if(StringUtil.isEmpty(flowRule.getIp()) || flowRule.getIp().equals(ipAddress)){ flowRules.add(flowRule); } } } if(!flowRules.isEmpty()){ newValue = (T)flowRules; } return newValue; } private boolean isEqual(T oldValue, T newValue) { if (oldValue == null && newValue == null) { return true; } if (oldValue == null) { return false; } return oldValue.equals(newValue); } public void close() { listeners.clear(); } public String getIpAddress(){ try{ Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); InetAddress ip = null; while (allNetInterfaces.hasMoreElements()){ NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()){ Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()){ ip = addresses.nextElement(); if (ip instanceof Inet4Address){ return ip.getHostAddress(); } } } } } catch (Exception e){ RecordLog.info("[DynamicSentinelProperty] Fail to get ip address: " + e.getMessage()); throw new RuntimeException("获取本地ip地址失败"); } throw new RuntimeException("获取本地ip地址失败"); } }
如果有打包完成后启动起不起来的话,提示方法类缺失,可能是sentinel-core 1.6.3源码的问题 见:为什么1.6.3的sentinel-core少了一个接口 · Issue #2315 · alibaba/Sentinel (github.com)
这里提供一下改好的dashboard和core:ZhuBobi/sentinel-1.6.3 (github.com)