美文网首页
方法监控实现及应用

方法监控实现及应用

作者: 西城丶 | 来源:发表于2021-04-28 17:42 被阅读0次

需求

现有的支付支持多种通道,现需要增加一种通道,并对这个通道的支付请求做监控,如果异常超过了一定的比例,那么这个通道就要变为不可用,采用其他的通道。

分析

这个需求,简单来说就是需要对发起支付请求的http接口进行监控,如果监控出现了异常,可以修改一下redis的标志位,在取通道那一步,去判断这个标志位,如果标志位为异常,那么就不返回这个通道。所以问题在于对发起http的方法如何做监控,初步分析有以下几个难点:

  • 如何做统计,如果对监控数据进行统计,多少时间窗口内异常比例超过多少才算是异常
  • 发起请求的方法可能被多个地方使用(一般对接外部的接口,都会封装统一方法),比如说支付的时候会调,查询的时候会调,但是查询是不需要做监控的
  • 支付可以还有细分项,比如这个通道支持微信支付,阿里支付,银联支付,微信支付被置为不可用的时候,阿里支付其实是可以用的,所以不能把阿里的支付置为不可用,也就是说支付需要做细分监控
  • 置为不可用的时候,需要告警出来

实现

需要对方法进行监控,第一个想到的是采用AOP实现,并结合自定义注解。

自定义注解

根据上面的分析,我们的自定义注解可以这样设计

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {

   /**
    * 监控枚举决定需不需要监控
    */
   MonitorType type() default MonitorType.DEFAULT;

   /**
    * 异常比例 默认不配置
    */
   int percent() default -1;

   /**
    * redis的key值
    */
   String key() default CacheConstant.USER_MONITOR_TYPE_KEY;

}
public enum MonitorType {
   /**
    * 支付监控
    */
   CHA_UMS_WX_PAY("微信支付监控","CHINAUMS.MOP.WXPAY",true),
   CHA_UMS_ALI_PAY("阿里支付监控","CHINAUMS.MOP.ALIPAY",true),
   CHA_UMS_BARCODE_PAY("扫码支付监控","CHINAUMS.MOP.BARCODEPAY",true),
   CHA_UMS_QUERY("查询监控","CHINAUMS_QUERY_TIMEOUT",false),
   CHA_UMS_CALLBACK("回调监控","CHINAUMS_PAY_TIMEOUT",false),
   DEFAULT("默认监控",CacheConstant.MONITOR_ERROR_FLAG,false);

   private String desc;
   private boolean open;
   private String key;

   MonitorType(String desc, String key, boolean open) {
      this.desc = desc;
      this.open = open;
      this.key = key;
   }
   // 省略get
}

监控,我们需要三个字段就行

  • type(监控类型),监控类型来决定这个接口需不需要开启监控,监控类型里有分为三个字段
    • desc,描述字段,这个主要用来告警提示的文字,方便阅读
    • key,监控异常的时候,如果外层的key设置了使用内部的key,那么会取这个key值作为redis缓存的标志位
    • open,是否开启监控,像支付是需要做监控,但是回调是不用做监控,由这个决定
  • percent(监控异常比例),这里采用的是一个接口一个监控比例配置,没有做细分项,对于一种类型,可以有个独立的监控比例(目前没做)
  • key(redis标志位的key值),默认采用type里面各自的key作为标志位的key,异常时修改redis的key值

AOP实现

@Aspect
@Component
@Order(-10)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MonitorAspect {

   private Logger logger = LoggerFactory.getLogger(this.getClass());

   @Around("@annotation(com.*.*.common.aop.annotation.Monitor)")
   public Object around(ProceedingJoinPoint joinPoint)  throws Throwable {
      // 当前环境拥有监控注解,直接执行监控
       MonitorType monitorType = MonitorHolder.getMonitor();
      Class<?> target = joinPoint.getTarget().getClass();
      MethodSignature signature = (MethodSignature)joinPoint.getSignature();
      Monitor monitor = beforeGetMonitor(target, signature.getMethod());
      if ( monitorType == null ){
         monitorType = monitor.type();
         MonitorHolder.setMonitor(monitor.type());
      }
      long startTime = System.currentTimeMillis();
      FastCompass fastCompass = null;
      if ( monitorType.isOpen() ){
         fastCompass = MetricManager.getFastCompass("pc", MetricName.build(monitorType.name()).level(MetricLevel.CRITICAL));
      }
      Object proceed = null;
      try{
         proceed = joinPoint.proceed();
         return proceed;
      }catch (Exception e){
         if ( e instanceof MonitorException ){
            // 如果是监控异常,增加异常数量
            if ( monitorType.isOpen() ){
               fastCompass.record(System.currentTimeMillis() - startTime, MetricCategory.ERROR.name());
            }
         }
         throw e;
      }finally{
         // 执行统计
         if ( monitorType.isOpen() ) {
             fastCompass.record(System.currentTimeMillis() - startTime, MetricCategory.TOTAL.name());
            boolean openMonitorError = checkMonitor(fastCompass, monitor);
            // 监控异常开启指定redis标志位
            if ( openMonitorError ){
               String key = monitor.key();
               // 采用监控类型的key值
               if ( StringUtils.hasText(key) && CacheConstant.USER_MONITOR_TYPE_KEY.equals(key) ){
                  key = monitorType.getKey();
               }
               if ( StringUtils.hasText(key) ){
                  CacheContext.set(CacheConstant.MONITOR_PREFIX + key,CacheConstant.MONITOR_ERROR_FLAG);
                  logger.error("监控【{}】出现异常,修改redis值【{}】", monitorType.getDesc(),(CacheConstant.MONITOR_PREFIX + key));
               }else{
                  logger.error("监控【{}】出现异常,但是没有设置redis标志位操作,不修改redis值", monitorType.getDesc());
               }

            }
         }
         //使用完清空
         MonitorHolder.removeMonitor();
      }
   }

   private boolean checkMonitor(FastCompass fastCompass,Monitor monitor) {
      try {
         Map<String, Map<Long, Long>> map = fastCompass.getMethodCountPerCategory();
         //  全局的监控比例,如果当前注解有自定义的比例值,优先采用注解上面的比例值
         int percent = 80;
         if ( monitor.percent() != -1 ){
            percent = monitor.percent();
         }else{
            String globalMonitorPercent = CacheContext.getString(CacheConstant.MONITOR_PERCENT);
            if ( globalMonitorPercent != null && StringUtils.hasText(globalMonitorPercent) ){
               percent = Integer.valueOf(globalMonitorPercent);
            }
         }
         long blockCount = 0, totalCount = 0;
         if (!map.containsKey(MetricCategory.ERROR.name())) {
            return false;
         }
         if (!map.containsKey(MetricCategory.TOTAL.name())) {
            logger.debug("没有总记录数据");
            return false;
         }
         Map<Long, Long> blockMap = map.get(MetricCategory.ERROR.name());
         for (Long key : blockMap.keySet()) {
            blockCount += blockMap.get(key);
         }
         Map<Long, Long> totalMap = map.get(MetricCategory.TOTAL.name());
         for (Long key : totalMap.keySet()) {
            totalCount += totalMap.get(key);
         }
         logger.error("{}-{}", blockMap, totalMap);
         if (totalCount == 0) {
            logger.error("总记录数等于0");
            return false;
         }
         double blockPercent = blockCount * 100.0 / totalCount;
         boolean needOpen = blockPercent > percent;

         if ( needOpen ){
            // 发送告警
            StringBuilder contentStr = new StringBuilder();
            contentStr.append("**接口【" + MonitorHolder.getMonitor().getDesc() +"】异常!**\n");
            contentStr.append(String.format("> 异常比例:**%s**\n",blockPercent));
            contentStr.append(String.format("> 主机IP:**%s**\n",IPUtil.getLocalIp()));
            contentStr.append(String.format("> 告警时间:**%s**\n",LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)));
            contentStr.append(String.format("> redis标志位:**%s**\n",MonitorHolder.getMonitor().getKey()));
            WxWorkWarnMsgDTO wxWorkWarnMsgDTO = WxWorkWarnMsgDTO.builder()
                  .withCategory("05")
                  .withColor(WarnMsgLevel.ERROR.getColor())
                  .withContent(contentStr.toString()).buildMarkdown();
            ProducerSendMessageFacade.sendPulsarMajorMsg("cp-comm/msg-daemon/warn-msg-push",JSONObject.toJSONString(wxWorkWarnMsgDTO));
         }
         logger.info("监控【{}】的异常配置比例:{},当前比例:{},{}/{},是否需要开启:{}",MonitorHolder.getMonitor().getDesc(), percent, blockPercent, blockCount, totalCount, needOpen);
         return needOpen;
      } catch (Exception e) {
         logger.error("判断是否监控异常出现异常:", e);
      }
      return false;
   }

   private Monitor beforeGetMonitor(Class<?> target,Method method){
      Monitor monitor = null ;
      //从类初始化
      monitor = getMonitor(target, method) ;
      //从接口初始化
      if(monitor == null){
         for (Class<?> clazz : target.getInterfaces()) {
            monitor = getMonitor(clazz, method);
            if(monitor != null){
               //从某个接口中一旦发现注解,不再循环
               break ;
            }
         }
      }
      return monitor;
   }

   /**
    * 获取方法或类的注解对象DataSource
    * @param target      类class
    * @param method    方法
    * @return DataSource
    */
   private Monitor getMonitor(Class<?> target, Method method){
      try {
         //1.优先方法注解
         Class<?>[] types = method.getParameterTypes();
         Method m = target.getMethod(method.getName(), types);
         if (m != null && m.isAnnotationPresent(Monitor.class)) {
            return m.getAnnotation(Monitor.class);
         }
         //2.其次类注解
         if (target.isAnnotationPresent(Monitor.class)) {
            return target.getAnnotation(Monitor.class);
         }

      } catch (Exception e) {
         logger.error(MessageFormat.format("通过注解注册监控时发生异常[class={0},method={1}]:"
               , target.getName(), method.getName()),e)  ;
      }
      return null ;
   }
}

这个aop的实现就是监控的所有操作,来看下上面分析的几个问题如何解决

  1. 监控数据进行统计

    这里采用的是阿里的一个开源包实现,github地址是这个https://github.com/alibaba/metrics/wiki/quick-start,采用的是里面的FastCompass,FastCompass能够方便的统计某个业务接口的吞吐率, 响应时间, 成功率, 错误率,命中率。

  2. 调方法有些不需要监控,有些需要监控

    可以看到aop最开始的代码里面,有一段MonitorType monitorType = MonitorHolder.getMonitor();,也就是说在调用支付之前,设置下当前线程的变量,持有一个监控类型就可以了,具体使用:

    //调用之前设置监控类型
    MonitorHolder.setMonitor(MonitorType.CHA_UMS_BARCODE_PAY);
    // 调用监控的方法
    String result =  chinaumsMopUtil.postJSON(pcCommonTppAccount.getTradeUrl(), requestJson, headers);
    
    @Monitor(type = MonitorType.DEFAULT)
    public String postJSON(String url,String jsonStr ,Map<String,String> headers) {
    }
    
  3. 支付细分项的监控

    这个简单,我们在监控类型里做细分就行了,微信支付,阿里支付,弄成单独的类型

  4. 告警

    在上面的代码中,如果需要告警,目前做的比较简单,采用的是企业微信机器人告警。

至此,方法的监控就已经全部实现了,支付通道那一层,判断下通道对应的标志位就行了。

相关文章

  • 方法监控实现及应用

    需求 现有的支付支持多种通道,现需要增加一种通道,并对这个通道的支付请求做监控,如果异常超过了一定的比例,那么这个...

  • 监控主机及联网状态

    监控主机及联网状态监控目的监控方法监控思路监控实现一、监控目的掌握在线业务机器及联网的状态 二、监控方法通过采用I...

  • 金笛短信猫应用于IT运维软件

    广通软件集成应用金笛M1203A型号短信猫及WEB中间件用于监控工具层,提供网络监控、系统监控、应用监控、云监控等...

  • 学习的思路

    一、学习策略1、策略必须在执行之前明确好。2、策略通过随时监控的方式随时调整,方法是“每日监控及记录应用某种策略并...

  • 监控端口状态

    监控主机服务状态监控目的监控方法监控实现一、监控目的实时掌握线上机器服务状态,保证服务正常运行 二、监控方法采用t...

  • Android卡顿监控

    实现思路 卡顿监控主要监控:慢方法的监控、ANR的监控、掉帧的监控。其实现方案主要有三种: Looper的Prin...

  • asmmonitor学习

    本文主要是分析tomcat应用监控插件——asmMonitor的实现原理和配置。asmMonitor部署在被监控的...

  • 金笛八网口猫池应用南车通信信号装备

    株洲南车应用金笛网口猫池及WEB中间件软件实现在下属各路段实现短信监控预警平台,实现电表数据的无线数据传输具有可充...

  • 监控系统及zabbix介绍

    监控系统的原理探究 监控系统的实现监控系统往往需要对物理硬件和应用软件的性能和参数进行数据汇集,实现集中管理和统一...

  • go-kit 微服务 服务监控(prometheus 实现)

    go-kit 微服务 服务监控(prometheus 实现) 实现对登录方法的请求次数,与请求耗时进行监控 pro...

网友评论

      本文标题:方法监控实现及应用

      本文链接:https://www.haomeiwen.com/subject/ltzvrltx.html