查看原文
其他

[附源码]SpringBoot整合Sentinel限流及Sentinel-Dashboard持久化规则到Nacos

小萌橘子 码农闲谈AI 2024-01-22

当我们在项目中使用Sentinel进行指定接口的限流规则及熔断规则后,默认是放置在内存中的,当我们重启项目后,之前配置的限流熔断规则会被清空,导致限制规则失效.那么我们如何来持久化规则数据呢?通过官网文档我们可以看到,Sentinel datasource支持动态规则拓展,动态规则拓展的方式分别有:

拉模式: 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更.

推模式: 规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证.

这样显而易见,我们肯定是比较推荐使用推模式.那么支持推模式的规则持久化数据源支持哪些方式呢?

Pull-based: 动态文件数据源、Consul、Eureka

Push-based: Zookeeper、Redis、Nacos、Apollo、Etcd

在这篇内容当中呢就来讲一下如何使用nacos来进行规则数据的持久化.

文档地址:

https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html



SpringBoot项目配置

springboot版本为2.1.9.RELEASE, 引入:

spring-cloud-starter-alibaba-sentinel

sentinel=datasource-nacos

因为版本问题,exclusion掉sentinel-datasource-nacos包中的nacos-client包,改为自己引入.

<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>    <version>2.1.1.RELEASE</version></dependency><dependency>    <groupId>com.alibaba.csp</groupId>    <artifactId>sentinel-datasource-nacos</artifactId>    <exclusions>      <exclusion>        <artifactId>nacos-client</artifactId>        <groupId>com.alibaba.nacos</groupId>      </exclusion>    </exclusions></dependency><dependency>    <groupId>com.alibaba.nacos</groupId>    <artifactId>nacos-client</artifactId>    <version>2.1.2</version></dependency>

项目yml文件配置

server:  port: 8081  servlet:    context-path: /bizspring:  application:    name: test-biz  cloud:    nacos:      config:        server-addr: localhost:31844        file-extension: yml        timeout: 60000        namespace: 1d6f66a8-b9f2-401f-9af0-781a1fa4ddce    sentinel:      transport:        #上报信息端口        port: 8719        #sentinel dashboard地址及端口        dashboard: localhost:8090      datasource:        #限流配置        flow:          nacos:            server-addr: ${spring.cloud.nacos.config.server-addr}            namespace: ${spring.cloud.nacos.config.namespace}            data-id: ${spring.application.name}-flow-rules            group-id: SENTINEL_GROUP            rule-type: flow        #熔断配置        degrade:          nacos:            server-addr: ${spring.cloud.nacos.config.server-addr}            data-id: ${spring.application.name}-degrade-rules            namespace: ${spring.cloud.nacos.config.namespace}            group-id: SENTINEL_GROUP            rule-type: degrade

还可以自定义返回接口被限流后的提示信息

package com.common.sentinel.config;import cn.hutool.core.util.StrUtil;import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlBlockHandler;import com.alibaba.csp.sentinel.slots.block.BlockException;import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;import com.alibaba.csp.sentinel.slots.block.flow.FlowException;import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;import com.alibaba.csp.sentinel.slots.system.SystemBlockException;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.amazonaws.services.ecs.model.BlockedException;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Objects;@Component@Slf4jpublic class SentinelExceptionHandler extends DefaultUrlBlockHandler {    @Value("${spring.application.name}")    private String applicationName;    @Override    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {        String msg = "";        if (ex instanceof FlowException) {            msg = "请求量过多,请稍后重试";        }else if (ex instanceof DegradeException) {            msg = "请求量过多,服务被降级";        }if (ex instanceof ParamFlowException) {            msg = "请求量过多,参数限流";        }if (ex instanceof SystemBlockException) {            msg = "请求量过多,系统规则限流";        }if (ex instanceof AuthorityException) {            msg = "授权规则不通过";        }        response.setStatus(200);        response.setCharacterEncoding("utf-8");        response.setHeader("Content-Type", "application:charset=utf-8");        response.setContentType("application/json;charset=utf-8");        response.getWriter().write(JSON.toJSONString(R.ok(msg)));    }}

到这里呢我们的springBoot项目的整个限流配置就好了



Sentinel Dashboard更改

在这里使用的sentinel-dashboard版本为1.8.6,您也可以官网下载最新的版本.dashboard默认是,当启动springboot项目是会上报应用信息至dashboard,dashboard操作限流或者熔断规则时通过请求把规则数据发送到具体的项目里.那么我们如何把数据保存到nacos,再由nacos自动推送到项目里呢?此时我们可能要对sentinel-dashboard进行二次开发.

DashboardConfig类新增:

public static final String CONFIG_NACOS = "sentinel.config.nacos.host";public static final String CONFIG_NACOS_NAMESPACE = "sentinel.config.nacos.namespace";

public static String getConfigNacos() {        return getConfigStr(CONFIG_NACOS); }public static String getConfigNacosNamespace() {return getConfigStr(CONFIG_NACOS_NAMESPACE); }

com.alibaba.csp.sentinel.dashboard.rule下新增nacos包.

复制rule包下的FlowRuleApiProvider跟FlowRuleApiPublisher类到nacos包下.分别改名FlowRuleNacosProvider,FlowRuleNacosPublisher.

然后注释掉rule包下的两个类的@Component注解.

nacos包下新增NacosConfig及NacosConfigUtils类.

NacosConfig用于在dashboard中初始化nacos的连接及命名空间

package com.alibaba.csp.sentinel.dashboard.rule.nacos;import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;import com.alibaba.csp.sentinel.datasource.Converter;import com.alibaba.fastjson.JSON;import com.alibaba.nacos.api.config.ConfigFactory;import com.alibaba.nacos.api.config.ConfigService;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.List;import java.util.Properties;@Configurationpublic class NacosConfig {    @Bean    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {        return JSON::toJSONString;    }    @Bean    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {        return s -> JSON.parseArray(s, FlowRuleEntity.class);    }    @Bean    public ConfigService nacosConfigService() throws Exception {        Properties properties = new Properties();        properties.put("serverAddr", DashboardConfig.getConfigNacos());        properties.put("namespace", DashboardConfig.getConfigNacosNamespace());        return ConfigFactory.createConfigService(properties);    }}

NacosConfigUtil用于定义nacos相关data-id后缀跟group相关常量

package com.alibaba.csp.sentinel.dashboard.rule.nacos;public final class NacosConfigUtil {    public static final String GROUP_ID = "SENTINEL_GROUP";        public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";        private NacosConfigUtil() {}}

FlowRuleNacosProvider 类

package com.alibaba.csp.sentinel.dashboard.rule.nacos;import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;import com.alibaba.csp.sentinel.datasource.Converter;import com.alibaba.csp.sentinel.util.StringUtil;import com.alibaba.nacos.api.config.ConfigService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;@Component("flowRuleNacosProvider")public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {    @Autowired    private ConfigService configService;    @Autowired    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);    }}

FlowRuleNacosPublisher类

package com.alibaba.csp.sentinel.dashboard.rule.nacos;import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;import com.alibaba.csp.sentinel.datasource.Converter;import com.alibaba.csp.sentinel.util.AssertUtil;import com.alibaba.nacos.api.config.ConfigService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.List;@Component("flowRuleNacosPublisher")public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {    @Autowired    private ConfigService configService;    @Autowired    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));    }}

修改FlowControllerV2类

@Autowired@Qualifier("flowRuleDefaultProvider")private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;@Autowired@Qualifier("flowRuleDefaultPublisher")private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;改为


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

修改webapp/resources/app/scripts/directives/sidebar下sidebar.html文件,放开42行流控规则 v1菜单,原来为注释

<li ui-sref-active="active" ng-if="entry.appType==0"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 V1</a></li>注释51行流控规则,原来为放开的,并把下面的dashboard.flowV1改为dashboard.flow
<!--          <li ui-sref-active="active" ng-if="entry.isGateway">--><!--            <a ui-sref="dashboard.gatewayFlow({app: entry.app})">--><!--              <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</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>&nbsp;&nbsp;流控规则</a></li>

此时就差不多完成了.接下来我们在dashboard启动参数上新增



-Dserver.port=8090-Dcsp.sentinel.dashboard.server=localhost:8090#不配置,dashboar上不会有dashboar模块显示-Dproject.name=sentinel-dashboard-Dsentinel.dashboard.auth.username=admin-Dsentinel.dashboard.auth.password=admin-Dsentinel.config.nacos.host=127.0.0.1:31844-Dsentinel.config.nacos.namespace=1d6f66a8-b9f2-401f-9af0-781a1fa4ddce



启动项目及DashBoard

此时我们访问下biz的接口,刷新dashboard就会看到biz模块,sentinel-dashboar模块不配置name此处不会显示

此时我们给captcha接口新增一条流控规则后,此时我们在流控规则v1出就可以新增的规则了

然后我们在看看这条规则是否有存储在nacos里面呢?

这时我们就可以在nacos对应的命名空间下新增了一个flow-rules结尾的配置文件,点击编辑进去看到的就是我们配置的流控规则,然后我们请求下验证码接口看看是否生效了呢?

通过请求我们可以看到,我们设置的0线程生效了.然后您还可以试下在dashboar删除这条流控规则,看看流控是否解除了限制呢。



对ip及用户名限流

此时新增流控规则那的针对来源默认是default类型,也就是限制所有,通过在springboot项目中编写代码,我们还可以做到具体配置限制哪个ip,限制哪个用户,新增配置类如下:

package com.common.sentinel.config;
import cn.hutool.core.collection.CollectionUtil;import cn.hutool.core.util.StrUtil;import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;import com.alibaba.fastjson.JSONObject;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;import java.util.List;import java.util.Objects;

@Slf4j@Configurationpublic class SentinelConfig {

   @Value("${spring.application.name}")    private String applicationName;
   @Bean("accountRequestOriginParser")    public RequestOriginParser accountRequestOriginParser() {        return (httpServletRequest -> {            List<FlowRule> flowRuleList = FlowRuleManager.getRules();            if (CollectionUtil.isNotEmpty(flowRuleList)) {                for (FlowRule rule : flowRuleList) {                    //规则如果包含点或者冒号就表示限制ip,如果不是就表示限制用户,配置为default,不会走此                    if (rule.getLimitApp().contains(":") || rule.getLimitApp().contains(".")) {                        return judgeIp(httpServletRequest);                    }else{                        return judgeAccount(httpServletRequest);                    }                }            }            return "";        });    }
   private String judgeIp(HttpServletRequest httpServletRequest) {        try {            String ip = 获取ip方法            return  ip;        } catch (Exception e) {            log.error("[sentinel限流]获取ip异常",e);        }        return "";    }
   private String judgeAccount(HttpServletRequest httpServletRequest) {          try{               //获取用户方法               String userName = //获取用户                            } catch (Exception e) {                log.error("[sentinel限流]获取用户失败",e);            }        }        return userName;    }}

到此,整个持久化及限流过程就完成了.有兴趣的朋友还可以思考下,如何通过某些机制实现对接口的自动化限流.源码为二次开发后sentinel-dashboard

关注公众号,发送:"限流" 即可获取源码




END
继续滑动看下一个

[附源码]SpringBoot整合Sentinel限流及Sentinel-Dashboard持久化规则到Nacos

小萌橘子 码农闲谈AI
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存