Sentinel控制台监控数据持久化到MySQL数据库

阅读文本大概需要3分钟。

       根据官方wiki文档,Sentinel控制台的实时监控数据,默认仅存储 5 分钟以内的数据。如需持久化,需要定制实现相关接口。

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

给出了指导步骤:

  • 自行扩展实现 MetricsRepository 接口;

  • 注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可;

0x01:MetricsRepository接口定义

package com.alibaba.csp.sentinel.dashboard.repository.metric;

import java.util.List;


public interface MetricsRepository<T> {

    void save(T metric);

    void saveAll(Iterable<T> metrics);

    List<T> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime);

    List<String> listResourcesOfApp(String app);
}

该接口就只定义4个方法,分别用于保存和查询Sentinel的metric数据。注释其实很清楚了,解析如下:

  • save:保存单个metric

  • saveAll:保存多个metric

  • queryByAppAndResourceBetween:通过应用名称、资源名称、开始时间、结束时间查询metric列表

  • listResourcesOfApp:通过应用名称查询资源列表

目前该接口只有一个基于内存级别的实现类:com.alibaba.csp.sentinel.dashboard.repository.metric.InMemoryMetricsRepository。

另外还有一个实体类com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity,如下图

梳理了相关的类关系就可以实现了。

0x02:根据MetricEntity新建数据库和新建实体类

建表语句如下

-- 创建监控数据表
CREATE TABLE `t_sentinel_metric` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'id,主键',
  `gmt_create` DATETIME COMMENT '创建时间',
  `gmt_modified` DATETIME COMMENT '修改时间',
  `app` VARCHAR(100) COMMENT '应用名称',
  `timestamp` DATETIME COMMENT '统计时间',
  `resource` VARCHAR(500) COMMENT '资源名称',
  `pass_qps` INT COMMENT '通过qps',
  `success_qps` INT COMMENT '成功qps',
  `block_qps` INT COMMENT '限流qps',
  `exception_qps` INT COMMENT '发送异常的次数',
  `rt` DOUBLE COMMENT '所有successQps的rt的和',
  `_count` INT COMMENT '本次聚合的总条数',
  `resource_code` INT COMMENT '资源的hashCode',
  INDEX app_idx(`app`) USING BTREE,
  INDEX resource_idx(`resource`) USING BTREE,
  INDEX timestamp_idx(`timestamp`) USING BTREE,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

实体类如下

package com.alibaba.csp.sentinel.dashboard.datasource.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author 2230
 *
 */
@Entity
@Table(name = "t_sentinel_metric")
public class MetricDto implements Serializable {

    private static final long serialVersionUID = 7200023615444172715L;

    /**id,主键*/
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    /**创建时间*/
    @Column(name = "gmt_create")
    private Date gmtCreate;

    /**修改时间*/
    @Column(name = "gmt_modified")
    private Date gmtModified;

    /**应用名称*/
    @Column(name = "app")
    private String app;

    /**统计时间*/
    @Column(name = "timestamp")
    private Date timestamp;

    /**资源名称*/
    @Column(name = "resource")
    private String resource;

    /**通过qps*/
    @Column(name = "pass_qps")
    private Long passQps;

    /**成功qps*/
    @Column(name = "success_qps")
    private Long successQps;

    /**限流qps*/
    @Column(name = "block_qps")
    private Long blockQps;

    /**发送异常的次数*/
    @Column(name = "exception_qps")
    private Long exceptionQps;

    /**所有successQps的rt的和*/
    @Column(name = "rt")
    private Double rt;

    /**本次聚合的总条数*/
    @Column(name = "_count")
    private Integer count;

    /**资源的hashCode*/
    @Column(name = "resource_code")
    private Integer resourceCode;

   // get  set 方法省略

}

0x03:pom.xml添加依赖

因为是基于JPA和MySQL数据库实现,所以需要添加JPA依赖和MySQL数据库驱动依赖

   <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
          <version>${spring.boot.version}</version>
        </dependency>

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

0x04:实现MetricsRepository 接口,把数据持久化到MySQL数据库

注意实现添加@Repository("jpaMetricsRepository")配置

package com.alibaba.csp.sentinel.dashboard.repository.metric;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricDto;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.util.StringUtil;

/**
 * https://www.cnblogs.com/yinjihuan/p/10574998.html
 * https://www.cnblogs.com/cdfive2018/p/9838577.html
 * https://blog.csdn.net/wk52525/article/details/104587239/
 * https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
 * 
 * @author 2230
 *
 */
@Transactional
@Repository("jpaMetricsRepository")
public class JpaMetricsRepository implements MetricsRepository<MetricEntity> {

    @PersistenceContext
    private EntityManager em;

    @Override
    public void save(MetricEntity metric) {
        if (metric == null || StringUtil.isBlank(metric.getApp())) {
            return;
        }

        MetricDto metricDto = new MetricDto();
        BeanUtils.copyProperties(metric, metricDto);
        em.persist(metricDto);
    }

    @Override
    public void saveAll(Iterable<MetricEntity> metrics) {
        if (metrics == null) {
            return;
        }

        metrics.forEach(this::save);
    }

    @Override
    public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {
        List<MetricEntity> results = new ArrayList<MetricEntity>();
        if (StringUtil.isBlank(app)) {
            return results;
        }

        if (StringUtil.isBlank(resource)) {
            return results;
        }

        StringBuilder hql = new StringBuilder();
        hql.append("FROM MetricDto");
        hql.append(" WHERE app=:app");
        hql.append(" AND resource=:resource");
        hql.append(" AND timestamp>=:startTime");
        hql.append(" AND timestamp<=:endTime");

        Query query = em.createQuery(hql.toString());
        query.setParameter("app", app);
        query.setParameter("resource", resource);
        query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));
        query.setParameter("endTime", Date.from(Instant.ofEpochMilli(endTime)));

        List<MetricDto> metricDtos = query.getResultList();
        if (CollectionUtils.isEmpty(metricDtos)) {
            return results;
        }

        for (MetricDto metricDto : metricDtos) {
            MetricEntity metricEntity = new MetricEntity();
            BeanUtils.copyProperties(metricDto, metricEntity);
            results.add(metricEntity);
        }
        return results;
    }

    @Override
    public List<String> listResourcesOfApp(String app) {
        List<String> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }

        StringBuilder hql = new StringBuilder();
        hql.append("FROM MetricDto");
        hql.append(" WHERE app=:app");
        hql.append(" AND timestamp>=:startTime");

        long startTime = System.currentTimeMillis() - 1000 * 60;
        Query query = em.createQuery(hql.toString());
        query.setParameter("app", app);
        query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));

        List<MetricDto> metricDtos = query.getResultList();
        if (CollectionUtils.isEmpty(metricDtos)) {
            return results;
        }

        List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();
        for (MetricDto metricDto : metricDtos) {
            MetricEntity metricEntity = new MetricEntity();
            BeanUtils.copyProperties(metricDto, metricEntity);
            metricEntities.add(metricEntity);
        }

        Map<String, MetricEntity> resourceCount = new HashMap<>(32);

        for (MetricEntity metricEntity : metricEntities) {
            String resource = metricEntity.getResource();
            if (resourceCount.containsKey(resource)) {
                MetricEntity oldEntity = resourceCount.get(resource);
                oldEntity.addPassQps(metricEntity.getPassQps());
                oldEntity.addRtAndSuccessQps(metricEntity.getRt(), metricEntity.getSuccessQps());
                oldEntity.addBlockQps(metricEntity.getBlockQps());
                oldEntity.addExceptionQps(metricEntity.getExceptionQps());
                oldEntity.addCount(1);
            } else {
                resourceCount.put(resource, MetricEntity.copyOf(metricEntity));
            }
        }

        // 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(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
}

0x05:application.properties配置文件添加数据库配置

# datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/gateway_v2?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root

# spring data jpa
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=false

主要配置数据库连接信息和JPA的配置项,JPA使用Hibernate实现。

0x06:数据库持久化换成JpaMetricsRepository实现

找到如下两个类

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

在metricStore属性上添加多一个@Qualifier("jpaMetricsRepository")注解,如下图

0x07:验证

设置sentinel-dashboard工程的启动参数

-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard

具体可以参考【 Sentinel如何进行流量监控 】;可以发现数据已经保存到MySQL数据库。

备注:以上代码改造都是在sentinel-dashboard项目上。

参考:https://www.cnblogs.com/cdfive2018/p/9838577.html

往期精彩

01 Sentinel如何进行流量监控

02 Nacos源码编译

03 基于Apache Curator框架的ZooKeeper使用详解

04 spring boot项目整合xxl-job

05 互联网支付系统整体架构详解

关注我

每天进步一点点

喜欢!在看☟

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章