SpringcloudAlibaba 接入sentinel 1.6.3 nacos规则持久化

内容简介:文章包含了springcloud alibaba 接入1.6.3版本的sentinel,sentinel-dashboard与nacos的规则推拉(控制台规则保存至nacos,client应用启动读取nacos规则配置)。仅包含以上内容。

sentinel简介:

  1. sentinel是阿里开源的一款用来统计限制熔断降级的中间件。
  2. 如果想统计接口调用失败率,高并发下限流,以及熔断等可以使用

sentinel的使用(通用版简单使用):

    PS:如果不知道自己当前是哪个版本的sentinel,在pom里面引入相应以来后查看依赖包版本号

  1. 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>
  2. 下载sentinel-dashboard 1.6.3源码 (alibaba/Sentinel at release-1.6 (github.com)
  3. 启动sentinel-dashboard修改配置,将其注册到自己的nacos
    spring.cloud.nacos.config.server-addr=localhost:8848
    spring.cloud.nacos.config.namespace=02fa0369-65ed-42b6-97a9-747c29d19bf5
  4. client端的bootstrap或application添加以下配置
    spring.cloud.sentinel.transport.port=8721
    spring.cloud.sentinel.transport.dashboard=localhost:8080
  5. 启动应用(即可)

sentinel进阶:(只针对sentinel 1.6.3的版本完成后属于半成品

  1. 修改sentinel-dashboard的规则持久化到nacos,拉取sentinel-dashbaord 1.6.3的代码
  2. 在com/alibaba/csp/sentinel/dashboard/rule路径下新建nacos,再在其下建一个flow
  3. 在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);
        }
    }
    
  4.  在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));
        }
    }
      
  5. 在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);
        }
    }

  6.  修改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;
     
  7. 修改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>

  8. 修改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>&nbsp;&nbsp;新增流控规则</button>
        <a class="btn btn-outline-success" style="float: right; margin-right: 10px;" ui-sref="dashboard.flow({app: app})">
          回到集群页面</a>
      </div>
    </div>
     
  9. 这里基本完成了流控规则sentinel-dashboard与nacos的推拉。
  10. 至于其他热点规则,系统规则,降级规则等,参考限流规则写,记得注入到对应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)

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值