云监控自定义事件功能可以帮助您实现系统在运行过程中出现异常时,自动记录异常情况并在满足特定条件时发送报警通知。

背景信息

服务在运行过程中,难免出现异常情况,有些异常通过重试等方法可以自动恢复,有些则不能,严重异常甚至会中断客户业务。因此需要一个系统来记录这些异常,且在满足特定的条件时触发报警。传统方法是打印日志文件,并收集日志到特定系统,例如开源的ELK(Elasticsearch、Logstash和Kibana)。 这些开源系统由多个复杂的分布式系统组成,自行维护面临着技术门槛高、成本高的问题。 云监控提供的事件监控功能,能够很好的解决这些问题。

准备工作

自定义事件监控提供了Java SDK和OpenAPI两种上报数据的方式,本文为您介绍通过Java SDK上报异常数据的处理方法。

  1. 添加Maven依赖。
    <dependency>
        <groupId>com.aliyun.openservices</groupId>
        <artifactId>aliyun-cms</artifactId>
        <version>0.1.2</version>
    </dependency>
  2. 初始化SDK。
    //118代表云监控的应用分组ID,可以从应用的角度来对事件进行归类, 可以在云监控的应用分组列表中查看分组ID。
    CMSClientInit.groupId = 118L;
    //地址是事件系统上报的入口,目前是公网地址。accesskey和secretkey用于身份识别。
    CMSClient c = new CMSClient("https://metrichub-cms-cn-hangzhou.aliyuncs.com", accesskey, secretkey);
  3. 是否异步上报数据。

    云监控事件默认提供了同步的上报策略。 虽然编写代码简单,但是可以保证每次上报事件的可靠性,不丢失数据。

    同步策略存在的问题:因为要在业务代码中嵌入事件上报代码,如果网络出现波动,可能会阻塞代码执行,影响正常的业务。由于部分场景无需事件100%不丢失,因此需要异步上报封装,先将事件写入LinkedBlockingQueue,再通过ScheduledExecutorService异步批量上报。

    //初始化queue与Executors:
    private LinkedBlockingQueue<EventEntry> eventQueue = new LinkedBlockingQueue<EventEntry>(10000);
    private ScheduledExecutorService schedule = Executors.newSingleThreadScheduledExecutor();
    //上报事件:
    //每一个事件都包含事件的名称与事件的内容,名称用于识别事件,内容是事件的详细信息,支持全文搜索。
    public void put(String name, String content) {
        EventEntry event = new EventEntry(name, content);
        //事件队列满后直接丢弃,可以根据自己的情况调整该策略。
        boolean b = eventQueue.offer(event);
        if (!b) {
            logger.warn("事件队列已满,丢弃事件:{}", event);
        }
    }
    //异步提交事件,初始化定时任务,每秒执行Run方法批量上报事件。您可以根据自己的情况调整上报间隔。
    schedule.scheduleAtFixedRate(this, 1, 1, TimeUnit.SECONDS);
    public void run() {
        do {
            batchPut();
        } while (this.eventQueue.size() > 500);
    }
    private void batchPut() {
        //从队列中取出99条事件,用于批量上报。
        List<CustomEvent> events = new ArrayList<CustomEvent>();
        for (int i = 0; i < 99; i++) {
            EventEntry e = this.eventQueue.poll();
            if (e == null) {
                break;
            }
            events.add(CustomEvent.builder().setContent(e.getContent()).setName(e.getName()).build());
        }
        if (events.isEmpty()) {
            return;
        }
        //批量上报事件到云监控且未重试。如果您对事件可靠度要求高,则需要自己加重试策略。
        try {
            CustomEventUploadRequestBuilder builder = CustomEventUploadRequest.builder();
            builder.setEventList(events);
            CustomEventUploadResponse response = cmsClient.putCustomEvent(builder.build());
            if (!"200".equals(response.getErrorCode())) {
                logger.warn("上报事件错误:msg: {}, rid: {}", response.getErrorMsg(), response.getRequestId());
            }
        } catch (Exception e1) {
            logger.error("上报事件异常", e1);
        }
    }

上报异常事件模板

  • 模板1:HTTP Controller的异常监控

    主要监控HTTP请求是否有大量异常,如果每分钟异常次数超过指定数量,则报警。

    实现原理:通过Spring拦截器或Servlet filter技术对HTTP请求拦截,如果出现异常就记录日志,通过配置报警规则来实现报警。

    上报事件的模板如下。

    //每个事件都有丰富的信息来帮助我们搜索和定位问题,这里使用Map来组织事件,最后将其转换成JSON格式作为事件的Content。 
    Map<String, String> eventContent = new HashMap<String, String>();
    eventContent.put("method", "GET");  // HTTP请求方法。
    eventContent.put("path", "/users"); // HTTP的Path。
    eventContent.put("exception", e.getClass().getName()); //异常类名,方便搜索。
    eventContent.put("error", e.getMessage()); //异常报错信息。
    eventContent.put("stack_trace", ExceptionUtils.getStackTrace(e)); // 异常堆栈,方便定位问题。
    //最后使用已封装的异步上报方法提交事件,当前是异步上报,且未重试,可能会小概率丢事件,但已满足HTTP未知异常报警场景。
    put("http_error", JsonUtils.toJson(eventContent));
    ![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/864cf095977cf61bd340dd1461a0247c.png)
  • 模板2:记录重要事件

    自定义事件监控用来记录重要的业务发生情况,但是不需要报警,方便日后查看。例如:重要业务的操作日志、修改密码、修改订单、异地登录等。

    查看重要事件