1. 程式人生 > >餓了麼物流移動端業務可用性監控體系建設

餓了麼物流移動端業務可用性監控體系建設

作者簡介

錦洋,負責餓了麼蜂鳥APP的架構、研發等工作。目前關注的技術方向為移動端監控、移動端架構、移動端效能優化等方向。

在這個重視穩定性的年代,很多公司在移動端效能監控上花了很大的力氣,對業務可用性監控的投入不足,但是移動端可用是由效能可用和業務可用共同組成,缺一不可,因為業 界效能監控已經比較成熟,有很多第三方的平臺,所以避開效能監控不談,下面介紹一下餓了麼物流移動端在業務可用性監控體系建設上的一些探索。

餓了麼物流移動端作為騎手直接使用的配送工具,需要每天承載千萬量級的配送單量,騎手app具備以下三個特點:時效要求高,網路環境複雜,重度使用。騎手需要在30分鐘內將訂單配送到使用者手中,中間實施多次訂單操作,可謂爭分奪秒,如果遇到網路或者定位異常都可能導致有損操作,為了保證騎手的操作順暢,我們需要將騎手整個配送過程納入可用性監控體系建設中,經過長期的探索,我們建立一套自己的移動端業務監控體系。

整個監控體系就像一個數據漏斗:

第一層 E-Monitor:全域性業務監控,縱觀全域性,掌握業務大盤趨勢;

第二層 TimeBomb:異常事件監控,定點插針,實時報警;

第三層 Dogger:單點日誌監控,全量日誌,還原現場;

第四層 EDW:離線大資料,T+1報表,大資料分析業務健康度;

下面給大家詳細介紹一下這四層監控

E-Monitor

大家都知道作為移動端本身不需要對介面監控敏感,因為後端有各種維度的API服務監控,但是App作為上層應用,介面的成功失敗,並不能完全替代使用者的感知,這是有呼叫方的特徵決定的。一次業務請求包含:準備請求資料->傳送請求->網路鏈路->請求回撥->解析->渲染,任何一個環節的失敗都可能代表著使用者的一次互動失敗,所以要想完全掌控線上大盤的核心功能使用曲線,完全依賴後端介面監控是不行的,必須要梳理呼叫鏈路,搭建客戶端業務監控。

作為全域性業務監控只看單個使用者的資料是沒有什麼意義的,需要將所有使用者的資料採集,儲存,視覺化。這裡資料的採集我們使用餓了麼MT部門提供的Skynet作為採集和上傳通道,它具備編譯期AOP插入,序列化儲存,針對移動端優化的對齊上傳等特性,保證了資料採集上傳的可靠,穩定。

而在服務端的儲存和視覺化我們採用LinDB+E-Monitor的監控架構,LinDB是一款優秀的時間序列資料庫,適合儲存裝置效能、日誌等帶時間戳的資料。能輕鬆處理高寫入和高查詢負載。配合E-Monitor強大的視覺化能力,可以完美展現騎手訂單操作主流程的穩定情況,異常報警。

報警的策略有以下幾種:移動端常用的是閾值模型和趨勢模型配合同環比

最終生成一個大盤的監控面板,這裡因為銘感資料只放出了部分脫敏面板

TimeBomb

TimeBomb-定時炸彈,從名字就可以猜到它和異常有關,它專門負責監控在規定時間和次數限定下沒有達成使用者互動結果的邏輯,TimeBomb作為全域性業務監控的補充,在排查異常中立下汗馬功勞。它的設計初衷是通過簡單的程式碼插入,由計數,時間間隔等條件觸發異常事件上傳, 適用於:

  1. 登入多次登入不上

  2. 多次點選確認送達,都失敗

  3. 定位一直報錯

  4. 定位上傳多次失敗

  5. 任何接⼝口多次報錯

等等...

總結來說,TimeBomb可以隨意定義異常的監控力度,並且可以靈活的遠端配置次數,時間,取樣率。

TimeBomb的資料採集和展示是通過我們自研的服務,主要包括兩個功能:

  1. 根據選擇的Tag和時間顯示異常曲線
  2. 選擇節點之後就可以檢視異常明細列表 在Parameter中檢視異常上報的次數和時間配置還有上報的原因,點選日誌拉取就可以拉取到使用者的詳細日誌資料。

騎手App通過TimeBomb完成了很多異常問題的上報,修復,觀測,優化,再觀測,這是一個異常問題解決的正向迴圈,而且特別適合一些需要多輪驗證的極端case的排查觀測。

Dogger

Dogger包含兩個部分:

  1. Trojan日誌寫入上傳SDK

  2. Dogger-Service 日誌解析服務

Trojan是一個面對高效能,極致體驗要求下,產出的輕量級,高效率的移動端日誌監控方案,它就像一隻聽話的狗狗,他在客戶端默默的記錄著使用者的各項操作日誌和技術性能埋點,最終在需要的時候,把日誌拋上來,交給Dogger-Service解析,通過完善的埋點,我們可以很快的還原騎手的操作現場,藉助對特定日誌的橫向分析,可以幫我們快速定位問題。

Trojan具備以下四個特點:

  • 第一點:開發透明,使用者不需要關心日誌檔案的讀寫。
  • 第二點:高效收集,一.採用AOP技術植入到埋點自動收集日誌,二.高效的檔案讀寫方式,毫秒級別的耗時。
  • 第三點:敏感加密,對於使用者相關的敏感資訊,為保證日誌的安全性,我們提供加密方式,比如說DES、AES。
  • 第四點:流量開銷低,日誌寫在本地,指定使用者壓縮上傳。

Trojan的架構圖:

Trojan 用C的方式通過mmap(記憶體對映)的方案寫入日誌,對比java api的寫入方式效能提高了一倍,低CPU,低記憶體消耗。

在效能監控這種大資料量寫入的場景上滿足了我們的需求,再配合檔案的gzip壓縮可以將日誌這種多重複字母的檔案達到50倍的壓縮效果,實測一個43M的檔案,壓縮上傳只要860kb。1M以內的檔案上傳對移動端來說也是一個可以接受的大小,這也對未來trojan除了完成邏輯回放,提供了可能。

經過三個版本的迭代,Trojan已經涵蓋了使用者的點選事件,頁面生命週期,請求監控,流量,電量,記憶體,執行緒等方方面面, 檔案的寫入和上傳都完成了,那一個幾十M的檔案該如何分析尼?下面就介紹一下我們的Trojan配套解析服務Dogger-Service。

Dogger-Service 主要的功能分為三個部分:

  1. ActionChart-全域性展示騎手一天的頁面跳轉,電量,記憶體,網路切換,定位頻率,特定請求頻率等

通過ActionChart我們可以直觀的看出騎手在時空座標系下的操作和資源使用情況,可以方便協助我們觀察某個時間點出現某個問題的環境,這種全域性掌控使用者操作可能是行業內第一次達到。

  1. Origin -可以輕鬆的完成百兆以內的檔案解析和展示,按時間查詢,全域性高亮搜尋等功能

通過對原始資料解析,我們可以拖拽時間滑片,直接定位到某個時間段檢視騎手的日誌明細,也可以選擇某個關注的Tag,或者直接通過關鍵字搜尋高亮查詢,Origin模組讓我們可以靈活的查詢問題的蛛絲馬跡,給定位問題的root cause提供了保障。

這樣就夠了嗎?

  1. Statistics - 統計模組是基於特定的Tag資料,資料探勘分析和展示。

當前實現了對電量,網路,流量,卡頓,請求,生命週期,記憶體,定位的資料分析。比如下面的記憶體分析,我們可以通過最長間隔,知道騎手有哪些時間段app是關閉著的,記憶體的峰值和低谷,平均記憶體各是多少,記憶體波動比較大的時間段是哪幾個,波動大代表著資源開銷可能異常,是需要仔細排查的點。

可以Loc Tag檢視騎手的定位軌跡,分析是否有定位漂移或者定位失敗情況

還可以通過PunchLoc Tag檢視定位上傳的失敗佔比,分析失敗的原因是否和當時的網路狀態有關

通過THttpReq可以檢視網路請求的Host和Path佔比情況,方便優化請求流量

Trojan和Dogger-Service組成了Dogger這個有機的整體,日誌和解析配合,可以讓我們在排查單個case的時候,對使用者的行為了如指掌,豐富的埋點資料可以為我們的排查提供資料支撐。

目前Dogger服務中的日誌寫入sdk Trojan已經開源,歡迎交流學習

當我們有了實時的全域性大盤和異常監控,還有單個使用者的全週期日誌資料,就夠了嗎?

大盤的曲線正常,異常的毛刺消除只能代表業務大盤穩定,但是業務功能真實的質量還不能一概而論,這時我們需要對資料漏斗的終點---離線資料池進行大資料探勘分析來做最後的監控兜底。

下面介紹一下最後一層監控EDW

EDW:

離線報表監控作為全域性大盤的另一種視角,E-Monitor屬於實時大盤監控,只能觀察實時曲線趨勢和昨天做對比,判斷粗粒度的業務是否異常,但是離線資料可以挖掘分析完整一天的資料,細粒度的判斷每一個訂單的健康程度,聚合定位失敗的原因佔比,獲取複雜條件篩選出的各種比例,讓我們從上帝視角觀察整個業務線,評估線上業務健康度,分析趨勢,表徵產出,是移動端監控體系中不可或缺的利器。

公司大資料平臺部自研的edw為我們提供了優質的離線大資料服務,它融合了即時查詢、資料抽取、資料計算、資料推送、元資料管理、資料監控等多種資料服務的平臺型產品。

當前我們在流量,定位質量,騎手多裝置使用,離線送達,推送質量,訂單異常等關鍵業務場景都有完備的離線報表。比如上圖的流量報表,可以知曉線上流量消耗Top 100的騎手device_id 和流量資料,而排行第一的騎手response資料遠大於request資料,通過Dogger拉日誌後發現,騎手有多次下載app的行為。第二幅圖則是線上主流程的偏向業務的流轉時長監控,因為資料敏感所以打碼了。這些報表可以說明線上業務的真實健康度,這一點能夠讓我們對全域性的把控更有自信。基於離線資料的聚合分析,可以發現優化點,為改善方案提供依據。

實戰

這裡記錄一下最近發生的一次網路層問題的排查過程,讓大家直觀感受這幾層監控的作用。

第一步:我們的gafana的監控發現Android騎手的訂單相關請求平均成功率降低到了98.69%,而正常請求成功率應該在99%以上

上面說到grafana屬於可用性全域性監控,如果這邊的資料異常,將會影響全盤,所以我們不敢怠慢,立馬著手排查。

首先我們懷疑是DNS解析問題,我們通過EDW拉取了出現問題騎手的id,然後配置了Dogger的騎手日誌拉取,經過分析發現,DNS失敗的場景多發生在斷網等弱網環境,屬於正常情況,而且我們發現日誌上出問題的請求的requestID在後端的trace系統上都查不到,查看了skynet網路監控攔截器的程式碼

apmNetInterceptor插在最後一個,資料沒有傳上去,說明請求在傳送前就已經拋了錯,所以我們開始排查請求傳送前的邏輯。 第二步:通過EDW抽取出現問題騎手,對他們的請求失敗原因聚合,得到了ioException異常佔比最大

第三步:由於請求前的日誌資料過少,所以我們升級了okhttp到3.11,使用EventListener來獲取請求生命週期埋點,針對上報問題的騎手發了內測版本,希望獲得出問題請求的鏈路明細。

完整的鏈路大致如下:

再次撈出有問題騎手的日誌,發現有些時候網路狀態是良好的,但是在 responseHeaderStart之後會直接拋錯或者是 timeout:

於是我們擼了多遍okhttp的原始碼,覺得應該是連線池複用的問題,複用了已經失效的連線. 我們又加入 IOException 的 stacktrace日誌.發現一個奇怪的問題:

線上的請求走的竟然是http/2的協議,仔細閱讀Okhttp 握手相關的程式碼發現,Okhttp 在 https的情況下會判斷服務端是否支援 http/2,如果支援則會走 http/2的協議,相關程式碼參見RealConnection.java的establishProtocol方法。

 private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        socket = rawSocket;
        protocol = Protocol.H2_PRIOR_KNOWLEDGE;
        startHttp2(pingIntervalMillis);
        return;
      }
      socket = rawSocket;
      protocol = Protocol.HTTP_1_1;
      return;
    }
    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);
    if (protocol == Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis);
    }
  }

複製程式碼

最終發現的確是 Okhttp在 http/2上對連線池的複用問題存在 bug ,在StreamAllocation.java上

 public void streamFailed(IOException e) {
    boolean noNewStreams = false;
    synchronized (connectionPool) {
      if (e instanceof StreamResetException) {
      } else if (connection != null
          && (!connection.isMultiplexed() || e instanceof ConnectionShutdownException)) {
        noNewStreams = true;
      }
      socket = deallocate(noNewStreams, false, true);
      }
  }

複製程式碼

當協議是http/的時候,noNewStreams為false 而在ConnectionPool.java的connectionBecameIdle就不會將這個connection從ConnectionPool中移除

  boolean connectionBecameIdle(RealConnection connection) {
    if (connection.noNewStreams || maxIdleConnections == 0) {
      connections.remove(connection);
      return true;
    } else {
    }
  }
複製程式碼

結合前段時間,後端將路由層切換了公司的SoPush服務上,而SoPush是支援Http/2的,切換的時間和曲線異常時間吻合,可以確定問題就在這裡。

線上使用的Okhttp版本還是3.8.4, 在Okhttp 3.10.0版本之後,加入了對http2的連線池中的連線做了嚴格的ping驗證, 下面是 changelog

可以看到 http/2 才剛加Ping機制,所以OKhttp對Http/2支援有問題的版本是<3.10,但是即使使用了3.11的最新版本依舊有一定概率發生這個問題.於是我們覺得先強制指定Android版本的協議為http/1.1,後面接入集團的網路庫再支援Http/2。修改完再次釋出內測版本,曲線恢復正常,問題解決。

這次網路層的排查,我們使用E-Monitor監控和分析問題嚴重程度,EDW離線資料過濾出問題騎手ID,Dogger單點日誌靈活埋點驗證修復方案,就這樣一次由第三方變動引起的客戶端可用性異常就這樣解決了,業務可用性監控功不可沒。

總結:

上面就是餓了麼物流移動當前在業務可用性監控領域做出的一些探索,我們按照資料漏斗,從全量埋點,異常監控,單使用者日誌,離線資料,由面到點再到面,每一個切面都插入了不同緯度的監控,希望能做到全面覆蓋業務,穩定性和質量兼顧,因為我們深信,只有做好監控,才能做到可報警,可排查,可優化。但是移動端可用性監控是一個長期建設的工程,需要不斷的優化迭代,當前我們也面臨資料冗餘和效能監控衝突,監控本身帶來的效能損耗等問題,未來我們也將在這些問題上做進一步探索、實踐和分享。




閱讀部落格還不過癮?

歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動

部落格轉載、線下活動及合作等問題請郵件至 [email protected] 進行溝通