在现代软件开发中,对于应用程序的监控和统计数据收集变得越来越重要。通过监控接口调用,我们可以获得有关应用程序性能、稳定性和用户行为的关键洞察。本文将描述笔者使用切面(AOP)和Redis实现的一个简易接口调用监控数据统计Demo。该Demo的主要功能是提供指定时间内(5min、10min)接口调用的成功率,以及一段时间内调用失败的接口暴露。
一、开发环境
因为接口调用监控数据统计demo的实现,其业务相对简单,涉及到的技术栈为springboot+redis。本文采用的开发环境信息如下:
环境信息 | 版本 |
操作系统 | Windows Server2016 |
JDK | 1.8.0_192 |
Redis-server | 3.2.100 |
IDE | IntelliJ IDEA 2023.1.4 |
二、项目Maven依赖
本文采用Maven进行版本管理,表中展示了项目的核心依赖信息:
核心依赖 | 版本 |
spring-boot-starter-parent | 2.6.13 |
spring-boot-starter-data-redis | 2.6.13 |
三、源代码目录结构
四、核心代码
(1)MonitorAspect
该类主要提供监控调用接口的数据,统计调用次数并写入到Redis。
//统计调用次数,写入到Redis
String key_total = String.format(Constant.REDIS_KEY_API_COLLECT_TOTAL, methodName);
String key_success = String.format(Constant.REDIS_KEY_API_COLLECT_SUCCESS, methodName);
String key_fail = String.format(Constant.REDIS_KEY_API_COLLECT_FAIL, methodName);
Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().rangeWithScores(key_total, 0, -1);
System.out.println("redis_key_total_hello: " + key_total);
//设置uuid作为调用动作的id,对应zset中的value
String uuidValue;
uuidValue = UUID.randomUUID().toString() + TimeUnit.MILLISECONDS;
redisTemplate.opsForZSet().add(key_total, uuidValue, System.currentTimeMillis());
// 调用目标方法,并获取响应数据
Object result = join_point.proceed();
if (result instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) result;
// 获取HTTP状态码
int statusCode = responseEntity.getStatusCodeValue();
//统计调用成功次数,写入到Redis
if(statusCode==200){
uuidValue = UUID.randomUUID().toString() + TimeUnit.MILLISECONDS;
redisTemplate.opsForZSet().add(key_success, uuidValue, System.currentTimeMillis());
// redisTemplate.expire(key_success, expiryMillis, TimeUnit.MILLISECONDS);
System.out.println(methodName + ":本次调用成功");
}else{
//调用失败set key_fail
uuidValue = UUID.randomUUID().toString() + TimeUnit.MILLISECONDS;
redisTemplate.opsForZSet().add(key_fail, uuidValue, System.currentTimeMillis());
// redisTemplate.expire(key_success, expiryMillis, TimeUnit.MILLISECONDS);
System.out.println(methodName + ":本次调用失败");
}
}
(2)MonitorService
该类提供了对Redis中接口调用数据的统计和处理。
public MethodCollectDetail querySuccessRateByApiName(String apiName){
String key_total = String.format(Constant.REDIS_KEY_API_COLLECT_TOTAL, apiName);
System.out.println(key_total);
String key_success = String.format(Constant.REDIS_KEY_API_COLLECT_SUCCESS, apiName);
long total_count = 0;
long success_count = 0;
Long count = 0L;
long now_time = System.currentTimeMillis();
long one_minute_ago = (now_time - 60000L);
System.out.println("redis_key_total_monitor: " + key_total);
//从Redis中读取近1分钟调用的总次数
count = redisTemplate.opsForZSet().count(key_total, one_minute_ago, now_time);
total_count = (count != null) ? count : 0;
//从Redis中读取近1分钟调用成功的次数
count = redisTemplate.opsForZSet().count(key_success, one_minute_ago, now_time);
success_count = (count != null) ? count : 0;
System.out.println("total_count:" + total_count);
System.out.println("success_count:" + success_count);
//计算得到调用成功率
double success_rate = (total_count != 0) ? ((double)success_count / total_count) : 1;
String success_rate_str = String.format("%.2f",success_rate * 100) + "%";
MethodCollectDetail methodCollectDetail = new MethodCollectDetail(apiName, success_count, total_count, success_rate_str);
//返回结果
return methodCollectDetail;
}
public List<MethodFailDetail> queryFailRecord(){
List<MethodFailDetail> detailList = new ArrayList<>();
String query_key = String.format(Constant.REDIS_KEY_API_COLLECT_FAIL, "*");
System.out.println("query_key:" + query_key);
//获取当前存在的调用接口失败记录
Set<String> keys_fail = redisTemplate.keys(query_key);
if(keys_fail.size() == 0){
System.out.println("未查到");
return null;
}
//设置要查询的时间(可为参数传入)暂定3分钟
long endTime = System.currentTimeMillis();
long startTime = endTime - 180000L;
for (String key_fail : keys_fail) {
System.out.println("处理……");
String methodName = key_fail.substring(query_key.length() - 1);
System.out.println(methodName);
String key_success = key_fail.replace(":fail:", ":success:");
Long fail_count = redisTemplate.opsForZSet().count(key_fail, startTime, endTime);
Long success_count = redisTemplate.opsForZSet().count(key_success, startTime, endTime);
fail_count = (fail_count != null) ? fail_count:0;
success_count = (success_count != null) ? success_count:0;
MethodFailDetail methodFailDetail = new MethodFailDetail(methodName, success_count, fail_count);
detailList.add(methodFailDetail);
}
return detailList;
}
(3)CleanMonitorDataScheduled
该类提供了定时清除Redis中过期数据的功能。
@Scheduled(cron = "0 */1 * * * ?")
public void run(){
String clean_prefix = Constant.REDIS_KEY_API_COLLECT_PREFIX + "*";
Set<String> keys = redisTemplate.keys(clean_prefix);
//删除过期数据,目前定为超过2分钟(方便测试)
long now_time = System.currentTimeMillis();
long expire_time = now_time - 2*60*1000;
log.info("开始清理超过2分钟的过期监控数据……");
for(String key: keys){
redisTemplate.opsForZSet().removeRangeByScore(key, 0, expire_time);
}
log.info("清理完成!");
}
五、总结
本文通过结合切面编程和Redis实现了一个简单的接口调用监控数据统计Demo。这种方法将监控逻辑与核心业务逻辑解耦,使我们能够更轻松地收集和分析有关应用程序性能的重要数据。当然,实际的监控系统会更加复杂和全面,本文的demo仅作为个人学习的记录。