Sentinel数据源(datasource)扩展-数据库

一、背景

sentinel推模式源码中的数据源是可扩展的,源码中只有几种数据源:redis、Apollo、Nacos、Zookeeper等;

image.png

拉模式中只有一种

image.png

二、具体实现

以mysql为例,作为数据库的数据源

1、定义一个MysqlDataSource

需要继承AutoRefreshDataSource<S,T>,然后这是一个模板类,第一个参数表示从DataSource中读取的数据格式是什么类型,第二个参数表示最后需要转换成什么类型(在这里分别是从数据库读取出来的List, 和转换后的List)

package com.高振芳.sentinel.controller;

import com.alibaba.csp.sentinel.datasource.AutoRefreshDataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.高振芳.sentinel.entity.ResourceRoleQps;
import com.高振芳.sentinel.config.DataSourceUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @program: sentinel
 * @description: sentinel集成sql规则数据源
 * @author: Gaozf
 * @create: 2020-08-11 10:38
 **/
public class MysqlRefreshableDataSource extends AutoRefreshDataSource<List<ResourceRoleQps>,List<FlowRule>> {

    private static final long DEFAULT_REFRESH_MS = 3000;
    
    public MysqlRefreshableDataSource(Converter<List<ResourceRoleQps>, List<FlowRule>> configParser) {
        super(configParser, DEFAULT_REFRESH_MS);
        firstLoad();
    }

    private void firstLoad() {
        try {
            List<FlowRule> newValue = loadConfig();
            getProperty().updateValue(newValue);
        } catch (Throwable e) {
            System.out.println(e);
            RecordLog.info("loadConfig exception", e);
        }
    }

    @Override
    public List<ResourceRoleQps> readSource() throws Exception {

        JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
        return jdbcTemplate.query("select id, app_id, api, limit_qps, create_at from resource_role_qps"
                ,new RowMapper<ResourceRoleQps>(){
                    @Override
                    public ResourceRoleQps mapRow(ResultSet resultSet, int i) throws SQLException {
                        return new ResourceRoleQps(resultSet.getLong("id"),
                                resultSet.getString("app_id"),
                                resultSet.getString("api"),
                                resultSet.getLong("limit_qps"),
                                resultSet.getLong("create_at"));
                    }
                });
    }

    @Override
    public void close() throws Exception {
        super.close();
    }
}

readSource()方法就是为了从数据源读取所需数据
这里需要注意了:不可以使用注解的方式通过mybatis从数据库拉取数据,因为这个类时不能通过注解进行注入,没有无参构造函数,所以通过注解注入Mapper类去对数据库进行查找是不可以的。我这里实现是通过jdbc的方式。

2、调用、实现

    @GetMapping("/gzf1111")
    @SentinelResource(value = "gzf1111",blockHandler = "blockMehord")
    public String hello(){
        ReadableDataSource readableDataSource = new MysqlRefreshableDataSource(source ->
                source.stream().map(openApiAppIdApiQps -> {
                    FlowRule flowRule = new FlowRule();
                    flowRule.setResource(openApiAppIdApiQps.getApi());
                    flowRule.setCount(openApiAppIdApiQps.getLimitQps());
                    flowRule.setLimitApp(openApiAppIdApiQps.getAppId());
                    flowRule.setGrade(1);
                    flowRule.setStrategy(0);
                    flowRule.setControlBehavior(0);
                    return flowRule;
                }).collect(Collectors.toList())
        );
        // 自定义拉取数据源
        FlowRuleManager.register2Property(readableDataSource.getProperty());
        return "hello world";
    }
   public String blockMehord(BlockException ex) {
        return "熔断";
    }

3、JdbcTemplate实现方式

maven依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>

配置文件datasource.properties中数据库配置

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/sentinel?characterEncoding=utf-8
username=root
password=root
initialSize=5
maxActive=10
maxWait=2000

配置参数说明

image.png

Jdbc工具类

public class DataSourceUtils {

    private static DataSource dataSource;
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    //在静态代码块中初始化连接池
    static {
        Properties properties = new Properties();
        try {
            //getResourceAsStream(String name) 在模块中查找指定名称的文件,返回该文件的输入流
            properties.load(DataSourceUtils.class.getClassLoader().getResourceAsStream("datasource.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 直接可以获取一个连接池
    public static DataSource getDataSource() {
        return dataSource;
    }

    // 获取连接对象
    public static Connection getConnection() throws SQLException {

        Connection con = tl.get();
        if (con == null) {
            con = dataSource.getConnection();
            tl.set(con);
        }
        return con;
    }

    // 开启事务
    public static void startTransaction() throws SQLException {
        Connection con = getConnection();
        if (con != null) {
            con.setAutoCommit(false);
        }
    }

    // 事务回滚
    public static void rollback() throws SQLException {
        Connection con = getConnection();
        if (con != null) {
            con.rollback();
        }
    }

    // 提交并且 关闭资源及从ThreadLocall中释放
    public static void commitAndRelease() throws SQLException {
        Connection con = getConnection();
        if (con != null) {
            con.commit(); // 事务提交
            con.close();// 关闭资源
            tl.remove();// 从线程绑定中移除
        }
    }

    // 关闭资源方法
    public static void closeConnection() throws SQLException {
        Connection con = getConnection();
        if (con != null) {
            con.close();
            tl.remove();// 从线程绑定中移除
        }
    }

    public static void closeStatement(Statement st) throws SQLException {
        if (st != null) {
            st.close();
        }
    }

    public static void closeResultSet(ResultSet rs) throws SQLException {
        if (rs != null) {
            rs.close();
        }
    }

}

实现原理看另一篇DataSource源码解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351