[case33]sentinel custom DataSource actual combat

  sentinel

Order

This article mainly studies how to customize sentinel’s DataSource, taking jdbc as an example.

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>

Extending 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;
        }

    }
}
  • Here, take pull mode as an example, so the extension is AutoRefreshDataSource.

Data Structure and Initialization Data

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
  }
]');
  • The schema of the jdbc store of spring cloud config server is modeled here.

autoloading

@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());
    }
}
  • At startup, the data sources of flowRule, degradeRule and systemRule are registered through flowrule.register2property.

Verification

Access after startuphttp://localhost: 8080/Actor/Sentinel, you can see the following rules:

{
  "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": [
        "/*"
      ]
    }
  }
}

Looking at sentinel’s dashboard, you can see that dashboard can also recognize the application of customized rules.

Summary

Sentinel datasource provides a flexible extension mechanism and can customize the datasource to meet the needs of different applications.

doc