需求:实现sentinel的监控数据持久化
分析原有实现
github下载源码,打开sentinel-dashboard模块。
监控数据查看的controller:com.alibaba.csp.sentinel.dashboard.controller.MetricController
查看源码,原有的存储实现如下:
com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository
是一个接口,原有的具体实现是:
com.alibaba.csp.sentinel.dashboard.repository.metric.repository.InMemoryMetricsRepository
是基于内存存储的监控数据。实现方法为:
com.alibaba.csp.sentinel.dashboard.repository.metric.repository.InMemoryMetricsRepository#save
db改造
使用jpa的方式实现存储
添加依赖
<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.49</version>
</dependency>
表结构
CREATE TABLE `sentinel_metrics` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
`app` varchar(100) DEFAULT NULL COMMENT '应用名称',
`timestamp` datetime DEFAULT NULL COMMENT '监控信息的时间戳',
`resource` varchar(500) 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` double DEFAULT NULL COMMENT '所有successQps的rt的和',
`count` int(11) DEFAULT NULL COMMENT '本次聚合的总条数',
`resource_code` int(11) DEFAULT NULL COMMENT '资源的hashCode',
PRIMARY KEY (`id`),
KEY `app_idx` (`app`) USING BTREE,
KEY `resource_idx` (`resource`) USING BTREE,
KEY `timestamp_idx` (`timestamp`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=200202 DEFAULT CHARSET=utf8mb4;
entity
package com.alibaba.csp.sentinel.dashboard.repository.metric.po;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* @author fy
* @date 2022/11/22
*/
@Entity
@Table(name = "sentinel_metrics")
public class SentinelMetricPO implements Serializable {
/**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;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getGmtCreate() {
return gmtCreate;
}
public void setGmtCreate(Date gmtCreate) {
this.gmtCreate = gmtCreate;
}
public Date getGmtModified() {
return gmtModified;
}
public void setGmtModified(Date gmtModified) {
this.gmtModified = gmtModified;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
public Long getPassQps() {
return passQps;
}
public void setPassQps(Long passQps) {
this.passQps = passQps;
}
public Long getSuccessQps() {
return successQps;
}
public void setSuccessQps(Long successQps) {
this.successQps = successQps;
}
public Long getBlockQps() {
return blockQps;
}
public void setBlockQps(Long blockQps) {
this.blockQps = blockQps;
}
public Long getExceptionQps() {
return exceptionQps;
}
public void setExceptionQps(Long exceptionQps) {
this.exceptionQps = exceptionQps;
}
public Double getRt() {
return rt;
}
public void setRt(Double rt) {
this.rt = rt;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getResourceCode() {
return resourceCode;
}
public void setResourceCode(Integer resourceCode) {
this.resourceCode = resourceCode;
}
}
存储实现类
package com.alibaba.csp.sentinel.dashboard.repository.metric.repository;
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 com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
import com.alibaba.csp.sentinel.dashboard.repository.metric.po.SentinelMetricPO;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
/**
* @author fy
* @date 2022/11/22
*/
@Transactional
@Repository("sentinelMetricsRepository")
public class SentinelMetricsRepository implements MetricsRepository<MetricEntity> {
@PersistenceContext
private EntityManager em;
@Override
public void save(MetricEntity metric) {
if (metric == null || StringUtil.isBlank(metric.getApp())) {
return;
}
SentinelMetricPO metricPO = new SentinelMetricPO();
BeanUtils.copyProperties(metric, metricPO);
em.persist(metricPO);
}
@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 SentinelMetricPO");
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<SentinelMetricPO> metricPOs = query.getResultList();
if (CollectionUtils.isEmpty(metricPOs)) {
return results;
}
for (SentinelMetricPO metricPO : metricPOs) {
MetricEntity metricEntity = new MetricEntity();
BeanUtils.copyProperties(metricPO, 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 SentinelMetricPO");
hql.append(" WHERE app=:app");
hql.append(" AND timestamp>=:startTime");
// TODO: 2022/11/22 查看多久历史监控数据 30分钟
long startTime = System.currentTimeMillis() - 1000 * 60 * 30;
Query query = em.createQuery(hql.toString());
query.setParameter("app", app);
query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));
List<SentinelMetricPO> metricPOs = query.getResultList();
if (CollectionUtils.isEmpty(metricPOs)) {
return results;
}
List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();
for (SentinelMetricPO metricPO : metricPOs) {
MetricEntity metricEntity = new MetricEntity();
BeanUtils.copyProperties(metricPO, 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());
}
}