Sentinel 控制台实时监控持久化到MySQL

        Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。

        Sentinel 控制台中监控数据聚合后直接存在内存中,未进行持久化,且仅保留最近 5 分钟的监控数据。若需要监控数据持久化的功能,可以自行扩展实现 MetricsRepository 接口 。

        根据官方文档,对 Sentinel Dashboard 进行优化,将监控数据持久化到 MySQL 数据库,通过 MyBatis-Plus 对监控数据进行操作。

        官方地址:https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel

Sentinel 监控

        Sentinel 会记录资源访问的秒级数据(若没有访问则不进行记录)并保存在本地日志中,具体格式请见 秒级监控日志文档。Sentinel 控制台可以通过 Sentinel 客户端预留的 HTTP API 从秒级监控日志中拉取监控数据,并进行聚合。

        目前 Sentinel 控制台中监控数据聚合后直接存在内存中,未进行持久化,且仅保留最近 5 分钟的监控数据。若需要监控数据持久化的功能,可以自行扩展实现 MetricsRepository 接口(0.2.0 版本),然后注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可。MetricsRepository 接口定义了以下功能:

  • save 与 saveAll:存储对应的监控数据
  • queryByAppAndResourceBetween:查询某段时间内的某个应用的某个资源的监控数据
  • listResourcesOfApp:查询某个应用下的所有资源

其中默认的监控数据类型为 MetricEntity,包含应用名称、时间戳、资源名称、异常数、请求通过数、请求拒绝数、平均响应时间等信息。对于监控数据的存储,用户需要根据自己的存储精度,来考虑如何存储这些监控数据。为了更好地支撑大规模的集群,生产环境通常需要部署多个控制台实例,通常需要仔细设计下监控分片拉取和写入策略。

        同时用户可以自行进行扩展,适配 Grafana 等可视化平台,以便将监控数据更好地进行可视化。

Sentinel Dashboard

MetricsRepository

MetricsRepository接口定义

    • save:保存 metric 信息
    • saveAll:批量保存 metric 信息
    • queryByAppAndResourceBetween:通过应用名称(app)、资源名称(resource)、timestamp 开始时间 、timestamp 结束时间查询 metric 列表
    • listResourcesOfApp:通过应用名称(app) 查询 Metric 列表
public interface MetricsRepository<T> {
    
    /**
    * Save the metric to the storage repository.
    */
    void save(T metric);
    
    /**
    * Save all metrics to the storage repository.
    */
    void saveAll(Iterable<T> metrics);
    
    /**
    * Get all metrics by appName and resourceName between a period of time.
    */
    List<T> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime);
    
    /**
    * List resource name of provided application name.
    */
    List<String> listResourcesOfApp(String app);
}

目前 MetricsRepository 接口的实现是基于内存级别

com.alibaba.csp.sentinel.dashboard.repository.metric.InMemoryMetricsRepository。

MetricEntity

        MetricEntity实体类

public class MetricEntity {
    private Long id;
    private Date gmtCreate;
    private Date gmtModified;
    private String app;
    /**
     * 监控信息的时间戳
     */
    private Date timestamp;
    private String resource;
    private Long passQps;
    private Long successQps;
    private Long blockQps;
    private Long exceptionQps;

    /**
     * summary rt of all success exit qps.
     */
    private double rt;

    /**
     * 本次聚合的总条数
     */
    private int count;

    private int resourceCode;
    
    ... ...
}

Sentinel Dashboard 整合 MyBatis Plus

MyBatis Plus 整合步骤

        Sentinel Dashboard 整合 MyBatis Plus 将监控数据持久化到 MySQL 步骤 :

        1、引入相关依赖

        2、修改 application.properties 配置

        3、根据 MetricEntity 创建 MySQL 数据库脚本并执行

        4、通过 MyBatis Plus Generator 生成 entity、mypper、xml 文件

        5、新增 MetricsRepository 的数据库实现 InDatabaseMetricsRepository

        6、修改 MetricsRepository 相关注入,改为 InDatabaseMetricsRepository 实现

1、引入相关依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.26</version>
    </dependency>

2、修改 application.properties 中配置

        修改 application.properties 文件,在 application.properties 文件中配置 MySQL 数据库连接地址和 mybatis-plus 相关配置。

# mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring-boot?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root

# mybatis plus
mybatis-plus.global-config.banner=false
mybatis-plus.configuration.map-underscore-to-camel-case=true
mapper-locations: classpath:mapper/*Mapper.xml

3、创建 MySQL 数据库脚本

MetricEntity 实体类中,已经提供了监控信息的相关数据,根据属性,创建对应的 MySQL 脚本

CREATE TABLE `metric` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `app` varchar(255) DEFAULT NULL COMMENT '应用名称',
  `resource` varchar(255) DEFAULT NULL COMMENT '资源名称',
  `timestamp` datetime DEFAULT NULL COMMENT '监控信息时间戳',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
  `pass_qps` bigint(20) DEFAULT NULL COMMENT '通过QPS',
  `success_qps` bigint(20) DEFAULT NULL COMMENT '成功QPS',
  `block_qps` bigint(20) DEFAULT NULL COMMENT '限流QPS',
  `exception_qps` bigint(20) DEFAULT NULL COMMENT '异常QPS',
  `rt` decimal(10,2) DEFAULT NULL COMMENT '资源的平均响应时间',
  `count` int(10) DEFAULT NULL COMMENT '本次聚合的总条数',
  `resource_code` int(10) DEFAULT NULL COMMENT '资源hashcode',
  PRIMARY KEY (`id`),
  KEY `idx_app_timestamp` (`app`,`timestamp`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Sentinel监控信息表';

4、生成实体类等文件

        通过 MyBatis Plus Generator 生成 entity、mypper、xml 文件

        MyBatis Plus Generator 工具下载地址:

        GitHub spring-boot-mybatis-plus-generator        

        实体类 Metric.java

public class Metric implements Serializable {
    
    private static final long serialVersionUID = 1L;
    /**
    * id
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    /**
    * 创建时间
    */
    private Date gmtCreate;
    
    /**
    * 修改时间
    */
    private Date gmtModified;
    
    /**
    * 应用名称
    */
    private String app;
    
    /**
    * 监控信息时间戳
    */
    private Date timestamp;
    
    /**
    * 资源名称
    */
    private String resource;
    
    /**
    * 通过QPS
    */
    private Long passQps;
    
    /**
    * 成功QPS
    */
    private Long successQps;
    
    /**
    * 限流QPS
    */
    private Long blockQps;
    
    /**
    * 异常QPS
    */
    private Long exceptionQps;
    
    /**
    * 资源平均响应时间
    */
    private Double rt;
    
    /**
    * 本次聚合的总条数
    */
    private Integer count;
    
    /**
    * 资源hashcode
    */
    private Integer resourceCode;
    
    // setter 、getter
}

        MetricMapper.java

        其中 MetricsRepository 的 saveAll 的实现是通过循环调用 save 方法,如果对数据库进行操作,可以使用批量插入操作,减少对数据库的频繁调用。

        metrics.forEach(this::save);

        在 MetricMapper.xml 文件中,batchInsert 为批量保存操作接口。

@Mapper
public interface MetricMapper extends BaseMapper<Metric> {

    int batchInsert(List<Metric> metricList);
    
}

        MetricMapper.xml文件中 batchInsert 是 MetricMapper 接口中 int batchInsert(List<Metric> metricList) 对应的批量保存 SQL 。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alibaba.csp.sentinel.dashboard.datasource.mapper.MetricMapper">
  
  <!-- 通用查询映射结果 -->
  <resultMap id="BaseResultMap" type="com.alibaba.csp.sentinel.dashboard.datasource.entity.Metric">
    <id column="id" property="id" />
    <result column="gmt_create" property="gmtCreate" />
    <result column="gmt_modified" property="gmtModified" />
    <result column="app" property="app" />
    <result column="timestamp" property="timestamp" />
    <result column="resource" property="resource" />
    <result column="pass_qps" property="passQps" />
    <result column="success_qps" property="successQps" />
    <result column="block_qps" property="blockQps" />
    <result column="exception_qps" property="exceptionQps" />
    <result column="rt" property="rt" />
    <result column="count" property="count" />
    <result column="resource_code" property="resourceCode" />
  </resultMap>
  
  <insert id="batchInsert">
    insert into metric
    (
    gmt_create, gmt_modified, app, timestamp, resource, pass_qps,
    block_qps, success_qps, exception_qps, rt, count, resource_code
    )
    values
    <foreach collection="list" separator="," item="item">
      (
      #{item.gmtCreate,jdbcType=TIMESTAMP}, #{item.gmtModified,jdbcType=TIMESTAMP}, #{item.app,jdbcType=VARCHAR},
      #{item.timestamp,jdbcType=TIMESTAMP}, #{item.resource,jdbcType=VARCHAR}, #{item.passQps,jdbcType=BIGINT},
      #{item.blockQps,jdbcType=BIGINT}, #{item.successQps,jdbcType=BIGINT}, #{item.exceptionQps,jdbcType=BIGINT},
      #{item.rt,jdbcType=DECIMAL}, #{item.count,jdbcType=INTEGER}, #{item.resourceCode,jdbcType=INTEGER}
      )
    </foreach>
  </insert>
</mapper>

5、MetricsRepository 数据库持久化实现

        新增一个 MetricsRepository 接口的实现类 InDatabaseMetricsRepository.java ,用来将监控信息保存到数据库,并且通过数据库进行操作。

@Component
public class InDatabaseMetricsRepository implements MetricsRepository<MetricEntity> {
    
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    @Resource
    private MetricMapper metricMapper;
    
    @Override
    public void save(MetricEntity entity) {
        if (entity == null || StringUtil.isBlank(entity.getApp())) {
            return;
        }
        readWriteLock.writeLock().lock();
        try {
            metricMapper.insert(toPo(entity));
        } finally {
            readWriteLock.writeLock().unlock();
        }
        
    }
    
    @Override
    public void saveAll(Iterable<MetricEntity> metrics) {
        if (metrics == null) {
            return;
        }
        readWriteLock.writeLock().lock();
        try {
            List<Metric> metricList = new ArrayList<>();
            metrics.forEach(metric -> metricList.add(toPo(metric)));
            metricMapper.batchInsert(metricList);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    
    @Override
    public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource,
                                                           long startTime, long endTime) {
        List<MetricEntity> results = new ArrayList<>();
        
        if (StringUtil.isBlank(app)) {
            return results;
        }
        
        readWriteLock.readLock().lock();
        try {
            Metric metric = new Metric();
            metric.setApp(app);
            metric.setResource(resource);
            QueryWrapper<Metric> queryWrapper = new QueryWrapper<>(metric);
            queryWrapper.between("timestamp", new Date(startTime), new Date(endTime));
            List<Metric> metricList = metricMapper.selectList(queryWrapper);
            
            if (CollectionUtils.isEmpty(metricList)){
                return results;
            }
            
            metricList.forEach(e->results.add(toPo(e)));
            return results;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    
    @Override
    public List<String> listResourcesOfApp(String app) {
        List<String> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }
        
        final long minTimeMs = System.currentTimeMillis() - 1000 * 60;
        Map<String, MetricEntity> resourceCount = new ConcurrentHashMap<>(32);
        
        readWriteLock.readLock().lock();
        try {
            QueryWrapper<Metric> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("app", app);
            queryWrapper.ge("timestamp", new Date(minTimeMs));
            List<Metric> metricList = metricMapper.selectList(queryWrapper);
            
            List<MetricEntity> metricEntityList = new ArrayList<>();
            metricList.forEach(e -> metricEntityList.add(toPo(e)));
            
            if (CollectionUtils.isEmpty(metricEntityList)){
                return results;
            }
            
            for (MetricEntity newEntity : metricEntityList) {
                String resource = newEntity.getResource();
                if (resourceCount.containsKey(resource)) {
                    MetricEntity oldEntity = resourceCount.get(resource);
                    oldEntity.addPassQps(newEntity.getPassQps());
                    oldEntity.addRtAndSuccessQps(newEntity.getRt(), newEntity.getSuccessQps());
                    oldEntity.addBlockQps(newEntity.getBlockQps());
                    oldEntity.addExceptionQps(newEntity.getExceptionQps());
                    oldEntity.addCount(1);
                } else {
                    resourceCount.put(resource, MetricEntity.copyOf(newEntity));
                }
            }
            // Order by last minute b_qps DESC.
            return resourceCount.entrySet()
                .stream()
                .sorted((o1, o2) -> {
                    MetricEntity e1 = o1.getValue();
                    MetricEntity e2 = o2.getValue();
                    int t = e2.getBlockQps().compareTo(e1.getBlockQps());
                    if (t != 0) {
                        return t;
                    }
                    return e2.getPassQps().compareTo(e1.getPassQps());
                })
                .map(Entry::getKey)
                .collect(Collectors.toList());
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    
    private Metric toPo(MetricEntity metricEntity){
        Metric metric = new Metric();
        BeanUtils.copyProperties(metricEntity,metric,Metric.class);
        return metric;
    }
    
    private MetricEntity toPo(Metric metric){
        MetricEntity metricEntity = new MetricEntity();
        BeanUtils.copyProperties(metric,metricEntity,MetricEntity.class);
        return metricEntity;
    }
}

6、修改 MetricsRepository 注入

        MetricController 和 MetricFetcher 中 使用 MetricsRepository ,修改 MetricsRepository 的注入。

com.alibaba.csp.sentinel.dashboard.controller.MetricController

com.alibaba.csp.sentinel.dashboard.metric.MetricFetcher

        官方 Wiki 中

        在相应位置通过 @Qualifier 注解指定对应的 bean name 即可

    @Qualifier("inDatabaseMetricsRepository")
    @Autowired
    private MetricsRepository<MetricEntity> metricStore;

如果不想使用 @Autowired 和 @Qualifier,也可以使用 @Resource 注解。

    @Resource(name = "inDatabaseMetricsRepository")
    private MetricsRepository<MetricEntity> metricStore;

启动 Sentinel Dashboard

        使用 mvn clean package将 Sentinel Dashboard 打成 jar ,在命令行通过 java -jar 启动 Sentinel Dashboard 控制台

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 \
 -Dproject.name=sentinel-dashboard -jar target/sentinel-dashboard.jar

        也可以使用 Spring Boot 直接启动

        我们打开控制台 http://localhost:8080 ,同时调用已经接入 Sentinel 服务,此时会有相关的监控信息

        数据库 metric 表保存了接口请求的监控信息

源码

        项目源码:

        GitHub Sentinel Dashboard

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值