本文主要研究一下如何自定义sentinel的DataSource,这里以jdbc为例。

maven

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sentinel</artifactId>
            <version>0.2.0.BUILD-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

扩展AutoRefreshDataSource

public class JdbcDataSource<T> extends AutoRefreshDataSource<String, T> {

    final String DEFAULT_SQL = "SELECT VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and KEY=?";

    final PropertiesResultSetExtractor extractor = new PropertiesResultSetExtractor();

    JdbcTemplate jdbc;

    String app;

    String key;

    String profile;

    public JdbcDataSource(JdbcTemplate jdbc,String app,String profile,String key,ConfigParser<String, T> configParser, long recommendRefreshMs) {
        super(configParser, recommendRefreshMs);
        this.jdbc = jdbc;
        this.app = app;
        this.key = key;
        this.profile = profile;
    }

    @Override
    public String readSource() throws Exception {
        List<String> data = (List<String>) jdbc.query(DEFAULT_SQL,
                new Object[] { app, profile, key }, this.extractor);
        if(data.size() > 0){
            return data.get(0);
        }
        return null;
    }

    class PropertiesResultSetExtractor implements ResultSetExtractor<List<String>> {

        @Override
        public List<String> extractData(ResultSet rs)
                throws SQLException, DataAccessException {
            List<String> result = new ArrayList<>(1);
            while (rs.next()) {
                result.add(rs.getString(1));
            }
            return result;
        }

    }
}
  • 这里以拉模式为例,因而扩展的是AutoRefreshDataSource

数据结构及初始化数据

CREATE TABLE IF NOT EXISTS PROPERTIES (
  KEY         VARCHAR(128),
  VALUE       VARCHAR(4096),
  APPLICATION VARCHAR(128),
  PROFILE     VARCHAR(128),
  PRIMARY KEY (`KEY`, `APPLICATION`, `PROFILE`)
);

INSERT INTO PROPERTIES (APPLICATION, PROFILE, KEY, VALUE)
VALUES ('sentinel-demo', 'jdbc', 'flow', '[
  {
    "resource": "abc",
    "controlBehavior": 0,
    "count": 20.0,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
  },
  {
    "resource": "abc1",
    "controlBehavior": 0,
    "count": 20.0,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
  }
]');
INSERT INTO PROPERTIES (APPLICATION, PROFILE, KEY, VALUE)
VALUES ('sentinel-demo', 'jdbc', 'system', '[
  {
    "avgRt": 10,
    "highestSystemLoad": 5.0,
    "maxThread": 10,
    "qps": 20.0
  }
]');

INSERT INTO PROPERTIES (APPLICATION, PROFILE, KEY, VALUE)
VALUES ('sentinel-demo', 'jdbc', 'degrade', '[
  {
    "resource": "abc0",
    "count": 20.0,
    "grade": 0,
    "passCount": 0,
    "timeWindow": 10
  },
  {
    "resource": "abc1",
    "count": 15.0,
    "grade": 0,
    "passCount": 0,
    "timeWindow": 10
  }
]');
  • 这里仿照spring cloud config server的jdbc存储的schema

自动加载

@Component
public class SentinelJdbcAutoConfig implements CommandLineRunner {

    @Value("${spring.application.name}")
    String app;

    @Autowired
    private Environment environment;

    @Autowired
    JdbcTemplate jdbcTemplate;

    int defaultRefreshMs = 10*1000;

    @Override
    public void run(String... args) throws Exception {
        String profile = environment.getActiveProfiles().length > 0 ? environment.getActiveProfiles()[0] : "default";
        // data source for FlowRule
        DataSource<String, List<FlowRule>> flowRuleDataSource = new JdbcDataSource<List<FlowRule>>(jdbcTemplate,
                app,profile,"flow", new JsonFlowRuleListParser(),defaultRefreshMs);
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

        // data source for DegradeRule
        DataSource<String, List<DegradeRule>> degradeRuleDataSource = new JdbcDataSource<List<DegradeRule>>(jdbcTemplate,
                app,profile,"degrade", new JsonDegradeRuleListParser(),defaultRefreshMs);
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());

        // data source for SystemRule
        DataSource<String, List<SystemRule>> systemRuleDataSource = new JdbcDataSource<List<SystemRule>>(jdbcTemplate,
                app,profile,"system", new JsonSystemRuleListParser(),defaultRefreshMs);
        SystemRuleManager.register2Property(systemRuleDataSource.getProperty());
    }
}
  • 这里在启动时通过FlowRuleManager.register2Property,注册了flowRule、degradeRule、systemRule三类规则的数据源

验证

启动之后访问http://localhost:8080/actuator/sentinel,可以看到如下规则:

{
  "DegradeRules": [
    {
      "resource": "abc1",
      "limitApp": "default",
      "count": 15,
      "timeWindow": 10,
      "grade": 0,
      "cut": false,
      "passCount": 0
    },
    {
      "resource": "abc0",
      "limitApp": "default",
      "count": 20,
      "timeWindow": 10,
      "grade": 0,
      "cut": false,
      "passCount": 0
    }
  ],
  "SystemRules": [
    {
      "resource": null,
      "limitApp": null,
      "highestSystemLoad": 5,
      "qps": -1,
      "avgRt": -1,
      "maxThread": -1
    },
    {
      "resource": null,
      "limitApp": null,
      "highestSystemLoad": -1,
      "qps": -1,
      "avgRt": 10,
      "maxThread": -1
    },
    {
      "resource": null,
      "limitApp": null,
      "highestSystemLoad": -1,
      "qps": -1,
      "avgRt": -1,
      "maxThread": 10
    },
    {
      "resource": null,
      "limitApp": null,
      "highestSystemLoad": -1,
      "qps": 20,
      "avgRt": -1,
      "maxThread": -1
    }
  ],
  "FlowRules": [
    {
      "resource": "abc1",
      "limitApp": "default",
      "grade": 1,
      "count": 20,
      "strategy": 0,
      "refResource": null,
      "controlBehavior": 0,
      "warmUpPeriodSec": 10,
      "maxQueueingTimeMs": 500
    },
    {
      "resource": "abc",
      "limitApp": "default",
      "grade": 1,
      "count": 20,
      "strategy": 0,
      "refResource": null,
      "controlBehavior": 0,
      "warmUpPeriodSec": 10,
      "maxQueueingTimeMs": 500
    }
  ],
  "properties": {
    "enabled": true,
    "port": "7080",
    "dashboard": "localhost:9999",
    "filter": {
      "order": -2147483648,
      "urlPatterns": [
        "/*"
      ]
    }
  }
}
查看sentinel的dashboard,可以发现dashboard也能识别出应用自定义的规则。

小结

sentinel datasource提供了灵活的扩展机制,可以自定义数据源来满足不同应用的需求。

doc


codecraft
11.9k 声望2k 粉丝

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下...


引用和评论

0 条评论