parent
4ce2fc826d
commit
80003c44d3
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 315 KiB |
@ -0,0 +1,79 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service; |
||||||
|
|
||||||
|
import com.gitee.easyopen.annotation.Api; |
||||||
|
import com.gitee.easyopen.annotation.ApiService; |
||||||
|
import com.gitee.fastmybatis.core.query.Query; |
||||||
|
import com.gitee.fastmybatis.core.query.Sort; |
||||||
|
import com.gitee.fastmybatis.core.support.PageEasyui; |
||||||
|
import com.gitee.fastmybatis.core.util.MapperUtil; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.InstanceMonitorSearchParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorErrorMsgParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorInfoErrorSolveParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorSearchParam; |
||||||
|
import com.gitee.sop.adminserver.bean.RouteErrorCount; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfoError; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorSummary; |
||||||
|
import com.gitee.sop.adminserver.mapper.MonitorInfoErrorMapper; |
||||||
|
import com.gitee.sop.adminserver.mapper.MonitorInfoMapper; |
||||||
|
import com.github.pagehelper.PageHelper; |
||||||
|
import com.github.pagehelper.PageInfo; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@ApiService |
||||||
|
public class MonitorNewApi { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoMapper monitorInfoMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoErrorMapper monitorInfoErrorMapper; |
||||||
|
|
||||||
|
@Api(name = "monitornew.data.page") |
||||||
|
PageInfo<Object> listMonitor(MonitorSearchParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
query.orderby("errorCount", Sort.DESC) |
||||||
|
.orderby("avgTime", Sort.DESC); |
||||||
|
return PageHelper.offsetPage(query.getStart(), query.getLimit()) |
||||||
|
.doSelectPage(() -> monitorInfoMapper.listMonitorSummary(query)) |
||||||
|
.toPageInfo(); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.routeid.data.get") |
||||||
|
List<MonitorSummary> listInstanceMonitor(InstanceMonitorSearchParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
query.orderby("errorCount", Sort.DESC) |
||||||
|
.orderby("avgTime", Sort.DESC); |
||||||
|
return monitorInfoMapper.listInstanceMonitorInfo(query); |
||||||
|
} |
||||||
|
|
||||||
|
private Map<String, Integer> getRouteErrorCount() { |
||||||
|
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCount(); |
||||||
|
return routeErrorCounts.stream() |
||||||
|
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.error.page") |
||||||
|
PageEasyui<MonitorInfoError> listError(MonitorErrorMsgParam param) { |
||||||
|
Query query = param.toQuery() |
||||||
|
.orderby("gmt_modified", Sort.DESC); |
||||||
|
return MapperUtil.queryForEasyuiDatagrid(monitorInfoErrorMapper, query); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.error.solve") |
||||||
|
void solve(MonitorInfoErrorSolveParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
Map<String, Object> set = new HashMap<>(4); |
||||||
|
set.put("is_deleted", 1); |
||||||
|
set.put("count", 0); |
||||||
|
monitorInfoErrorMapper.updateByMap(set, query); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class InstanceMonitorSearchParam { |
||||||
|
@Condition(column = "t.route_id") |
||||||
|
private String routeId; |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import com.gitee.fastmybatis.core.query.param.PageParam; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class MonitorErrorMsgParam extends PageParam { |
||||||
|
@NotBlank(message = "routeId不能为空") |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
@Condition(ignoreEmptyString = true) |
||||||
|
private String instanceId; |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorInfoErrorSolveParam { |
||||||
|
/** 错误id,md5(error_msg), 数据库字段:error_id */ |
||||||
|
@NotBlank |
||||||
|
@Condition(index = 1) |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField; |
||||||
|
import com.gitee.fastmybatis.core.query.Operator; |
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import com.gitee.fastmybatis.core.query.param.PageParam; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class MonitorSearchParam extends PageParam { |
||||||
|
@ApiDocField(description = "服务名serviceId") |
||||||
|
@Condition(column = "service_id", operator = Operator.like, ignoreEmptyString = true) |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
@ApiDocField(description = "路由id") |
||||||
|
@Condition(column = "route_id", operator = Operator.like, ignoreEmptyString = true) |
||||||
|
private String routeId; |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.result; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorInfoErrorMsgResult { |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
|
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 错误信息, 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
private Integer count; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.gitee.sop.adminserver.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class RouteErrorCount { |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
private Integer count; |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
package com.gitee.sop.adminserver.common; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.BeanUtils; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.function.Consumer; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class CopyUtil { |
||||||
|
|
||||||
|
public static void copyProperties(Object from, Object to) { |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier) { |
||||||
|
Objects.requireNonNull(from); |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) { |
||||||
|
if (from == null) { |
||||||
|
return supplier.get(); |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) { |
||||||
|
if (from == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
after.accept(to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
after.accept(target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info |
||||||
|
* 备注:接口监控信息 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info") |
||||||
|
@Data |
||||||
|
public class MonitorInfo { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 路由id, 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 接口名, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
/** 版本号, 数据库字段:version */ |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 数据库字段:service_id */ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** 总时长,毫秒, 数据库字段:total_time */ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info_error |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info_error") |
||||||
|
@Data |
||||||
|
public class MonitorInfoError { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 实例id, 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
/** http status,非200错误, 数据库字段:error_status */ |
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
/** 错误次数, 数据库字段:count */ |
||||||
|
private Integer count; |
||||||
|
|
||||||
|
/** 数据库字段:is_deleted */ |
||||||
|
@com.gitee.fastmybatis.core.annotation.LogicDelete |
||||||
|
private Byte isDeleted; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorSummary { |
||||||
|
|
||||||
|
private static final AtomicInteger i = new AtomicInteger(); |
||||||
|
|
||||||
|
private Integer id = i.incrementAndGet(); |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
|
||||||
|
private String name; |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 数据库字段:service_id */ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** 总时长,毫秒, 数据库字段:total_time */ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** 未解决的错误数量 */ |
||||||
|
private Long unsolvedErrorCount; |
||||||
|
|
||||||
|
private Float avgTime; |
||||||
|
|
||||||
|
private Boolean hasChildren; |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.sop.adminserver.bean.RouteErrorCount; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfoError; |
||||||
|
import org.apache.ibatis.annotations.Select; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> { |
||||||
|
|
||||||
|
|
||||||
|
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + |
||||||
|
"WHERE is_deleted=0 \n" + |
||||||
|
"GROUP BY route_id") |
||||||
|
List<RouteErrorCount> listRouteErrorCount(); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.fastmybatis.core.query.Query; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfo; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorSummary; |
||||||
|
import org.apache.ibatis.annotations.Param; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> { |
||||||
|
List<MonitorSummary> listMonitorSummary(@Param("query") Query query); |
||||||
|
|
||||||
|
List<MonitorSummary> listInstanceMonitorInfo(@Param("query") Query query); |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
<?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"> |
||||||
|
<!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> |
||||||
|
<mapper namespace="com.gitee.sop.adminserver.mapper.MonitorInfoMapper"> |
||||||
|
|
||||||
|
|
||||||
|
<select id="listMonitorSummary" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> |
||||||
|
SELECT t.*, IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
t.`service_id` serviceId, |
||||||
|
CONCAT(t.name, t.version) routeId, |
||||||
|
t.`name` name, |
||||||
|
t.`version` version, |
||||||
|
SUM(t.`max_time`) maxTime, |
||||||
|
SUM(t.`min_time`) minTime, |
||||||
|
SUM(t.`total_time`) totalTime, |
||||||
|
SUM(t.`total_request_count`) totalRequestCount, |
||||||
|
SUM(t.`success_count`) successCount, |
||||||
|
SUM(t.`error_count`) errorCount, |
||||||
|
SUM(t.`total_time`)/SUM(t.`total_request_count`) avgTime, |
||||||
|
1 hasChildren |
||||||
|
FROM |
||||||
|
`monitor_info` t |
||||||
|
<include refid="common.where"/> |
||||||
|
GROUP BY t.service_id, t.name, t.version |
||||||
|
<include refid="common.orderBy"/> |
||||||
|
) t |
||||||
|
LEFT JOIN |
||||||
|
( |
||||||
|
SELECT route_id, count(*) unsolvedErrorCount |
||||||
|
FROM monitor_info_error WHERE is_deleted=0 GROUP BY route_id |
||||||
|
) t2 on t.routeId = t2.route_id |
||||||
|
</select> |
||||||
|
|
||||||
|
<select id="listInstanceMonitorInfo" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> |
||||||
|
SELECT |
||||||
|
t.`service_id` serviceId, |
||||||
|
t.`instance_id` instanceId, |
||||||
|
t.route_id routeId, |
||||||
|
t.`name` name, |
||||||
|
t.`version` version, |
||||||
|
t.`max_time` maxTime, |
||||||
|
t.`min_time` minTime, |
||||||
|
t.`total_time` totalTime, |
||||||
|
t.`total_request_count` totalRequestCount, |
||||||
|
t.`success_count` successCount, |
||||||
|
t.`error_count` errorCount, |
||||||
|
IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount, |
||||||
|
t.`total_time`/t.`total_request_count` avgTime, |
||||||
|
0 hasChildren |
||||||
|
FROM |
||||||
|
`monitor_info` t |
||||||
|
LEFT JOIN |
||||||
|
( |
||||||
|
SELECT instance_id, route_id, count(*) unsolvedErrorCount |
||||||
|
FROM monitor_info_error WHERE is_deleted=0 GROUP BY instance_id, route_id |
||||||
|
) t2 on t.route_id = t2.route_id AND t.instance_id = t2.instance_id |
||||||
|
<include refid="common.where" /> |
||||||
|
<include refid="common.orderBy" /> |
||||||
|
</select> |
||||||
|
|
||||||
|
</mapper> |
@ -0,0 +1,10 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> |
||||||
|
<configuration> |
||||||
|
|
||||||
|
<plugins> |
||||||
|
<!-- com.github.pagehelper为PageHelper类所在包名 --> |
||||||
|
<plugin interceptor="com.github.pagehelper.PageInterceptor" /> |
||||||
|
</plugins> |
||||||
|
|
||||||
|
</configuration> |
@ -0,0 +1,273 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container"> |
||||||
|
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini" @submit.native.prevent> |
||||||
|
<el-form-item label="接口名"> |
||||||
|
<el-input v-model="searchFormData.routeId" :clearable="true" style="width: 250px;" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="serviceId"> |
||||||
|
<el-input v-model="searchFormData.serviceId" :clearable="true" style="width: 250px;" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="el-icon-search" native-type="submit" @click="loadTable">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<el-table |
||||||
|
:data="pageInfo.list" |
||||||
|
row-key="id" |
||||||
|
lazy |
||||||
|
empty-text="无数据" |
||||||
|
:load="loadInstanceMonitorInfo" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
fixed |
||||||
|
prop="instanceId" |
||||||
|
label="网关实例" |
||||||
|
width="200" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<span v-if="!scope.row.children">{{ scope.row.instanceId }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
fixed |
||||||
|
prop="name" |
||||||
|
label="接口名 (版本号)" |
||||||
|
width="200" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="serviceId" |
||||||
|
label="serviceId" |
||||||
|
width="150" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="maxTime" |
||||||
|
label="最大耗时(ms)" |
||||||
|
> |
||||||
|
<template slot="header"> |
||||||
|
最大耗时(ms) |
||||||
|
<el-tooltip content="耗时计算:签名验证成功后开始,应用返回结果后结束" placement="top"> |
||||||
|
<i class="el-icon-question" style="cursor: pointer"></i> |
||||||
|
</el-tooltip> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="minTime" |
||||||
|
label="最小耗时(ms)" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="avgTime" |
||||||
|
label="平均耗时(ms)" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
{{ scope.row.avgTime.toFixed(1) }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="totalRequestCount" |
||||||
|
label="总调用次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="successCount" |
||||||
|
label="成功次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="errorCount" |
||||||
|
label="失败次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="unsolvedErrorCount" |
||||||
|
label="未解决错误" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-link |
||||||
|
v-if="scope.row.unsolvedErrorCount > 0" |
||||||
|
:underline="false" |
||||||
|
type="danger" |
||||||
|
style="text-decoration: underline;" |
||||||
|
@click="onShowErrorDetail(scope.row)" |
||||||
|
> |
||||||
|
{{ scope.row.unsolvedErrorCount }} |
||||||
|
</el-link> |
||||||
|
<span v-if="scope.row.unsolvedErrorCount === 0">0</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-pagination |
||||||
|
background |
||||||
|
style="margin-top: 5px" |
||||||
|
:current-page="searchFormData.pageIndex" |
||||||
|
:page-size="searchFormData.pageSize" |
||||||
|
:page-sizes="[5, 10, 20, 40]" |
||||||
|
:total="pageInfo.total" |
||||||
|
layout="total, sizes, prev, pager, next" |
||||||
|
@size-change="onSizeChange" |
||||||
|
@current-change="onPageIndexChange" |
||||||
|
/> |
||||||
|
<!-- dialog --> |
||||||
|
<el-dialog |
||||||
|
:title="errorMsgData.title" |
||||||
|
:visible.sync="logDetailVisible" |
||||||
|
:close-on-click-modal="false" |
||||||
|
width="70%" |
||||||
|
@close="onCloseErrorDlg" |
||||||
|
> |
||||||
|
<el-alert |
||||||
|
title="修复错误后请标记解决" |
||||||
|
:closable="false" |
||||||
|
class="el-alert-tip" |
||||||
|
/> |
||||||
|
<el-table |
||||||
|
:data="errorMsgData.pageInfo.rows" |
||||||
|
empty-text="无错误日志" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
type="expand" |
||||||
|
> |
||||||
|
<template slot-scope="props"> |
||||||
|
<el-input v-model="props.row.errorMsg" type="textarea" :rows="8" readonly /> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="errorMsg" |
||||||
|
label="错误内容" |
||||||
|
> |
||||||
|
<template slot-scope="props"> |
||||||
|
<span v-if="props.row.errorMsg.length > 50">{{ props.row.errorMsg.substring(0, 50) }}...</span> |
||||||
|
<span v-else>{{ props.row.errorMsg }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="instanceId" |
||||||
|
label="实例ID" |
||||||
|
width="150px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="count" |
||||||
|
label="报错次数" |
||||||
|
width="80px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="gmtModified" |
||||||
|
label="报错时间" |
||||||
|
width="160px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
label="操作" |
||||||
|
width="120" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-link type="primary" @click="onSolve(scope.row)">标记解决</el-link> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-pagination |
||||||
|
background |
||||||
|
style="margin-top: 5px" |
||||||
|
:current-page="errorMsgFormData.pageIndex" |
||||||
|
:page-size="errorMsgFormData.pageSize" |
||||||
|
:page-sizes="[5, 10, 20, 40]" |
||||||
|
:total="errorMsgData.pageInfo.total" |
||||||
|
layout="total, sizes, prev, pager, next" |
||||||
|
@size-change="onSizeChange" |
||||||
|
@current-change="onPageIndexChange" |
||||||
|
/> |
||||||
|
<div slot="footer" class="dialog-footer"> |
||||||
|
<el-button type="primary" @click="logDetailVisible = false">关 闭</el-button> |
||||||
|
</div> |
||||||
|
</el-dialog> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
data() { |
||||||
|
return { |
||||||
|
searchFormData: { |
||||||
|
routeId: '', |
||||||
|
serviceId: '', |
||||||
|
pageIndex: 1, |
||||||
|
pageSize: 20 |
||||||
|
}, |
||||||
|
pageInfo: { |
||||||
|
list: [], |
||||||
|
total: 0 |
||||||
|
}, |
||||||
|
logDetailVisible: false, |
||||||
|
errorMsgFormData: { |
||||||
|
routeId: '', |
||||||
|
instanceId: '', |
||||||
|
pageIndex: 1, |
||||||
|
pageSize: 5 |
||||||
|
}, |
||||||
|
errorMsgData: { |
||||||
|
title: '', |
||||||
|
name: '', |
||||||
|
version: '', |
||||||
|
pageInfo: { |
||||||
|
rows: [], |
||||||
|
total: 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
loadTable: function() { |
||||||
|
this.post('monitornew.data.page', this.searchFormData, function(resp) { |
||||||
|
this.pageInfo = resp.data |
||||||
|
}) |
||||||
|
}, |
||||||
|
loadErrorData: function() { |
||||||
|
this.post('monitornew.error.page', this.errorMsgFormData, function(resp) { |
||||||
|
this.errorMsgData.pageInfo = resp.data |
||||||
|
this.logDetailVisible = true |
||||||
|
}) |
||||||
|
}, |
||||||
|
loadInstanceMonitorInfo(row, treeNode, resolve) { |
||||||
|
this.post('monitornew.routeid.data.get', { routeId: row.routeId }, resp => { |
||||||
|
const children = resp.data |
||||||
|
row.children = children |
||||||
|
resolve(children) |
||||||
|
}) |
||||||
|
}, |
||||||
|
onShowErrorDetail: function(row) { |
||||||
|
this.errorMsgData.title = `错误日志 ${row.name}(${row.version})` |
||||||
|
this.errorMsgData.name = row.name |
||||||
|
this.errorMsgData.version = row.version |
||||||
|
this.errorMsgFormData.routeId = row.routeId |
||||||
|
this.errorMsgFormData.instanceId = row.instanceId |
||||||
|
this.loadErrorData() |
||||||
|
}, |
||||||
|
onSolve: function(row) { |
||||||
|
this.confirm('确认标记为已解决吗?', function(done) { |
||||||
|
this.post('monitornew.error.solve', { routeId: row.routeId, errorId: row.errorId }, function(resp) { |
||||||
|
done() |
||||||
|
this.loadErrorData() |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
onCloseErrorDlg: function() { |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onSizeChange: function(size) { |
||||||
|
this.searchFormData.pageSize = size |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onAdd: function() { |
||||||
|
this.dialogTitle = '新增IP' |
||||||
|
this.dialogVisible = true |
||||||
|
this.dialogFormData.id = 0 |
||||||
|
}, |
||||||
|
onPageIndexChange: function(pageIndex) { |
||||||
|
this.searchFormData.pageIndex = pageIndex |
||||||
|
this.loadTable() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,43 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* LRU缓存 |
||||||
|
* LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素) |
||||||
|
* 由于最近最少使用元素是 LinkedHashMap 内部处理 |
||||||
|
* 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储 |
||||||
|
* created by Ethan-Walker on 2019/2/16 |
||||||
|
*/ |
||||||
|
public class LRUCache<K, V> { |
||||||
|
private final Map<K, V> map; |
||||||
|
|
||||||
|
public LRUCache(int capacity) { |
||||||
|
map = new LinkedHashMap<K, V>(capacity, 0.75f, true) { |
||||||
|
@Override |
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) { |
||||||
|
// 容量大于capacity 时就删除
|
||||||
|
return size() > capacity; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
public V get(K key) { |
||||||
|
return map.get(key); |
||||||
|
} |
||||||
|
|
||||||
|
public V computeIfAbsent(K key, |
||||||
|
Function<? super K, ? extends V> mappingFunction) { |
||||||
|
return map.computeIfAbsent(key, mappingFunction); |
||||||
|
} |
||||||
|
|
||||||
|
public V put(K key, V value) { |
||||||
|
return map.put(key, value); |
||||||
|
} |
||||||
|
|
||||||
|
public Collection<V> values() { |
||||||
|
return map.values(); |
||||||
|
} |
||||||
|
} |
@ -1,96 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.interceptor; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
|
||||||
import com.gitee.sop.gatewaycommon.monitor.MonitorInfo; |
|
||||||
import com.gitee.sop.gatewaycommon.monitor.MonitorManager; |
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.concurrent.LinkedBlockingQueue; |
|
||||||
import java.util.concurrent.ThreadPoolExecutor; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.concurrent.atomic.AtomicLong; |
|
||||||
|
|
||||||
/** |
|
||||||
* SOP默认的拦截器,用于收集监控数据 |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class MonitorRouteInterceptor implements RouteInterceptor { |
|
||||||
|
|
||||||
private ThreadPoolExecutor threadPoolExecutor; |
|
||||||
|
|
||||||
public MonitorRouteInterceptor(int threadPoolSize) { |
|
||||||
threadPoolExecutor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize, |
|
||||||
0L, TimeUnit.MILLISECONDS, |
|
||||||
new LinkedBlockingQueue<>()); |
|
||||||
} |
|
||||||
|
|
||||||
public MonitorRouteInterceptor() { |
|
||||||
this(4); |
|
||||||
} |
|
||||||
|
|
||||||
public MonitorRouteInterceptor(ThreadPoolExecutor threadPoolExecutor) { |
|
||||||
this.threadPoolExecutor = threadPoolExecutor; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void preRoute(RouteInterceptorContext context) { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void afterRoute(RouteInterceptorContext context) { |
|
||||||
threadPoolExecutor.execute(() -> this.storeRequestInfo(context)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. |
|
||||||
* 需要考虑并发情况。 |
|
||||||
*/ |
|
||||||
protected void storeRequestInfo(RouteInterceptorContext context) { |
|
||||||
MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); |
|
||||||
ApiParam apiParam = context.getApiParam(); |
|
||||||
String routeId = apiParam.fetchNameVersion(); |
|
||||||
long spendTime = context.getFinishTimeMillis() - context.getBeginTimeMillis(); |
|
||||||
// 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent
|
|
||||||
MonitorInfo monitorInfo = monitorManager.getMonitorInfo(routeId, (k) -> this.createMonitorInfo(apiParam)); |
|
||||||
|
|
||||||
monitorInfo.storeMaxTime(spendTime); |
|
||||||
monitorInfo.storeMinTime(spendTime); |
|
||||||
monitorInfo.getTotalCount().incrementAndGet(); |
|
||||||
monitorInfo.getTotalTime().addAndGet(spendTime); |
|
||||||
monitorInfo.getTotalRequestDataSize().addAndGet(context.getRequestDataSize()); |
|
||||||
monitorInfo.getTotalResponseDataSize().addAndGet(context.getResponseDataSize()); |
|
||||||
if (context.isSuccessRequest()) { |
|
||||||
monitorInfo.getSuccessCount().incrementAndGet(); |
|
||||||
} else { |
|
||||||
monitorInfo.getErrorCount().incrementAndGet(); |
|
||||||
String errorMsg = context.getServiceErrorMsg(); |
|
||||||
monitorInfo.addErrorMsg(errorMsg); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private MonitorInfo createMonitorInfo(ApiParam apiParam) { |
|
||||||
MonitorInfo monitorInfo = new MonitorInfo(); |
|
||||||
monitorInfo.setName(apiParam.fetchName()); |
|
||||||
monitorInfo.setVersion(apiParam.fetchVersion()); |
|
||||||
monitorInfo.setServiceId(apiParam.fetchServiceId()); |
|
||||||
monitorInfo.setTotalRequestDataSize(new AtomicLong()); |
|
||||||
monitorInfo.setTotalResponseDataSize(new AtomicLong()); |
|
||||||
monitorInfo.setTotalTime(new AtomicLong()); |
|
||||||
monitorInfo.setMaxTime(0L); |
|
||||||
monitorInfo.setMinTime(0L); |
|
||||||
monitorInfo.setSuccessCount(new AtomicLong()); |
|
||||||
monitorInfo.setTotalCount(new AtomicLong()); |
|
||||||
monitorInfo.setErrorCount(new AtomicLong()); |
|
||||||
monitorInfo.setErrorMsgList(new ArrayList<>(10)); |
|
||||||
return monitorInfo; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public int getOrder() { |
|
||||||
return -1000; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,63 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.monitor; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
/** |
||||||
|
* 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorDTO { |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由id |
||||||
|
*/ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 接口名 |
||||||
|
*/ |
||||||
|
private String name; |
||||||
|
/** |
||||||
|
* 版本号 |
||||||
|
*/ |
||||||
|
private String version; |
||||||
|
/** |
||||||
|
* serviceId |
||||||
|
*/ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 实例id |
||||||
|
*/ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** |
||||||
|
* 总时长 |
||||||
|
*/ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** |
||||||
|
* 错误信息 |
||||||
|
*/ |
||||||
|
private Collection<MonitorErrorMsg> errorMsgList; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.monitor; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.LRUCache; |
||||||
|
import lombok.Data; |
||||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||||
|
|
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorData { |
||||||
|
|
||||||
|
public static final int LIMIT_SIZE = 50; |
||||||
|
|
||||||
|
|
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 接口名 |
||||||
|
*/ |
||||||
|
private String name; |
||||||
|
/** |
||||||
|
* 版本号 |
||||||
|
*/ |
||||||
|
private String version; |
||||||
|
/** |
||||||
|
* serviceId |
||||||
|
*/ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 请求耗时最长时间 |
||||||
|
*/ |
||||||
|
private Integer maxTime; |
||||||
|
/** |
||||||
|
* 请求耗时最小时间 |
||||||
|
*/ |
||||||
|
private Integer minTime; |
||||||
|
/** |
||||||
|
* 总时长 |
||||||
|
*/ |
||||||
|
private AtomicInteger totalTime; |
||||||
|
/** |
||||||
|
* 总调用次数 |
||||||
|
*/ |
||||||
|
private AtomicInteger totalRequestCount; |
||||||
|
/** |
||||||
|
* 成功次数 |
||||||
|
*/ |
||||||
|
private AtomicInteger successCount; |
||||||
|
/** |
||||||
|
* 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) |
||||||
|
*/ |
||||||
|
private AtomicInteger errorCount; |
||||||
|
|
||||||
|
/** |
||||||
|
* 错误信息,key: errorId |
||||||
|
*/ |
||||||
|
private LRUCache<String, MonitorErrorMsg> monitorErrorMsgMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* 下一次刷新到数据库的时间 |
||||||
|
*/ |
||||||
|
private LocalDateTime flushTime; |
||||||
|
|
||||||
|
public synchronized void storeMaxTime(int spendTime) { |
||||||
|
if (spendTime > maxTime) { |
||||||
|
maxTime = spendTime; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void storeMinTime(int spendTime) { |
||||||
|
if (minTime == 0 || spendTime < minTime) { |
||||||
|
minTime = spendTime; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addErrorMsg(String errorMsg, int httpStatus) { |
||||||
|
if (errorMsg == null || "".equals(errorMsg)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
synchronized (this) { |
||||||
|
String errorId = DigestUtils.md5Hex(instanceId + routeId + errorMsg); |
||||||
|
MonitorErrorMsg monitorErrorMsg = monitorErrorMsgMap.computeIfAbsent(errorId, (k) -> { |
||||||
|
MonitorErrorMsg value = new MonitorErrorMsg(); |
||||||
|
value.setErrorId(errorId); |
||||||
|
value.setInstanceId(instanceId); |
||||||
|
value.setRouteId(routeId); |
||||||
|
value.setErrorMsg(errorMsg); |
||||||
|
value.setErrorStatus(httpStatus); |
||||||
|
value.setCount(0); |
||||||
|
return value; |
||||||
|
}); |
||||||
|
monitorErrorMsg.setCount(monitorErrorMsg.getCount() + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.monitor; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author thc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorErrorMsg { |
||||||
|
/** 错误id,md5(error_msg), 数据库字段:error_id */ |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 实例id, 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 数据库字段:isp_id */ |
||||||
|
private Long ispId; |
||||||
|
|
||||||
|
/** 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
/** http status,非200错误 */ |
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
/** 错误次数, 数据库字段:count */ |
||||||
|
private Integer count; |
||||||
|
} |
@ -1,88 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.monitor; |
|
||||||
|
|
||||||
import lombok.Data; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.concurrent.atomic.AtomicLong; |
|
||||||
|
|
||||||
/** |
|
||||||
* 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Data |
|
||||||
public class MonitorInfo { |
|
||||||
|
|
||||||
/** |
|
||||||
* 接口名 |
|
||||||
*/ |
|
||||||
private String name; |
|
||||||
/** |
|
||||||
* 版本号 |
|
||||||
*/ |
|
||||||
private String version; |
|
||||||
/** |
|
||||||
* serviceId |
|
||||||
*/ |
|
||||||
private String serviceId; |
|
||||||
/** |
|
||||||
* 总请求数据量 |
|
||||||
*/ |
|
||||||
private AtomicLong totalRequestDataSize; |
|
||||||
/** |
|
||||||
* 总返回数据量 |
|
||||||
*/ |
|
||||||
private AtomicLong totalResponseDataSize; |
|
||||||
/** |
|
||||||
* 请求耗时最长时间 |
|
||||||
*/ |
|
||||||
private Long maxTime; |
|
||||||
/** |
|
||||||
* 请求耗时最小时间 |
|
||||||
*/ |
|
||||||
private Long minTime; |
|
||||||
/** |
|
||||||
* 总时长 |
|
||||||
*/ |
|
||||||
private AtomicLong totalTime; |
|
||||||
/** |
|
||||||
* 总调用次数 |
|
||||||
*/ |
|
||||||
private AtomicLong totalCount; |
|
||||||
/** |
|
||||||
* 成功次数 |
|
||||||
*/ |
|
||||||
private AtomicLong successCount; |
|
||||||
/** |
|
||||||
* 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) |
|
||||||
*/ |
|
||||||
private AtomicLong errorCount; |
|
||||||
/** |
|
||||||
* 错误信息 |
|
||||||
*/ |
|
||||||
private List<String> errorMsgList; |
|
||||||
|
|
||||||
public synchronized void storeMaxTime(long spendTime) { |
|
||||||
if (spendTime > maxTime) { |
|
||||||
maxTime = spendTime; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public synchronized void storeMinTime(long spendTime) { |
|
||||||
if (minTime == 0 || spendTime < minTime) { |
|
||||||
minTime = spendTime; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void addErrorMsg(String errorMsg) { |
|
||||||
if (errorMsg == null || "".equals(errorMsg)) { |
|
||||||
return; |
|
||||||
} |
|
||||||
synchronized (this) { |
|
||||||
if (errorMsgList != null && errorMsgList.size() < 10) { |
|
||||||
errorMsgList.add(errorMsg); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,13 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.monitor; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class RouteErrorCount { |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
private Integer count; |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.sync; |
||||||
|
|
||||||
|
import java.util.concurrent.ThreadFactory; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author thc |
||||||
|
*/ |
||||||
|
public class MyNamedThreadFactory implements ThreadFactory { |
||||||
|
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); |
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1); |
||||||
|
private final ThreadGroup group; |
||||||
|
private final String namePrefix; |
||||||
|
|
||||||
|
public MyNamedThreadFactory(String name) { |
||||||
|
|
||||||
|
SecurityManager s = System.getSecurityManager(); |
||||||
|
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); |
||||||
|
if (null == name || name.isEmpty()) { |
||||||
|
name = "pool"; |
||||||
|
} |
||||||
|
|
||||||
|
namePrefix = name + "-" + POOL_NUMBER.getAndIncrement() + "-thread-"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Thread newThread(Runnable r) { |
||||||
|
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); |
||||||
|
if (t.isDaemon()) { |
||||||
|
t.setDaemon(false); |
||||||
|
} |
||||||
|
if (t.getPriority() != Thread.NORM_PRIORITY) { |
||||||
|
t.setPriority(Thread.NORM_PRIORITY); |
||||||
|
} |
||||||
|
return t; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.sync; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; |
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.concurrent.Executor; |
||||||
|
import java.util.concurrent.LinkedBlockingQueue; |
||||||
|
import java.util.concurrent.ThreadPoolExecutor; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* 异步执行配置 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class SopAsyncConfigurer implements AsyncConfigurer { |
||||||
|
|
||||||
|
private final ThreadPoolExecutor threadPoolExecutor; |
||||||
|
|
||||||
|
public SopAsyncConfigurer(String threadName, int poolSize) { |
||||||
|
threadPoolExecutor = new ThreadPoolExecutor(poolSize, poolSize, |
||||||
|
0L, TimeUnit.MILLISECONDS, |
||||||
|
new LinkedBlockingQueue<>(), new MyNamedThreadFactory(threadName)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Executor getAsyncExecutor() { |
||||||
|
return threadPoolExecutor; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { |
||||||
|
return (Throwable e, Method method, Object... args) -> { |
||||||
|
log.error("异步运行方法出错, method:{}, args:{}, message:{}", method, Arrays.deepToString(args), e.getMessage(), e); |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.util; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.BeanUtils; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.function.Consumer; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class CopyUtil { |
||||||
|
|
||||||
|
public static void copyPropertiesIgnoreNull(Object from, Object to) { |
||||||
|
MyBeanUtil.copyPropertiesIgnoreNull(from, to); |
||||||
|
} |
||||||
|
|
||||||
|
public static void copyProperties(Object from, Object to) { |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier) { |
||||||
|
Objects.requireNonNull(from); |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) { |
||||||
|
if (from == null) { |
||||||
|
return supplier.get(); |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) { |
||||||
|
if (from == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
after.accept(to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
after.accept(target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
package com.gitee.sop.gateway.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info |
||||||
|
* 备注:接口监控信息 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info") |
||||||
|
@Data |
||||||
|
public class MonitorInfo { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 路由id, 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 接口名, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
/** 版本号, 数据库字段:version */ |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 数据库字段:service_id */ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** 总时长,毫秒, 数据库字段:total_time */ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
package com.gitee.sop.gateway.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info_error |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info_error") |
||||||
|
@Data |
||||||
|
public class MonitorInfoError { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 实例id, 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
/** http status,非200错误, 数据库字段:error_status */ |
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
/** 错误次数, 数据库字段:count */ |
||||||
|
private Integer count; |
||||||
|
|
||||||
|
/** 数据库字段:is_deleted */ |
||||||
|
@com.gitee.fastmybatis.core.annotation.LogicDelete |
||||||
|
private Byte isDeleted; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package com.gitee.sop.gateway.interceptor; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; |
||||||
|
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于收集监控数据 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
@Slf4j |
||||||
|
public class MonitorRouteInterceptor implements RouteInterceptor { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
SopAsyncConfigurer sopAsyncConfigurer; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
MonitorRouteInterceptorService monitorRouteInterceptorService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void preRoute(RouteInterceptorContext context) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void afterRoute(RouteInterceptorContext context) { |
||||||
|
sopAsyncConfigurer.getAsyncExecutor().execute(()-> { |
||||||
|
monitorRouteInterceptorService.storeRequestInfo(context); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getOrder() { |
||||||
|
return -1000; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,164 @@ |
|||||||
|
package com.gitee.sop.gateway.interceptor; |
||||||
|
|
||||||
|
import com.gitee.sop.gateway.mapper.DbMonitorInfoManager; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.LRUCache; |
||||||
|
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorData; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorManager; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.cloud.client.ServiceInstance; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Random; |
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
@Slf4j |
||||||
|
public class MonitorRouteInterceptorService { |
||||||
|
|
||||||
|
/** |
||||||
|
* 刷新到数据库时间间隔,秒 |
||||||
|
*/ |
||||||
|
@Value("${sop.monitor.flush-period-seconds:30}") |
||||||
|
int flushPeriodSeconds; |
||||||
|
|
||||||
|
/** |
||||||
|
* 定时任务每n秒,执行一次 |
||||||
|
*/ |
||||||
|
@Value("${sop.monitor.schedule-period-seconds:30}") |
||||||
|
int schedulePeriodSeconds; |
||||||
|
|
||||||
|
/** |
||||||
|
* 错误数量容量 |
||||||
|
*/ |
||||||
|
@Value("${sop.monitor.error-count-capacity:50}") |
||||||
|
int monitorErrorCapacity; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
DbMonitorInfoManager dbMonitorInfoManager; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
MonitorManager monitorManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. |
||||||
|
* 需要考虑并发情况。 |
||||||
|
*/ |
||||||
|
public synchronized void storeRequestInfo(RouteInterceptorContext context) { |
||||||
|
ApiParam apiParam = context.getApiParam(); |
||||||
|
ServiceInstance serviceInstance = context.getServiceInstance(); |
||||||
|
String routeId = apiParam.getRouteId(); |
||||||
|
int spendTime = (int)(context.getFinishTimeMillis() - context.getBeginTimeMillis()); |
||||||
|
// 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent
|
||||||
|
String key = getMonitorKey(routeId, serviceInstance.getInstanceId()); |
||||||
|
MonitorData monitorData = monitorManager.getMonitorInfo(key, (k) -> this.createMonitorInfo(apiParam, serviceInstance)); |
||||||
|
monitorData.storeMaxTime(spendTime); |
||||||
|
monitorData.storeMinTime(spendTime); |
||||||
|
monitorData.getTotalRequestCount().incrementAndGet(); |
||||||
|
monitorData.getTotalTime().addAndGet(spendTime); |
||||||
|
if (context.isSuccessRequest()) { |
||||||
|
monitorData.getSuccessCount().incrementAndGet(); |
||||||
|
} else { |
||||||
|
monitorData.getErrorCount().incrementAndGet(); |
||||||
|
String errorMsg = context.getServiceErrorMsg(); |
||||||
|
monitorData.addErrorMsg(errorMsg, context.getResponseStatus()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String getMonitorKey(String routeId, String instanceId) { |
||||||
|
return routeId + instanceId; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 刷新到数据库 |
||||||
|
*/ |
||||||
|
private synchronized void flushDb() { |
||||||
|
Map<String, MonitorData> monitorData = monitorManager.getMonitorData(); |
||||||
|
if (monitorData.isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
LocalDateTime checkTime = LocalDateTime.now(); |
||||||
|
List<String> tobeRemoveKeys = new ArrayList<>(); |
||||||
|
List<MonitorDTO> tobeSaveBatch = new ArrayList<>(monitorData.size()); |
||||||
|
monitorData.forEach((key, value) -> { |
||||||
|
LocalDateTime flushTime = value.getFlushTime(); |
||||||
|
if (flushTime.isEqual(checkTime) || flushTime.isBefore(checkTime)) { |
||||||
|
log.debug("刷新监控数据到数据库, MonitorData:{}", value); |
||||||
|
tobeRemoveKeys.add(key); |
||||||
|
MonitorDTO monitorDTO = getMonitorDTO(value); |
||||||
|
tobeSaveBatch.add(monitorDTO); |
||||||
|
} |
||||||
|
}); |
||||||
|
dbMonitorInfoManager.saveMonitorInfoBatch(tobeSaveBatch); |
||||||
|
|
||||||
|
for (String key : tobeRemoveKeys) { |
||||||
|
monitorData.remove(key); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private MonitorDTO getMonitorDTO(MonitorData monitorData) { |
||||||
|
MonitorDTO monitorDTO = new MonitorDTO(); |
||||||
|
monitorDTO.setRouteId(monitorData.getRouteId()); |
||||||
|
monitorDTO.setName(monitorData.getName()); |
||||||
|
monitorDTO.setVersion(monitorData.getVersion()); |
||||||
|
monitorDTO.setServiceId(monitorData.getServiceId()); |
||||||
|
monitorDTO.setInstanceId(monitorData.getInstanceId()); |
||||||
|
monitorDTO.setMaxTime(monitorData.getMaxTime()); |
||||||
|
monitorDTO.setMinTime(monitorData.getMinTime()); |
||||||
|
monitorDTO.setTotalTime(monitorData.getTotalTime().longValue()); |
||||||
|
monitorDTO.setTotalRequestCount(monitorData.getTotalRequestCount().longValue()); |
||||||
|
monitorDTO.setSuccessCount(monitorData.getSuccessCount().longValue()); |
||||||
|
monitorDTO.setErrorCount(monitorData.getErrorCount().longValue()); |
||||||
|
monitorDTO.setErrorMsgList(monitorData.getMonitorErrorMsgMap().values()); |
||||||
|
return monitorDTO; |
||||||
|
} |
||||||
|
|
||||||
|
private MonitorData createMonitorInfo(ApiParam apiParam, ServiceInstance serviceInstance) { |
||||||
|
MonitorData monitorData = new MonitorData(); |
||||||
|
monitorData.setRouteId(apiParam.getRouteId()); |
||||||
|
monitorData.setName(apiParam.fetchName()); |
||||||
|
monitorData.setVersion(apiParam.fetchVersion()); |
||||||
|
monitorData.setServiceId(apiParam.fetchServiceId()); |
||||||
|
monitorData.setInstanceId(serviceInstance.getInstanceId()); |
||||||
|
monitorData.setTotalTime(new AtomicInteger()); |
||||||
|
monitorData.setMaxTime(0); |
||||||
|
monitorData.setMinTime(0); |
||||||
|
monitorData.setSuccessCount(new AtomicInteger()); |
||||||
|
monitorData.setTotalRequestCount(new AtomicInteger()); |
||||||
|
monitorData.setErrorCount(new AtomicInteger()); |
||||||
|
monitorData.setFlushTime(getFlushTime()); |
||||||
|
monitorData.setMonitorErrorMsgMap(new LRUCache<>(monitorErrorCapacity)); |
||||||
|
return monitorData; |
||||||
|
} |
||||||
|
|
||||||
|
private LocalDateTime getFlushTime() { |
||||||
|
return LocalDateTime.now() |
||||||
|
.plusSeconds(flushPeriodSeconds); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct |
||||||
|
public void after() { |
||||||
|
// 每隔schedulePeriodSeconds秒执行一次
|
||||||
|
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new MyNamedThreadFactory("monitorSchedule")); |
||||||
|
// 延迟执行,随机5~14秒
|
||||||
|
int delay = 5 + new Random().nextInt(10); |
||||||
|
scheduledThreadPoolExecutor.scheduleWithFixedDelay(this::flushDb, delay, schedulePeriodSeconds, TimeUnit.SECONDS); |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
package com.gitee.sop.gateway.mapper; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; |
||||||
|
import org.apache.commons.collections.CollectionUtils; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class DbMonitorInfoManager { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoMapper monitorInfoMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoErrorMapper monitorInfoErrorMapper; |
||||||
|
|
||||||
|
@Value("${sop.monitor.error-count-capacity:50}") |
||||||
|
int limitCount; |
||||||
|
|
||||||
|
public void saveMonitorInfoBatch(List<MonitorDTO> list) { |
||||||
|
if (CollectionUtils.isEmpty(list)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
monitorInfoMapper.saveMonitorInfoBatch(list); |
||||||
|
this.saveMonitorInfoErrorBatch(list); |
||||||
|
} |
||||||
|
|
||||||
|
private void saveMonitorInfoErrorBatch(List<MonitorDTO> list) { |
||||||
|
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCountAll(); |
||||||
|
// 路由id对应的错误次数,key:routeId,value:错误次数
|
||||||
|
Map<String, Integer> routeErrorCountsMap = routeErrorCounts.stream() |
||||||
|
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); |
||||||
|
List<MonitorErrorMsg> monitorErrorMsgList = list.stream() |
||||||
|
.filter(monitorDTO -> CollectionUtils.isNotEmpty(monitorDTO.getErrorMsgList())) |
||||||
|
.flatMap(monitorDTO -> { |
||||||
|
int limit = limitCount - routeErrorCountsMap.getOrDefault(monitorDTO.getRouteId(), 0); |
||||||
|
// 容量已满
|
||||||
|
if (limit <= 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
// 截取剩余
|
||||||
|
return monitorDTO.getErrorMsgList().stream().limit(limit); |
||||||
|
}) |
||||||
|
.filter(Objects::nonNull) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
if (CollectionUtils.isNotEmpty(monitorErrorMsgList)) { |
||||||
|
monitorInfoErrorMapper.saveMonitorInfoErrorBatch(monitorErrorMsgList); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.gitee.sop.gateway.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.sop.gateway.entity.MonitorInfoError; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; |
||||||
|
import org.apache.ibatis.annotations.Param; |
||||||
|
import org.apache.ibatis.annotations.Select; |
||||||
|
import org.apache.ibatis.annotations.Update; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> { |
||||||
|
|
||||||
|
@Update("UPDATE monitor_info_error " + |
||||||
|
"SET is_deleted=0 " + |
||||||
|
",count=count + 1 " + |
||||||
|
"WHERE route_id=#{routeId} AND error_id=#{errorId}") |
||||||
|
int updateError(@Param("routeId") String routeId,@Param("errorId") String errorId); |
||||||
|
|
||||||
|
int saveMonitorInfoErrorBatch(@Param("list") List<MonitorErrorMsg> list); |
||||||
|
|
||||||
|
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + |
||||||
|
"WHERE is_deleted=0 \n" + |
||||||
|
"GROUP BY route_id") |
||||||
|
List<RouteErrorCount> listRouteErrorCount(); |
||||||
|
|
||||||
|
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + |
||||||
|
"WHERE is_deleted=0 \n" + |
||||||
|
"GROUP BY route_id") |
||||||
|
List<RouteErrorCount> listRouteErrorCountAll(); |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package com.gitee.sop.gateway.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.sop.gateway.entity.MonitorInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; |
||||||
|
import org.apache.ibatis.annotations.Param; |
||||||
|
import org.apache.ibatis.annotations.Update; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> { |
||||||
|
|
||||||
|
/** |
||||||
|
* 更新监控状态 |
||||||
|
* |
||||||
|
* @return 返回影响行数 |
||||||
|
*/ |
||||||
|
@Update("UPDATE monitor_info " + |
||||||
|
"set max_time=case when max_time < #{maxTime} then #{maxTime} else max_time end " + |
||||||
|
",min_time=case when min_time > #{minTime} then #{minTime} else min_time end " + |
||||||
|
",total_request_count=total_request_count + #{totalRequestCount} " + |
||||||
|
",total_time=total_time + #{totalTime} " + |
||||||
|
",success_count=success_count + #{successCount} " + |
||||||
|
",error_count=error_count + #{errorCount} " + |
||||||
|
"where route_id=#{routeId}") |
||||||
|
int updateMonitorInfo(MonitorDTO monitorDTO); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 批量插入监控数据 |
||||||
|
* @param list 监控数据 |
||||||
|
* @return 返回影响行数 |
||||||
|
*/ |
||||||
|
int saveMonitorInfoBatch(@Param("list") List<MonitorDTO> list); |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
<?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"> |
||||||
|
<!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> |
||||||
|
<mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoErrorMapper"> |
||||||
|
|
||||||
|
<insert id="saveMonitorInfoErrorBatch"> |
||||||
|
INSERT INTO `monitor_info_error` ( |
||||||
|
`error_id`, |
||||||
|
`instance_id`, |
||||||
|
`route_id`, |
||||||
|
`error_msg`, |
||||||
|
`error_status`, |
||||||
|
`count`) |
||||||
|
VALUES |
||||||
|
<foreach collection="list" item="data" separator="," > |
||||||
|
(#{data.errorId}, |
||||||
|
#{data.instanceId}, |
||||||
|
#{data.routeId}, |
||||||
|
#{data.errorMsg}, |
||||||
|
#{data.errorStatus}, |
||||||
|
#{data.count}) |
||||||
|
</foreach> |
||||||
|
ON DUPLICATE KEY UPDATE |
||||||
|
error_msg = VALUES(error_msg) |
||||||
|
, error_status = VALUES(error_status) |
||||||
|
, `count`= `count` + VALUES(count) |
||||||
|
, is_deleted = 0 |
||||||
|
</insert> |
||||||
|
|
||||||
|
</mapper> |
@ -0,0 +1,43 @@ |
|||||||
|
<?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"> |
||||||
|
<!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> |
||||||
|
<mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoMapper"> |
||||||
|
|
||||||
|
<insert id="saveMonitorInfoBatch"> |
||||||
|
INSERT INTO `monitor_info` ( |
||||||
|
`route_id`, |
||||||
|
`name`, |
||||||
|
`version`, |
||||||
|
`service_id`, |
||||||
|
`instance_id`, |
||||||
|
`max_time`, |
||||||
|
`min_time`, |
||||||
|
`total_time`, |
||||||
|
`total_request_count`, |
||||||
|
`success_count`, |
||||||
|
`error_count`) |
||||||
|
VALUES |
||||||
|
<foreach collection="list" item="data" separator="," > |
||||||
|
(#{data.routeId}, |
||||||
|
#{data.name}, |
||||||
|
#{data.version}, |
||||||
|
#{data.serviceId}, |
||||||
|
#{data.instanceId}, |
||||||
|
#{data.maxTime}, |
||||||
|
#{data.minTime}, |
||||||
|
#{data.totalTime}, |
||||||
|
#{data.totalRequestCount}, |
||||||
|
#{data.successCount}, |
||||||
|
#{data.errorCount}) |
||||||
|
</foreach> |
||||||
|
<![CDATA[ |
||||||
|
ON DUPLICATE KEY UPDATE |
||||||
|
max_time = case when max_time < VALUES(max_time) then VALUES(max_time) else max_time end |
||||||
|
,min_time = case when min_time > VALUES(min_time) then VALUES(min_time) else min_time end |
||||||
|
,total_time = total_time + VALUES(total_time) |
||||||
|
,total_request_count = total_request_count + VALUES(total_request_count) |
||||||
|
,success_count = success_count + VALUES(success_count) |
||||||
|
,error_count = error_count + VALUES(error_count) |
||||||
|
]]></insert> |
||||||
|
|
||||||
|
</mapper> |
@ -0,0 +1,38 @@ |
|||||||
|
-- 4.1.0升级脚本 |
||||||
|
|
||||||
|
use sop; |
||||||
|
|
||||||
|
CREATE TABLE `monitor_info` ( |
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, |
||||||
|
`route_id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id', |
||||||
|
`name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名', |
||||||
|
`version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号', |
||||||
|
`service_id` varchar(64) NOT NULL DEFAULT '', |
||||||
|
`instance_id` varchar(128) NOT NULL DEFAULT '', |
||||||
|
`max_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最长时间', |
||||||
|
`min_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最小时间', |
||||||
|
`total_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '总时长,毫秒', |
||||||
|
`total_request_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '总调用次数', |
||||||
|
`success_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '成功次数', |
||||||
|
`error_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)', |
||||||
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, |
||||||
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||||
|
PRIMARY KEY (`id`), |
||||||
|
UNIQUE KEY `uk_routeid` (`route_id`) USING BTREE, |
||||||
|
KEY `idex_name` (`name`) USING BTREE |
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='接口监控信息'; |
||||||
|
|
||||||
|
CREATE TABLE `monitor_info_error` ( |
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, |
||||||
|
`error_id` varchar(64) NOT NULL DEFAULT '' COMMENT '错误id,md5Hex(instanceId + routeId + errorMsg)', |
||||||
|
`route_id` varchar(128) NOT NULL DEFAULT '', |
||||||
|
`error_msg` text NOT NULL, |
||||||
|
`error_status` int(11) NOT NULL DEFAULT '0' COMMENT 'http status,非200错误', |
||||||
|
`count` int(11) NOT NULL DEFAULT '0' COMMENT '错误次数', |
||||||
|
`is_deleted` tinyint(4) NOT NULL DEFAULT '0', |
||||||
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, |
||||||
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||||
|
PRIMARY KEY (`id`), |
||||||
|
UNIQUE KEY `uk_erririd` (`error_id`) USING BTREE, |
||||||
|
KEY `idx_routeid` (`route_id`) USING BTREE |
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Loading…
Reference in new issue