impl: 初步的面板接口实现

impl: 数据收集部分新增“最近一小时数据”缓存
feat: 增加swagger支持
ref: 移动部分常量至全局常量定义类中
dev-lensfrex
lensfrex 7 months ago
parent 1152e9b42c
commit 91e03bee52
Signed by: lensfrex
GPG Key ID: B1E395B3C6CA0356
  1. 5
      rition-center/api/pom.xml
  2. 2
      rition-center/api/src/main/java/rition/backend/api/v1/dto/request/AlertRuleAddRequest.java
  3. 34
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/AlertResponse.java
  4. 34
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/ContractResponse.java
  5. 5
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/MetricDataResponse.java
  6. 54
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/RuleResponse.java
  7. 33
      rition-center/api/src/main/java/rition/backend/api/v1/panel/AlertHistoryController.java
  8. 59
      rition-center/api/src/main/java/rition/backend/api/v1/panel/AlertRuleController.java
  9. 51
      rition-center/api/src/main/java/rition/backend/api/v1/panel/ContractController.java
  10. 84
      rition-center/api/src/main/java/rition/backend/api/v1/panel/MetricsViewController.java
  11. 10
      rition-center/api/src/main/java/rition/backend/api/v1/panel/PanelController.java
  12. 35
      rition-center/api/src/main/java/rition/backend/configure/OpenAPIConfigure.java
  13. 11
      rition-center/api/src/main/java/rition/backend/configure/WebMvcConfigure.java
  14. 5
      rition-center/api/src/main/java/rition/backend/service/MetricDataCollectingService.java
  15. 7
      rition-center/api/src/main/java/rition/backend/service/panel/PanelMetricService.java
  16. 3
      rition-center/api/src/main/resources/application.yml
  17. 39
      rition-center/api/src/test/java/panel/MetricServiceTest.java
  18. 21
      rition-center/common/src/main/java/rition/common/configure/RedisLuaConfigure.java
  19. 9
      rition-center/common/src/main/java/rition/common/data/dao/mapper/AlertMapper.java
  20. 7
      rition-center/common/src/main/java/rition/common/data/dao/mapper/ContractMapper.java
  21. 13
      rition-center/common/src/main/java/rition/common/data/dao/mapper/MetricRecordMapper.java
  22. 9
      rition-center/common/src/main/java/rition/common/data/dao/mapper/RuleMapper.java
  23. 52
      rition-center/common/src/main/java/rition/common/data/dto/PagingData.java
  24. 54
      rition-center/common/src/main/java/rition/common/data/dto/service/AlertRuleDto.java
  25. 36
      rition-center/common/src/main/java/rition/common/data/dto/service/panel/AlertRuleAddDto.java
  26. 36
      rition-center/common/src/main/java/rition/common/data/dto/service/panel/AlertRuleDto.java
  27. 17
      rition-center/common/src/main/java/rition/common/data/dto/service/panel/ContractAddDto.java
  28. 5
      rition-center/common/src/main/java/rition/common/data/entity/ContractEntity.java
  29. 5
      rition-center/common/src/main/java/rition/common/data/entity/RuleEntity.java
  30. 11
      rition-center/common/src/main/java/rition/common/data/enums/Constants.java
  31. 10
      rition-center/common/src/main/java/rition/common/exception/ServiceException.java
  32. 5
      rition-center/common/src/main/java/rition/common/exception/code/ServiceCode.java
  33. 36
      rition-center/common/src/main/resources/mapper/MetricRecordMapper.xml
  34. 7
      rition-center/common/src/main/resources/redis/const_len_queue.lua
  35. 19
      rition-center/pom.xml
  36. 27
      rition-center/service/collector/src/main/java/rition/service/collector/MetricCollectingService.java
  37. 48
      rition-center/service/panel/src/main/java/rition/service/panel/AlertHistoryService.java
  38. 78
      rition-center/service/panel/src/main/java/rition/service/panel/AlertRuleService.java
  39. 64
      rition-center/service/panel/src/main/java/rition/service/panel/ContractService.java
  40. 74
      rition-center/service/panel/src/main/java/rition/service/panel/MetricService.java
  41. 7
      rition-center/service/panel/src/main/java/rition/service/panel/PanelService.java
  42. 6
      rition-center/service/panel/src/main/resources/application-panel.yml

@ -48,10 +48,5 @@
<artifactId>panel</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

@ -22,7 +22,7 @@ public class AlertRuleAddRequest {
/**
* 阈值
*/
private String threshold;
private Double threshold;
/**
* 触发方法实时计算或定时计算

@ -0,0 +1,34 @@
package rition.backend.api.v1.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlertResponse {
/**
* id
*/
private Long id;
/**
* 出现警告的实例id
*/
private String instanceId;
/**
* 触发的规则
*/
private Long rule;
/**
* 警告出现的时间
*/
private Instant time;
}

@ -0,0 +1,34 @@
package rition.backend.api.v1.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContractResponse {
/**
* id
*/
private Long id;
/**
* 联系方式
*/
private String contract;
/**
* 联系方式类型
*/
private Integer type;
/**
* create_time
*/
private Instant createTime;
}

@ -2,13 +2,11 @@ package rition.backend.api.v1.dto.response;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
import java.util.Map;
@Data
@ -25,6 +23,5 @@ public class MetricDataResponse {
/**
* create_time
*/
@JsonFormat()
private Instant time;
private Long time;
}

@ -0,0 +1,54 @@
package rition.backend.api.v1.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleResponse {
/**
* 规则id
*/
private Long id;
/**
* 需要计算的指标项或者表达式
*/
private String expression;
/**
* 触发条件
*/
private Integer condition;
/**
* 阈值
*/
private Double threshold;
/**
* 触发方法实时计算或定时计算
*/
private Integer trigger;
/**
* 规则描述
*/
private String description;
/**
* create_time
*/
private Instant createTime;
/**
* update_time
*/
private Instant updateTime;
}

@ -0,0 +1,33 @@
package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import rition.backend.api.v1.dto.response.Response;
import rition.common.data.dto.PagingData;
import rition.common.data.entity.AlertEntity;
import rition.service.panel.AlertHistoryService;
@RestController
@RequestMapping("/panel/alerts")
public class AlertHistoryController {
private final AlertHistoryService alertHistoryService;
public AlertHistoryController(AlertHistoryService alertHistoryService) {
this.alertHistoryService = alertHistoryService;
}
@GetMapping("/list")
public Response<PagingData<AlertEntity>> getContractList(
@RequestParam(value = "instance_id", required = false) String instanceId,
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "page_size", required = false, defaultValue = "10") Integer pageSize
) {
PagingData<AlertEntity> alertEntityPagingData = new PagingData<>(page, pageSize);
var result = alertHistoryService.getAlertHistory(instanceId, alertEntityPagingData);
return Response.success(result);
}
}

@ -3,24 +3,63 @@ package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.*;
import rition.backend.api.v1.dto.request.AlertRuleAddRequest;
import rition.backend.api.v1.dto.response.Response;
import rition.backend.api.v1.dto.response.RuleResponse;
import rition.common.data.dto.service.panel.AlertRuleAddDto;
import rition.common.data.entity.RuleEntity;
import rition.service.panel.AlertRuleService;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/rules")
@RequestMapping("/panel/rules")
public class AlertRuleController {
@GetMapping("/list/{instanceId}")
public Response<Object> getAlertRuleList(@PathVariable("instanceId") String instanceId) {
return Response.success();
private final AlertRuleService alertRuleService;
public AlertRuleController(AlertRuleService alertRuleService) {
this.alertRuleService = alertRuleService;
}
@GetMapping("/list")
public Response<List<RuleResponse>> getAlertRuleList() {
List<RuleEntity> ruleEntityList = alertRuleService.getRule();
List<RuleResponse> ruleResponseList = new ArrayList<>(ruleEntityList.size());
for (RuleEntity ruleEntity : ruleEntityList) {
RuleResponse response = new RuleResponse();
response.setId(ruleEntity.getId());
response.setExpression(ruleEntity.getExpression());
response.setCondition(ruleEntity.getCondition());
response.setThreshold(ruleEntity.getThreshold());
response.setTrigger(ruleEntity.getTrigger());
response.setDescription(ruleEntity.getDescription());
response.setCreateTime(ruleEntity.getCreateTime());
response.setUpdateTime(ruleEntity.getUpdateTime());
ruleResponseList.add(response);
}
return Response.success(ruleResponseList);
}
@PostMapping("/add/{instanceId}")
public Response<Object> addAlertRule(@PathVariable("instanceId") String instanceId,
@RequestBody AlertRuleAddRequest alertRuleAddRequest) {
@PostMapping("/add")
public Response<Object> addAlertRule(@RequestBody AlertRuleAddRequest alertRuleAddRequest) {
AlertRuleAddDto alertRuleAddDto = new AlertRuleAddDto();
alertRuleAddDto.setInstanceId(alertRuleAddRequest.getInstanceId());
alertRuleAddDto.setExpression(alertRuleAddRequest.getExpression());
alertRuleAddDto.setCondition(alertRuleAddRequest.getCondition());
alertRuleAddDto.setThreshold(alertRuleAddRequest.getThreshold());
alertRuleAddDto.setTrigger(alertRuleAddRequest.getTrigger());
alertRuleAddDto.setDescription(alertRuleAddRequest.getDescription());
alertRuleService.addAlertRule(alertRuleAddDto);
return Response.success();
}
@PostMapping("/delete/{instanceId}")
public Response<Object> deleteAlertRule(@PathVariable("instanceId") String instanceId,
@RequestParam("id") String alertRuleId) {
@PostMapping("/delete")
public Response<Object> deleteAlertRule(@RequestParam("id") Long alertRuleId) {
alertRuleService.deleteRule(alertRuleId);
return Response.success();
}
}

@ -2,25 +2,56 @@ package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.*;
import rition.backend.api.v1.dto.request.ContractAddRequest;
import rition.backend.api.v1.dto.response.ContractResponse;
import rition.backend.api.v1.dto.response.Response;
import rition.common.data.dto.service.panel.ContractAddDto;
import rition.common.data.entity.ContractEntity;
import rition.service.panel.ContractService;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/contract")
@RequestMapping("/panel/contract")
public class ContractController {
@GetMapping("/list/{instanceId}")
public Response<Object> getContractList(@PathVariable("instanceId") String instanceId) {
return Response.success();
private final ContractService contractService;
public ContractController(ContractService contractService) {
this.contractService = contractService;
}
@GetMapping("/list")
public Response<List<ContractResponse>> getContractList() {
List<ContractEntity> contractEntityList = contractService.getContractList();
List<ContractResponse> responseList = new ArrayList<>(contractEntityList.size());
for (var contractEntity : contractEntityList) {
ContractResponse response = new ContractResponse();
response.setId(contractEntity.getId());
response.setContract(contractEntity.getContract());
response.setType(contractEntity.getType());
response.setCreateTime(contractEntity.getCreateTime());
responseList.add(response);
}
return Response.success(responseList);
}
@PostMapping("/add/{instanceId}")
public Response<Object> addContract(@PathVariable("instanceId") String instanceId,
@RequestBody ContractAddRequest contractAddRequest) {
@PostMapping("/add")
public Response<Object> addContract(@RequestBody ContractAddRequest contractAddRequest) {
ContractAddDto contractAddDto = new ContractAddDto();
contractAddDto.setContract(contractAddRequest.getContract());
contractAddDto.setType(contractAddRequest.getType());
contractService.addContract(contractAddDto);
return Response.success();
}
@PostMapping("/delete/{instanceId}")
public Response<Object> deleteContract(@PathVariable("instanceId") String instanceId,
@RequestParam("id") String contractId) {
@PostMapping("/delete")
public Response<Object> deleteContract(@RequestParam("id") Long contractId) {
contractService.deleteContract(contractId);
return Response.success();
}
}

@ -1,34 +1,61 @@
package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import rition.backend.api.v1.dto.request.MetricDataRequest;
import rition.backend.api.v1.dto.response.MetricDataResponse;
import rition.backend.api.v1.dto.response.Response;
import rition.common.data.dto.service.MetricDataDto;
import rition.common.data.entity.MetricRecordEntity;
import rition.common.data.enums.Constants;
import rition.service.panel.MetricService;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/panel/metrics")
public class MetricsViewController {
private final MetricService metricService;
private final RedisTemplate<String, Object> redisTemplate;
public MetricsViewController(MetricService metricService) {
public MetricsViewController(MetricService metricService, RedisTemplate<String, Object> redisTemplate) {
this.metricService = metricService;
this.redisTemplate = redisTemplate;
}
@GetMapping("/list")
@PostMapping("/list")
public Response<List<MetricDataResponse>> getMetrics(@RequestBody MetricDataRequest metricDataRequest) {
// 如果未给定范围参数,则使用最近一小时的数据
if (metricDataRequest.getStart() == null || metricDataRequest.getEnd() == null) {
List<Object> recentMetricDataList =
redisTemplate.opsForList().range(
Constants.RedisKeys.RECENT_METRIC_CACHE, 0, Constants.MAX_METRIC_CACHE_NUM
);
if (recentMetricDataList == null) {
return Response.success(new ArrayList<>());
}
List<MetricDataResponse> responseList = new ArrayList<>(recentMetricDataList.size());
for (Object recentMetricData : recentMetricDataList) {
var data = (MetricDataDto) recentMetricData;
responseList.add(MetricDataResponse.builder()
.metricData(data.getData())
.time(data.getTimestamp())
.build());
}
return Response.success(responseList);
}
var entityResult = metricService.getMetricDataRange(
metricDataRequest.getInstanceId(),
metricDataRequest.getMetricItems(),
this.filterMetricItems(metricDataRequest.getMetricItems()),
Instant.ofEpochMilli(metricDataRequest.getStart()),
Instant.ofEpochMilli(metricDataRequest.getEnd())
);
@ -38,11 +65,52 @@ public class MetricsViewController {
for (MetricRecordEntity entity : entityResult) {
responseList.add(MetricDataResponse.builder()
.metricData(entity.getMetricData())
.time(entity.getTime())
.time(entity.getTime().toEpochMilli())
.build()
);
}
return Response.success(responseList);
}
private static final HashSet<String> allowedMetricItems = new HashSet<>(22);
static {
allowedMetricItems.add("up");
allowedMetricItems.add("node_load5");
allowedMetricItems.add("node_sockstat_TCP_tw");
allowedMetricItems.add("node_cpu_seconds_total");
allowedMetricItems.add("node_memory_Cached_bytes");
allowedMetricItems.add("node_memory_Buffers_bytes");
allowedMetricItems.add("node_memory_MemFree_bytes");
allowedMetricItems.add("node_disk_read_bytes_total");
allowedMetricItems.add("node_filesystem_free_bytes");
allowedMetricItems.add("node_filesystem_size_bytes");
allowedMetricItems.add("node_memory_MemTotal_bytes");
allowedMetricItems.add("node_netstat_Tcp_CurrEstab");
allowedMetricItems.add("node_filesystem_avail_bytes");
allowedMetricItems.add("node_disk_written_bytes_total");
allowedMetricItems.add("node_disk_reads_completed_total");
allowedMetricItems.add("node_network_receive_drop_total");
allowedMetricItems.add("node_disk_writes_completed_total");
allowedMetricItems.add("node_network_receive_bytes_total");
allowedMetricItems.add("node_network_transmit_drop_total");
allowedMetricItems.add("node_network_transmit_bytes_total");
allowedMetricItems.add("node_network_receive_packets_total");
allowedMetricItems.add("node_network_transmit_packets_total");
}
private List<String> filterMetricItems(List<String> metricItems) {
List<String> filteredMetricItems = new ArrayList<>(metricItems.size());
for (String metricItem : metricItems) {
if (allowedMetricItems.contains(metricItem)) {
filteredMetricItems.add(metricItem);
} else {
log.warn("[MetricService]: 请求的指标值含有非允许的指标值:{}", metricItem);
}
}
return filteredMetricItems;
}
}

@ -1,10 +0,0 @@
package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/panel")
public class PanelController {
}

@ -0,0 +1,35 @@
package rition.backend.configure;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Getter
@Component
@Configuration
public class OpenAPIConfigure {
private String serverUrl;
@Value("${wusthelper.docs.server-url:/}")
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
@Bean
public OpenAPI openAPI() {
var info = new Info()
.title("Rition-center")
.description("Rition-center api")
.version("v1");
return new OpenAPI()
.info(info);
}
}

@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import rition.backend.api.interceptor.ResponseIdInterceptor;
import rition.backend.api.resolver.RequestIdArgumentResolver;
@ -12,24 +13,24 @@ import java.util.List;
@Configuration
@SpringBootConfiguration
public class WebAppConfig extends WebMvcConfigurationSupport {
public class WebMvcConfigure implements WebMvcConfigurer {
private final RequestIdArgumentResolver requestIdArgumentResolver;
private final ResponseIdInterceptor requestIdInterceptor;
public WebAppConfig(RequestIdArgumentResolver requestIdArgumentResolver,
ResponseIdInterceptor requestIdInterceptor) {
public WebMvcConfigure(RequestIdArgumentResolver requestIdArgumentResolver,
ResponseIdInterceptor requestIdInterceptor) {
this.requestIdArgumentResolver = requestIdArgumentResolver;
this.requestIdInterceptor = requestIdInterceptor;
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(requestIdArgumentResolver);
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestIdInterceptor)
.addPathPatterns("/**");
}

@ -6,6 +6,7 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import rition.common.data.dto.log.RequestProcessTraceRecord;
import rition.common.data.dto.service.MetricDataDto;
import rition.common.data.enums.Constants;
import rition.service.collector.MetricCollectingService;
import java.util.Map;
@ -29,8 +30,6 @@ public class MetricDataCollectingService {
this.redisTemplate = redisTemplate;
}
private static final String PROCESS_TRACE_REDIS_KEY_FORMAT = "rition:request:trace:%s";
/**
* 接收处理好的监控指标数据异步处理请求发送后处理状态使用requestId追踪处理结果放置于redis
*
@ -55,7 +54,7 @@ public class MetricDataCollectingService {
}
try {
redisTemplate.opsForValue().set(PROCESS_TRACE_REDIS_KEY_FORMAT.formatted(requestId), record);
redisTemplate.opsForValue().set(Constants.RedisKeys.PROCESS_TRACE.formatted(requestId), record);
} catch (Exception e2) {
log.error("请求结果保存出现异常: ", e2);
log.error("请求结果:{}", record);

@ -0,0 +1,7 @@
package rition.backend.service.panel;
import org.springframework.stereotype.Service;
@Service
public class PanelMetricService {
}

@ -11,3 +11,6 @@ spring:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:Test2333!}
logging:
level:
rition: debug

@ -0,0 +1,39 @@
package panel;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import rition.backend.RitionBackendMain;
import rition.common.data.dao.mapper.MetricRecordMapper;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@MybatisTest
@ContextConfiguration(classes = RitionBackendMain.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class MetricServiceTest {
@Autowired
MetricRecordMapper metricRecordMapper;
@Test
public void testGetMetricRecord() {
List<String> metricItems = new ArrayList<>();
metricItems.add("node_network_receive_packets_total");
metricItems.add("node_sockstat_TCP_tw");
var result = metricRecordMapper.getMetricDataGroupByHour(
"7273a1ea-0089-4674-b606-b1b8d809d866",
metricItems,
Instant.parse("2024-04-17T14:00:00.00Z"),
Instant.parse("2024-04-17T16:00:00.00Z")
);
System.out.println(Arrays.toString(result.toArray()));
}
}

@ -0,0 +1,21 @@
package rition.common.configure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
@Configuration
public class RedisLuaConfigure {
@Bean(name = "constLenQueueRedisScript")
public RedisScript<Object> constLenQueueRedisScript() {
Resource luaResource = new ClassPathResource("redis/const_len_queue.lua");
DefaultRedisScript<Object> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(luaResource));
return redisScript;
}
}

@ -0,0 +1,9 @@
package rition.common.data.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import rition.common.data.entity.AlertEntity;
@Mapper
public interface AlertMapper extends BaseMapper<AlertEntity> {
}

@ -0,0 +1,7 @@
package rition.common.data.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import rition.common.data.entity.ContractEntity;
public interface ContractMapper extends BaseMapper<ContractEntity> {
}

@ -19,4 +19,17 @@ public interface MetricRecordMapper extends BaseMapper<MetricRecordEntity> {
@Param("metricItems") List<String> metricItems,
@Param("startTime") Instant startTime, @Param("endTime") Instant endTime
);
List<MetricRecordEntity> getMetricDataGroupByMinute(
@Param("instanceId") String instanceId,
@Param("metricItems") List<String> metricItems,
@Param("startTime") Instant startTime, @Param("endTime") Instant endTime
);
List<MetricRecordEntity> getMetricDataGroupBySomeMinute(
@Param("minutes") Integer minutes,
@Param("instanceId") String instanceId,
@Param("metricItems") List<String> metricItems,
@Param("startTime") Instant startTime, @Param("endTime") Instant endTime
);
}

@ -0,0 +1,9 @@
package rition.common.data.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import rition.common.data.entity.RuleEntity;
@Mapper
public interface RuleMapper extends BaseMapper<RuleEntity> {
}

@ -0,0 +1,52 @@
package rition.common.data.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collection;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PagingData<T> {
private long currentPage;
private long pageSize;
private long totalPage;
private long totalResult;
private Collection<T> data;
public PagingData(long currentPage, long pageSize) {
this.currentPage = currentPage;
this.pageSize = pageSize;
}
public PagingData<T> copy() {
PagingData<T> pagingData = new PagingData<>();
pagingData.setCurrentPage(this.getCurrentPage());
pagingData.setPageSize(this.getPageSize());
pagingData.setTotalPage(this.getTotalPage());
pagingData.setTotalResult(this.getTotalResult());
pagingData.setData(this.getData());
return pagingData;
}
public static <K> PagingData<K> copyOnlyPagingValues(PagingData<?> source) {
PagingData<K> pagingData = new PagingData<>();
pagingData.setCurrentPage(source.getCurrentPage());
pagingData.setPageSize(source.getPageSize());
pagingData.setTotalPage(source.getTotalPage());
pagingData.setTotalResult(source.getTotalResult());
return pagingData;
}
}

@ -0,0 +1,54 @@
package rition.common.data.dto.service;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlertRuleDto {
/**
* 规则id
*/
private Long id;
/**
* 规则对应的实例id
*/
private String instanceId;
/**
* 需要计算的指标项或者表达式
*/
private String expression;
/**
* 触发条件
*/
private Integer condition;
/**
* 阈值
*/
private Double threshold;
/**
* 触发方法实时计算或定时计算
*/
private Integer trigger;
/**
* 规则描述
*/
private String description;
/**
* create_time
*/
private Instant createTime;
}

@ -0,0 +1,36 @@
package rition.common.data.dto.service.panel;
import lombok.Data;
@Data
public class AlertRuleAddDto {
/**
* 规则对应的实例id
*/
private String instanceId;
/**
* 需要计算的指标项或者表达式
*/
private String expression;
/**
* 触发条件
*/
private Integer condition;
/**
* 阈值
*/
private Double threshold;
/**
* 触发方法实时计算或定时计算
*/
private Integer trigger;
/**
* 规则描述
*/
private String description;
}

@ -0,0 +1,36 @@
package rition.common.data.dto.service.panel;
import lombok.Data;
@Data
public class AlertRuleDto {
/**
* 规则对应的实例id
*/
private String instanceId;
/**
* 需要计算的指标项或者表达式
*/
private String expression;
/**
* 触发条件
*/
private Integer condition;
/**
* 阈值
*/
private Double threshold;
/**
* 触发方法实时计算或定时计算
*/
private Integer trigger;
/**
* 规则描述
*/
private String description;
}

@ -0,0 +1,17 @@
package rition.common.data.dto.service.panel;
import lombok.Data;
@Data
public class ContractAddDto {
/**
* 联系方式
*/
private String contract;
/**
* 联系方式类型
*/
private Integer type;
}

@ -22,11 +22,6 @@ public class ContractEntity {
*/
private Long id;
/**
* 绑定的实例
*/
private String instanceId;
/**
* 联系方式
*/

@ -22,11 +22,6 @@ public class RuleEntity {
*/
private Long id;
/**
* 规则对应的实例id
*/
private String instanceId;
/**
* 需要计算的指标项或者表达式
*/

@ -9,4 +9,15 @@ public class Constants {
public static final Integer NORMAL = 0;
public static final Integer DELETED = 1;
}
public static class RedisKeys {
public static final String RULE_CACHE = "rition:cache:rule";
public static final String RECENT_METRIC_CACHE = "rition:cache:metrics";
public static final String PROCESS_TRACE = "rition:request:trace:%s";
}
/**
* 缓存最近一小时的数据
*/
public static final int MAX_METRIC_CACHE_NUM = 3600/5;
}

@ -43,4 +43,14 @@ public class ServiceException extends RuntimeException {
public static void error(int code) throws ServiceException {
throw new ServiceException(code);
}
/**
* 直接按错误码抛出异常
*
* @param code 错误码
* @throws ServiceException .
*/
public static void error(ServiceCode code) throws ServiceException {
throw new ServiceException(code);
}
}

@ -13,7 +13,10 @@ public enum ServiceCode {
ParamWrong(10_00_03, "参数错误"),
PermissionDenied(10_00_04, "权限不足"),
TokenInvalid(10_00_05, "用户凭据无效,登录态失效"),
UnknownErr(10_00_06, "服务器内部错误");
UnknownErr(10_00_06, "服务器内部错误"),
ContractTooManyData(11_00_00, "联系通知信息过多"),
;
private final int code;

@ -37,4 +37,40 @@
-->
</select>
<select id="getMetricDataGroupByMinute" resultMap="metricDataEntity" resultType="rition.common.data.entity.MetricRecordEntity">
SELECT JSON_OBJECT(
<foreach collection="metricItems" item="metricItem" separator=",">
"${metricItem}", ${metricItem}
</foreach>
) AS metric_data,
t AS time
FROM (
SELECT
<foreach collection="metricItems" item="metricItem">
avg(JSON_EXTRACT(metric_data, "$.${metricItem}")) as ${metricItem},
</foreach>
DATE_FORMAT(time,'%Y-%m-%d %H:%i:00') as t
FROM record WHERE `time` BETWEEN #{startTime} and #{endTime} and `instance_id` = #{instanceId}
GROUP BY t ORDER BY t
) AS tab;
</select>
<select id="getMetricDataGroupBySomeMinute" resultMap="metricDataEntity" resultType="rition.common.data.entity.MetricRecordEntity">
SELECT JSON_OBJECT(
<foreach collection="metricItems" item="metricItem" separator=",">
"${metricItem}", ${metricItem}
</foreach>
) AS metric_data,
t AS time
FROM (
SELECT
<foreach collection="metricItems" item="metricItem">
avg(JSON_EXTRACT(metric_data, "$.${metricItem}")) as ${metricItem},
</foreach>
DATE_FORMAT(time,'%Y-%m-%d %H:%i:00') as t
FROM record WHERE `time` BETWEEN #{startTime} and #{endTime} and `instance_id` = #{instanceId}
GROUP BY t ORDER BY t
) AS tab;
</select>
</mapper>

@ -0,0 +1,7 @@
local key = KEYS[1]
local max_len = tonumber(ARGV[1])
local val = ARGV[2]
if (redis.call('llen', key) > max_len) then
redis.call('lpop', key)
end
redis.call('rpush', key, val)

@ -33,6 +33,8 @@
<mybatis-sb-starter.version>3.0.3</mybatis-sb-starter.version>
<mybatis.version>3.5.16</mybatis.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<spring-doc.version>2.5.0</spring-doc.version>
</properties>
<dependencies>
@ -71,6 +73,23 @@
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${spring-doc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>${spring-doc.version}</version>
</dependency>
<!-- id生成 -->
<dependency>
<groupId>com.github.yitter</groupId>

@ -2,12 +2,16 @@ package rition.service.collector;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import rition.common.data.dao.mapper.MetricRecordMapper;
import rition.common.data.dto.service.MetricDataDto;
import rition.common.data.entity.MetricRecordEntity;
import rition.common.data.enums.Constants;
import rition.service.collector.configure.CollectorServiceKafkaConfigure;
import rition.common.data.dao.mapper.MetricRecordMapper;
import java.time.Instant;
import java.util.ArrayList;
@ -25,13 +29,23 @@ public class MetricCollectingService {
private final String collectedDataTopic;
private final MetricRecordMapper metricRecordMapper;
private final RedisTemplate<String, MetricDataDto> redisTemplate;
private final RedisScript<Object> constLenQueueRedisScript;
public MetricCollectingService(KafkaTemplate<String, MetricDataDto> kafkaTemplate,
CollectorServiceKafkaConfigure collectorServiceKafkaConfigure,
MetricRecordMapper metricRecordMapper) {
MetricRecordMapper metricRecordMapper,
RedisTemplate<String, MetricDataDto> redisTemplate,
@Qualifier("constLenQueueRedisScript") RedisScript<Object> constLenQueueRedisScript) {
this.kafkaTemplate = kafkaTemplate;
this.collectedDataTopic = collectorServiceKafkaConfigure.getDataCollecting().getTopic();
this.metricRecordMapper = metricRecordMapper;
this.redisTemplate = redisTemplate;
this.constLenQueueRedisScript = constLenQueueRedisScript;
}
private static final int METRIC_NUMS = 22;
@ -64,7 +78,7 @@ public class MetricCollectingService {
// kafka发布
kafkaTemplate.send(this.collectedDataTopic, metricDataDto);
// 分批入库
// 分批入库,只是针对大批量请求的处理
batchSaveEntityList.add(entity);
if (turn >= DEFAULT_DB_BATCH_SIZE) {
metricRecordMapper.insertBatchSomeColumn(batchSaveEntityList);
@ -74,6 +88,13 @@ public class MetricCollectingService {
} else {
turn++;
}
// 缓存最近数据
redisTemplate.execute(
constLenQueueRedisScript,
List.of(Constants.RedisKeys.RECENT_METRIC_CACHE),
Constants.MAX_METRIC_CACHE_NUM, metricDataDto
);
}
}

@ -0,0 +1,48 @@
package rition.service.panel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import rition.common.data.dao.mapper.AlertMapper;
import rition.common.data.dto.PagingData;
import rition.common.data.entity.AlertEntity;
import rition.common.data.enums.Constants;
@Service
public class AlertHistoryService {
private final AlertMapper alertMapper;
public AlertHistoryService(AlertMapper alertMapper) {
this.alertMapper = alertMapper;
}
/**
* 分页获取报警信息
*
* @param instanceId 实例id
* @param pagingData 分页数据查询完成后会对此对象进行修改
* @return 分页数据含结果实际上与参数里的pagingData是同一个对象
*/
public PagingData<AlertEntity> getAlertHistory(String instanceId, PagingData<AlertEntity> pagingData) {
LambdaQueryWrapper<AlertEntity> query;
if (instanceId != null) {
query = new LambdaQueryWrapper<AlertEntity>()
.eq(AlertEntity::getInstanceId, instanceId)
.eq(AlertEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
} else {
query = new LambdaQueryWrapper<AlertEntity>()
.eq(AlertEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
}
Page<AlertEntity> page = new Page<>(pagingData.getCurrentPage(), pagingData.getPageSize());
var result = alertMapper.selectPage(page, query);
pagingData.setData(result.getRecords());
pagingData.setTotalResult(result.getTotal());
pagingData.setTotalPage(result.getPages());
pagingData.setCurrentPage(result.getCurrent());
pagingData.setPageSize(result.getSize());
return pagingData;
}
}

@ -0,0 +1,78 @@
package rition.service.panel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yitter.idgen.YitIdHelper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import rition.common.data.dao.mapper.RuleMapper;
import rition.common.data.dto.service.AlertRuleDto;
import rition.common.data.dto.service.panel.AlertRuleAddDto;
import rition.common.data.entity.RuleEntity;
import rition.common.data.enums.Constants;
import java.time.Instant;
import java.util.List;
@Service
public class AlertRuleService {
private final RuleMapper ruleMapper;
private final RedisTemplate<String, AlertRuleDto> redisTemplate;
public AlertRuleService(RuleMapper ruleMapper,
RedisTemplate<String, AlertRuleDto> redisTemplate) {
this.ruleMapper = ruleMapper;
this.redisTemplate = redisTemplate;
}
public void addAlertRule(AlertRuleAddDto alertRuleAddDto) {
RuleEntity ruleEntity = new RuleEntity();
ruleEntity.setId(YitIdHelper.nextId());
ruleEntity.setExpression(alertRuleAddDto.getExpression());
ruleEntity.setCondition(alertRuleAddDto.getCondition());
ruleEntity.setThreshold(alertRuleAddDto.getThreshold());
ruleEntity.setTrigger(alertRuleAddDto.getTrigger());
ruleEntity.setDescription(alertRuleAddDto.getDescription());
var now = Instant.now();
ruleEntity.setCreateTime(now);
ruleEntity.setUpdateTime(now);
ruleEntity.setStatus(Constants.EntityCommonStatus.NORMAL);
ruleMapper.insert(ruleEntity);
AlertRuleDto alertRuleDto = new AlertRuleDto();
alertRuleDto.setId(ruleEntity.getId());
alertRuleDto.setExpression(ruleEntity.getExpression());
alertRuleDto.setCondition(ruleEntity.getCondition());
alertRuleDto.setThreshold(ruleEntity.getThreshold());
alertRuleDto.setTrigger(ruleEntity.getTrigger());
alertRuleDto.setDescription(ruleEntity.getDescription());
alertRuleDto.setCreateTime(now);
// 规则缓存到redis
redisTemplate.opsForHash().put(Constants.RedisKeys.RULE_CACHE, alertRuleDto.getId(), alertRuleDto);
}
public List<RuleEntity> getRule() {
var query = new LambdaQueryWrapper<RuleEntity>()
.eq(RuleEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
return ruleMapper.selectList(query);
}
public int deleteRule(Long ruleId) {
var query = new LambdaUpdateWrapper<RuleEntity>()
.set(RuleEntity::getStatus, Constants.EntityCommonStatus.DELETED)
.eq(RuleEntity::getId, ruleId)
.eq(RuleEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
var updatedRows = ruleMapper.update(query);
// 删除相应的缓存
if (updatedRows != 0) {
redisTemplate.opsForHash().delete(Constants.RedisKeys.RULE_CACHE, ruleId);
}
return updatedRows;
}
}

@ -0,0 +1,64 @@
package rition.service.panel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yitter.idgen.YitIdHelper;
import org.springframework.stereotype.Service;
import rition.common.data.dao.mapper.ContractMapper;
import rition.common.data.dto.service.panel.ContractAddDto;
import rition.common.data.entity.ContractEntity;
import rition.common.data.enums.Constants;
import rition.common.exception.ServiceException;
import rition.common.exception.code.ServiceCode;
import java.time.Instant;
import java.util.List;
@Service
public class ContractService {
private final ContractMapper contractMapper;
public ContractService(ContractMapper contractMapper) {
this.contractMapper = contractMapper;
}
public List<ContractEntity> getContractList() {
var query = new LambdaQueryWrapper<ContractEntity>()
.eq(ContractEntity::getStatus, Constants.EntityCommonStatus.NORMAL)
.last("limit 100");
return contractMapper.selectList(query);
}
private static final int MAX_CONTRACT_NUM = 20;
public void addContract(ContractAddDto contractAddDto) {
var countQuery = new LambdaQueryWrapper<ContractEntity>()
.eq(ContractEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
var count = contractMapper.selectCount(countQuery);
if (count > MAX_CONTRACT_NUM) {
ServiceException.error(ServiceCode.ContractTooManyData);
}
var now = Instant.now();
var entity = ContractEntity.builder()
.id(YitIdHelper.nextId())
.contract(contractAddDto.getContract())
.type(contractAddDto.getType())
.createTime(now)
.updateTime(now)
.status(Constants.EntityCommonStatus.NORMAL)
.build();
contractMapper.insert(entity);
}
public void deleteContract(Long id) {
var query = new LambdaUpdateWrapper<ContractEntity>()
.set(ContractEntity::getStatus, Constants.EntityCommonStatus.DELETED)
.eq(ContractEntity::getId, id)
.eq(ContractEntity::getStatus, Constants.EntityCommonStatus.NORMAL);
contractMapper.update(query);
}
}

@ -1,13 +1,12 @@
package rition.service.panel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import rition.common.data.dao.mapper.MetricRecordMapper;
import rition.common.data.entity.MetricRecordEntity;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@Slf4j
@ -20,54 +19,35 @@ public class MetricService {
this.metricRecordMapper = metricRecordMapper;
}
private static final long HOUR = 3600 * 1000;
private static final long DAY = 24 * 3600 * 1000;
private static final long MONTH = 30L * 24 * 3600 * 1000;
/**
* 按时间范围获取监测数据
*
* @param instanceId 实例id
* @param metricItems 监测指标项
* @param start 开始时间
* @param end 结束时间
* @return 监测指标数据指标数据为给定的指标平均统计
*/
public List<MetricRecordEntity> getMetricDataRange(String instanceId,
List<String> metricItems,
Instant start, Instant end) {
var filteredMetricItems = this.filterMetricItems(metricItems);
if (filteredMetricItems.isEmpty()) {
return new ArrayList<>();
// 时间跨度小于1小时:不按时间粒度获取
// 时间跨度大于1小时:时间粒度为1分钟
// 时间跨度超过一天:时间粒度为小时
var diff = end.minusMillis(start.toEpochMilli()).toEpochMilli();
if (diff < HOUR) {
var query = new LambdaQueryWrapper<MetricRecordEntity>()
.eq(MetricRecordEntity::getInstanceId, instanceId)
.between(MetricRecordEntity::getTime, start, end);
return metricRecordMapper.selectList(query);
} else if (diff < DAY) {
return metricRecordMapper.getMetricDataGroupByMinute(instanceId, metricItems, start, end);
} else {
return metricRecordMapper.getMetricDataGroupByHour(instanceId, metricItems, start, end);
}
return metricRecordMapper.getMetricDataGroupByHour(instanceId, metricItems, start, end);
}
private static final HashSet<String> allowedMetricItems = new HashSet<>(22);
static {
allowedMetricItems.add("up");
allowedMetricItems.add("node_load5");
allowedMetricItems.add("node_sockstat_TCP_tw");
allowedMetricItems.add("node_cpu_seconds_total");
allowedMetricItems.add("node_memory_Cached_bytes");
allowedMetricItems.add("node_memory_Buffers_bytes");
allowedMetricItems.add("node_memory_MemFree_bytes");
allowedMetricItems.add("node_disk_read_bytes_total");
allowedMetricItems.add("node_filesystem_free_bytes");
allowedMetricItems.add("node_filesystem_size_bytes");
allowedMetricItems.add("node_memory_MemTotal_bytes");
allowedMetricItems.add("node_netstat_Tcp_CurrEstab");
allowedMetricItems.add("node_filesystem_avail_bytes");
allowedMetricItems.add("node_disk_written_bytes_total");
allowedMetricItems.add("node_disk_reads_completed_total");
allowedMetricItems.add("node_network_receive_drop_total");
allowedMetricItems.add("node_disk_writes_completed_total");
allowedMetricItems.add("node_network_receive_bytes_total");
allowedMetricItems.add("node_network_transmit_drop_total");
allowedMetricItems.add("node_network_transmit_bytes_total");
allowedMetricItems.add("node_network_receive_packets_total");
allowedMetricItems.add("node_network_transmit_packets_total");
}
public List<String> filterMetricItems(List<String> metricItems) {
List<String> filteredMetricItems = new ArrayList<>(metricItems.size());
for (String metricItem : metricItems) {
if (allowedMetricItems.contains(metricItem)) {
filteredMetricItems.add(metricItem);
} else {
log.warn("[MetricService]: 含有非允许的指标值:{}", metricItem);
}
}
return filteredMetricItems;
}
}

@ -1,7 +0,0 @@
package rition.service.panel;
import org.springframework.stereotype.Service;
@Service
public class PanelService {
}

@ -4,9 +4,3 @@ spring:
url: jdbc:mysql://127.0.0.1:3306/rition?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: Test2333!
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Loading…
Cancel
Save