目錄
(1)改造目標:
通過使用sentinel-dashboard發現控制檯裏默認的實時流量監控數據只保留5分鐘,並且是保存在內存中,這很不合適,想要把這個控制檯應用在生產環境那麼就需要將實時監控的數據進行持久化。
因此,改造監控邏輯的目標就是將監控數據持久化。可以保存數據,以供查看歷史流量信息。
(2)源碼分析:
若需要監控數據持久化的功能,可以自行擴展實現 MetricsRepository 接口(0.2.0 版本),然後註冊成Spring Bean並在相應位置通過@Qualifier註解指定對應的bean name即可。
(3)設計表結構:
參考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;
(4)源碼增加依賴:
由於本次要將監控數據持久化到mysql數據庫中,因此在sentinel源碼中sentinel-dashboard模塊下增加依賴:
(5)源碼增加實體類:
在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 +
'}';
}
}
(6)源碼修改配置文件:
在sentinel源碼中sentinel-dashboard模塊下,修改配置文件application.properties:
(7)源碼增加接口自定義實現類:
在sentinel源碼中sentinel-dashboard模塊下,repository.metric包下新建JpaMetricsRepository類,實現MetricsRepository<MetricEntity>接口,重寫save、saveAll、queryByAppAndResourceBetween、listResourcesOfApp方法,實現保存數據時將數據保存在mysql數據庫時,讀取數據時從mysql數據庫讀取數據。
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<MetricEntity>{
@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<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 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<MetricPO> 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<String> listResourcesOfApp(String app) {
List<String> 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<MetricPO> metricPOs = query.getResultList();
if (CollectionUtils.isEmpty(metricPOs)) {
return results;
}
List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();
for (MetricPO 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());
}
}
(8)源碼修改接口調用自定義實現類:
在sentinel源碼中sentinel-dashboard模塊下,在idea中通過使用Ctrl+H在該模塊下查找調用MetricsRepository接口的controller,發現只有MetricController、MetricFetcher兩個類,進入到兩個類中,分別找到MetricsRepository接口創建的對象代碼,在@Autowired註解上面加上@Qualifier("jpaMetricsRepository")註解,也就是調用剛剛自定義的接口實現類:
(9)源碼打包:
通過查看根目錄下的pom.xml文件中的artifactId,找到根模塊名,然後在maven中找到對應模塊名下的package進行打包,打包時最好將test忽略掉。
打包完成後,可以在sentinel-dashboard模塊的target下找到最終打好的可啓動jar包:sentinel-dashboard.jar
(10)windows啓動控制檯:
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。
(11)windows關閉控制檯:
直接將桌面啓動控制檯的窗口關閉即可。
(12)linux啓動控制檯:
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 &
(13)linux關閉控制檯:
A、查找服務進程ID:
在linux中執行查找進行ID命令:
ps -ef | grep sentinel
B、殺死服務進程ID:
在linux中執行殺死進行ID的命令:
kill -9 進程ID號
(14)訪問測試:
先啓動nacoss-address(地址服務器)、nacoss(配置中心)、sentinel(流量控制檯),然後啓動兩個微服務。在瀏覽器中請求微服務中的接口,在sentinel控制檯中可以看到對應微服務下的各種監控數據在隨着接口的請求,數據在不斷的增加,並且在配置的mysql數據庫中,存儲監控信息的表中數據量在不斷的增加。因此監控數據持久化成功。
(15)移動部署修改:
在以後移動部署修改的時候,用7z等工具打開jar包,找到application.properties配置文件,打開文件,修改配置文件中連接mysql數據庫的信息,然後保存。直接將修改後的jar進行啓動即可。
(16)個人修改運行包下載地址:
個人經過完善修改後的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包: