目录
(1)改造目标:
(2)源码分析:
(3)设计表结构:
(4)源码增加依赖:
(5)源码增加实体类:
(6)源码修改配置文件:
(7)源码增加接口自定义实现类:
(8)源码修改接口调用自定义实现类:
(9)源码打包:
(10)windows启动控制台:
(11)windows关闭控制台:
(12)linux启动控制台:
(13)linux关闭控制台:
A、查找服务进程ID:
B、杀死服务进程ID:
(14)访问测试:
(15)移动部署修改:
(16)个人修改运行包下载地址:
官网说明:https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel
通过使用sentinel-dashboard发现控制台里默认的实时流量监控数据只保留5分钟,并且是保存在内存中,这很不合适,想要把这个控制台应用在生产环境那么就需要将实时监控的数据进行持久化。
因此,改造监控逻辑的目标就是将监控数据持久化。可以保存数据,以供查看历史流量信息。
官网说明:https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel
若需要监控数据持久化的功能,可以自行扩展实现 MetricsRepository 接口(0.2.0 版本),然后注册成Spring Bean并在相应位置通过@Qualifier注解指定对应的bean name即可。
参考MetricEntity类设计一张表sentinel_metric来存储监控的metric数据。具体表结构SQL脚本如下:
CREATE TABLE `sentinel_metric` (
`id` int(11) 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` int(11) DEFAULT NULL COMMENT '通过qps',
`success_qps` int(11) DEFAULT NULL COMMENT '成功qps',
`block_qps` int(11) DEFAULT NULL COMMENT '限流qps',
`exception_qps` int(11) DEFAULT NULL COMMENT '发送异常的次数',
`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 DEFAULT CHARSET=utf8;
由于本次要将监控数据持久化到mysql数据库中,因此在sentinel源码中sentinel-dashboard模块下增加依赖:
在sentinel源码中sentinel-dashboard模块下datasource.entity包下,新建jpa包,下面新建sentinel_metric表对应的实体类MetricPO。在类中写上表结构对应的属性、创建setter方法、创建getter方法、创建构造方法、重写toString()等方法。
package com.alibaba.csp.sentinel.dashboard.datasource.entity.jpa;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "sentinel_metric")
public class MetricPO 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;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public Long getId() {
return id;
}
public Date getGmtCreate() {
return gmtCreate;
}
public Date getGmtModified() {
return gmtModified;
}
public String getApp() {
return app;
}
public Date getTimestamp() {
return timestamp;
}
public String getResource() {
return resource;
}
public Long getPassQps() {
return passQps;
}
public Long getSuccessQps() {
return successQps;
}
public Long getBlockQps() {
return blockQps;
}
public Long getExceptionQps() {
return exceptionQps;
}
public Double getRt() {
return rt;
}
public Integer getCount() {
return count;
}
public Integer getResourceCode() {
return resourceCode;
}
public void setId(Long id) {
this.id = id;
}
public void setGmtCreate(Date gmtCreate) {
this.gmtCreate = gmtCreate;
}
public void setGmtModified(Date gmtModified) {
this.gmtModified = gmtModified;
}
public void setApp(String app) {
this.app = app;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public void setResource(String resource) {
this.resource = resource;
}
public void setPassQps(Long passQps) {
this.passQps = passQps;
}
public void setSuccessQps(Long successQps) {
this.successQps = successQps;
}
public void setBlockQps(Long blockQps) {
this.blockQps = blockQps;
}
public void setExceptionQps(Long exceptionQps) {
this.exceptionQps = exceptionQps;
}
public void setRt(Double rt) {
this.rt = rt;
}
public void setCount(Integer count) {
this.count = count;
}
public void setResourceCode(Integer resourceCode) {
this.resourceCode = resourceCode;
}
public MetricPO(){
}
public MetricPO(Date gmtCreate, Date gmtModified, String app, Date timestamp, String resource, Long passQps, Long successQps, Long blockQps, Long exceptionQps, Double rt, Integer count, Integer resourceCode) {
this.gmtCreate = gmtCreate;
this.gmtModified = gmtModified;
this.app = app;
this.timestamp = timestamp;
this.resource = resource;
this.passQps = passQps;
this.successQps = successQps;
this.blockQps = blockQps;
this.exceptionQps = exceptionQps;
this.rt = rt;
this.count = count;
this.resourceCode = resourceCode;
}
@Override
public String toString() {
return "MetricPO{" +
"id=" + id +
", gmtCreate=" + gmtCreate +
", gmtModified=" + gmtModified +
", app='" + app + '\'' +
", timestamp=" + timestamp +
", resource='" + resource + '\'' +
", passQps=" + passQps +
", successQps=" + successQps +
", blockQps=" + blockQps +
", exceptionQps=" + exceptionQps +
", rt=" + rt +
", count=" + count +
", resourceCode=" + resourceCode +
'}';
}
}
在sentinel源码中sentinel-dashboard模块下,修改配置文件application.properties:
在sentinel源码中sentinel-dashboard模块下,repository.metric包下新建JpaMetricsRepository类,实现MetricsRepository
package com.alibaba.csp.sentinel.dashboard.repository.metric;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.jpa.MetricPO;
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;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
@Transactional
@Repository("jpaMetricsRepository")
public class JpaMetricsRepository implements MetricsRepository{
@PersistenceContext
private EntityManager em;
@Override
public void save(MetricEntity metric) {
if (metric == null || StringUtil.isBlank(metric.getApp())) {
return;
}
MetricPO metricPO = new MetricPO();
BeanUtils.copyProperties(metric, metricPO);
em.persist(metricPO);
}
@Override
public void saveAll(Iterable metrics) {
if (metrics == null) {
return;
}
metrics.forEach(this::save);
}
@Override
public List queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {
List results = new ArrayList();
if (StringUtil.isBlank(app)) {
return results;
}
if (StringUtil.isBlank(resource)) {
return results;
}
StringBuilder hql = new StringBuilder();
hql.append("FROM MetricPO");
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 metricPOs = query.getResultList();
if (CollectionUtils.isEmpty(metricPOs)) {
return results;
}
for (MetricPO metricPO : metricPOs) {
MetricEntity metricEntity = new MetricEntity();
BeanUtils.copyProperties(metricPO, metricEntity);
results.add(metricEntity);
}
return results;
}
@Override
public List listResourcesOfApp(String app) {
List results = new ArrayList<>();
if (StringUtil.isBlank(app)) {
return results;
}
StringBuilder hql = new StringBuilder();
hql.append("FROM MetricPO");
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 metricPOs = query.getResultList();
if (CollectionUtils.isEmpty(metricPOs)) {
return results;
}
List metricEntities = new ArrayList();
for (MetricPO metricPO : metricPOs) {
MetricEntity metricEntity = new MetricEntity();
BeanUtils.copyProperties(metricPO, metricEntity);
metricEntities.add(metricEntity);
}
Map 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());
}
}
在sentinel源码中sentinel-dashboard模块下,在idea中通过使用Ctrl+H在该模块下查找调用MetricsRepository接口的controller,发现只有MetricController、MetricFetcher两个类,进入到两个类中,分别找到MetricsRepository接口创建的对象代码,在@Autowired注解上面加上@Qualifier("jpaMetricsRepository")注解,也就是调用刚刚自定义的接口实现类:
通过查看根目录下的pom.xml文件中的artifactId,找到根模块名,然后在maven中找到对应模块名下的package进行打包,打包时最好将test忽略掉。
打包完成后,可以在sentinel-dashboard模块的target下找到最终打好的可启动jar包:sentinel-dashboard.jar
Sentinel 控制台是一个标准的 SpringBoot 应用,因此首先进入到部署sentinel的文件夹中,然后直接以 SpringBoot 的方式运行 jar 包即可。启动命令:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
注意:端口号不要与其它应用冲突,建议server访问为IP和端口号,生产环境不要写localhost,本地开发环境可以写成localhost。
直接将桌面启动控制台的窗口关闭即可。
Sentinel 控制台是一个标准的 SpringBoot 应用,因此首先进入到部署sentinel的文件夹中,然后直接以 SpringBoot 的方式运行 jar 包即可。生产环境一般是部署在linux上,因此在启动控制台的时候需要后台启动,后台启动命令:
nohup java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar &
在linux中执行查找进行ID命令:
ps -ef | grep sentinel
在linux中执行杀死进行ID的命令:
kill -9 进程ID号
先启动nacoss-address(地址服务器)、nacoss(配置中心)、sentinel(流量控制台),然后启动两个微服务。在浏览器中请求微服务中的接口,在sentinel控制台中可以看到对应微服务下的各种监控数据在随着接口的请求,数据在不断的增加,并且在配置的mysql数据库中,存储监控信息的表中数据量在不断的增加。因此监控数据持久化成功。
在以后移动部署修改的时候,用7z等工具打开jar包,找到application.properties配置文件,打开文件,修改配置文件中连接mysql数据库的信息,然后保存。直接将修改后的jar进行启动即可。
个人经过完善修改后的sentinel运行包下载地址如下:
sentinel-1.7.2控制台增加持久化监控数据到mysql数据库运行包:
https://download.csdn.net/download/LSY_CSDN_/12252103
sentinel-1.6.2控制台增加监控数据持久化mysql和规则数据持久化nacos部署jar包:
https://download.csdn.net/download/LSY_CSDN_/12254178
sentinel-1.6.2控制台增加监控数据持久化mysql和规则数据持久化nacos部署war包:
https://download.csdn.net/download/LSY_CSDN_/12254182