應用情境

服務在運行過程中,難免出現異常情況,有些異常通過重試等手段可以自動回復,有些則不能,嚴重異常甚至會中斷客戶業務。所以我們需要一個系統來記錄這些異常,並且在滿足特定的條件時觸發警示。傳統方法是列印檔案日誌,通過收集日誌到特定的系統,例如開源的ELK(Elasticsearch, Logstash, Kibana)中。 這些開源的系統往往是由多個複雜的分布式系統組成,自我維護面臨著技術門檻高、成本高的問題。 Cloud Monitor提供了一個事件監控功能,能很好解決這些問題。

下面通過幾個例子簡單說明下如何使用事件監控功能。

應用案例

  1. 上報異常

    事件監控提供了JAVA SDK和Open API兩種上報資料的方式,這裡介紹通過JAVA SDK 上報資料。

    1. 添加 Maven 依賴
      <dependency>
          <groupId>com.aliyun.openservices</groupId>
          <artifactId>aliyun-cms</artifactId>
          <version>0.1.2</version>
      </dependency>
    2. 初始化SDK
      // 這裡的118代表Cloud Monitor的應用分組ID,可以以應用的角度來對事件歸類, 可以到Cloud Monitor應用分組列表中查看分組的ID。
      CMSClientInit.groupId = 118L;
      // 這裡的地址是事件系統上報的入口,目前是公網地址。accesskey和secretkey用於身份識別。
      CMSClient c = new CMSClient("https://metrichub-cms-cn-hangzhou.aliyuncs.com", accesskey, secretkey);
    3. 是否非同步上報資料

      Cloud Monitor事件預設提供了同步的上報策略。 好處是編寫代碼簡單、 保證每次上報事件的可靠,不遺失資料。

      但是同步策略也帶來一些問題。因為要在業務代碼中嵌入事件上報代碼,如果網路出現波動,可能會出現阻塞代碼執行,影響正常的業務。有很多業務情境並不需要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;
          }
          // 批量上報事件到Cloud Monitor, 這裡並未重試, SDK也沒有重試, 如果對事件可靠度要求高需要自己加重試策略。
          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);
          }
      }
    4. 事件上報Demo
      • Demo1:http controller的異常監控

        主要目的是監控http請求是否有大量異常,如果每分鐘異常次數超過一定數量就警示。實現原理是通過spring的攔截器或者servlet filter等技術對HTTP請求攔截,如果出現異常就記錄日誌,最後通過配置警示規則來達到警示的目的。

        上報事件的demo如下:
        // 每個事件應該有豐富的資訊來協助我們搜尋和定位問題,這裡使用的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)
      • Demo2:後台定時任務執行情況的監控與訊息消費情況的監控

        同上面的http事件,有很多類似的業務情境需要警示,例如背景工作與訊息佇列消費等,都可以通過類似的方式上報事件達到監控的目的。當異常發生時,第一時間收到警示。

        //訊息佇列的事件組織:
        Map<String, String> eventContent = new HashMap<String, String>();
        eventContent.put("cid", consumerId);  // 代表消費者的身份
        eventContent.put("mid", msg.getMsgId()); // 訊息的id
        eventContent.put("topic", msg.getTopic()); // 訊息的主題,
        eventContent.put("body", body); // 訊息的主體
        eventContent.put("reconsume_times", String.valueOf(msg.getReconsumeTimes())); // 訊息失敗重試的次數
        eventContent.put("exception", e.getClass().getName()); // 發生異常時的異常類名
        eventContent.put("error", e.getMessage()); // 異常資訊
        eventContent.put("stack_trace", ExceptionUtils.getStackTrace(e)); // 異常堆棧
        // 最後上報事件
        put("metaq_error", JsonUtils.toJson(eventContent));

        上報後查看事件



      • 對隊列訊息消費異常設定警示



      • Demo 3:記錄重要事件

        事件還有一種使用情境是用來記錄一些重要的業務發生,但是不需要警示,方便日後翻看。 例如重要業務的動作記錄,改密碼,修改訂單,異地登入等。