日志系統之基于Zookeeper的分布式協同設計

jopen 8年前發布 | 19K 次閱讀 分布式/云計算/大數據 ZooKeeper

最近這段時間在設計和實現日志系統,在整個日志系統系統中Zookeeper的作用非常重要——它用于協調各個分布式組件并提供必要的配置信息和元 數據。這篇文章主要分享一下Zookeeper的使用場景。這里主要涉及到Zookeeper在日志系統中的使用,但其實它在我們的消息總線和搜索模塊中 也同樣非常重要。

日志元數據

日志的類型和日志的字段這里我們統稱為日志的元數據。我們構建日志系統的目的最終主要是為了:日志搜索,日志分析。這兩大塊我們很大程度上依賴于——ElasticSearch(關于什么是ElasticSearch,這里就不多做介紹了)。

關于日志字段

日志的字段定義,在ElasticSearch中是一個索引中某個mapping的Schema。ElasticSearch是一個號稱Schema Free的分布式全文檢索系統。這里需要正確地理解Schema Free,它并不是不需要Schema,也不是強制要求你必須有明確的Schema,有沒有都可以做全文檢索。但是像聚合、分析等很多高級功能都建立在明確的Schema的基礎上。因此,從分析統計的角度看,我們對日志進行明確的字段定義是很有必要的。

日志與搜索模塊的協同

日志元數據是日志系統的基礎信息,我們在web管控臺管理它并同步至Zookeeper供其他模塊使用,比如搜索模塊。因為上文提到日志的類型、字段其實跟ElasticSearch的Mapping Type是 對等的映射關系,所以搜索模塊會重度依賴日志元數據。另外,為了保證新的日志元數據(通常是一個新的日志類型被創建)盡快同步至 ElasticSearch,我們利用了Zookeeper的事件Push機制(或者叫Pub/Sub機制)來實時獲知日志元數據的變化。一旦有新的日志 元數據產生,我們的搜索模塊會立即得到事件通知,它會獲取最新的日志元數據,然后為其在ElasticSearch的indices中新建一個 mapping,為這種日志類型的日志存入ElasticSearch做好準備。

這種方式帶來了哪些好處,目前來看至少有三點:

  • 實時性:搜索模塊能第一時間感知到新的日志類型的創建
  • 低耦合性:管控臺上日志模塊跟搜索模塊,沒有因為信息的依賴而產生較強的耦合性;它們通過Zookeeper進行了解耦
  • Mapping Type的可控性:ElasticSearch有個非常好的特性,就是當你將一個文檔存入某個mapping type,如果該文檔中存在mapping未曾定義的字段,ElasticSearch將會為你自動添加該字段的定義。我們認為這種機制將會使日志字段變 得不可控。因此我們通過統一日志元數據再加上后面基于同樣的解析行為來保證Schema的可控性。
  • </ul>

    日志采集

    從之前的文章中,你應該可以找到日志采集器的選型,出于多種原因(跟消息總線的 集成、可定制性、支持從zookeeper獲取配置等),我們選擇了Flume-ng。它在最新的穩定版(1.6.0)提供了從zookeeper獲取采 集配置的功能。于是,日志采集花費在運維上的成本就大大降低了。因為有了配置之后,運維人員就不需要手動在目標服務器上修改配置文件,完成各種配置,只需 要固定鍵入如下的啟動命令即可:

    sudo bin/flume-ng agent --conf conf -z 127.0.0.1:2181 -p /component/log/mysqlSlowquery/flume -name mysql-slowquery-30 -Dflume.root.logger=DEBUG,console


    而上面這個命令中,唯一需要變動的只有以下幾個部分:

    • zookeeper的服務器(集群)信息
    • 即將收集的日志的類型的flume路徑
    • 即將收集的日志的flume配置的znode名稱,如上例是mysql-slowquery-30
    • </ul>

      其實原先需要手動修改配置文件的部分參數項將在提供的管控臺中進行配置,但基于web的表單填寫顯然要比在服務器上以命令行的方式來得容易得多。

      這里我們的做法是拆解了flume的配置文件,將其固定不變的部分做成模板,將其可變部分做成表單。在提交之前,通過模板引擎將模板跟配置信息進行合并為完整的配置并推送到Zookeeper中去。

      其中需要配置的部分參數有:

      • 日志文件所在目標服務器的路徑位置
      • 日志文件名稱的格式
      • 日志是單行模式還是多行模式
      • 消息源的secret(消息總線部分)
      • 消息槽的名稱(消息總線部分)
      • 消息流的token(消息總線部分)
      • ….
      • </ul>

        日志解析

        同 樣在之前的文章中我也提及我們在日志解析上的選擇是morphline。morphline是個在Hadoop生態系統中的ETL Framework。morphline也有一個配置文件用于定義一系列的Commands。而它也有固定部分和可變部分,因為解析主要應用了 morphline的Grok命令,所以針對這個命令,可變部分主要是:

        • 解析字典
        • 解析的正則表達式
        • </ul>

          我們的做法同樣類似于日志采集模塊,將morphline的配置文件的固定部分做成固定模板,然后將可變部分在管控臺上進行配置,最終合并提交到Zookeeper中。

          日志解析服務(歸屬于下面的后臺服務), 在啟動時會根據自己的日志類型,從Zookeeper的特定節點下找到該日志類型的morphline的配置,將其獲取下來并保存在本地文件系統中,然后 構建Mrophline對象(因為morphline目前只提供基于File對象的構造方式,所以多了一個先保存至本地文件再基于文件構造 Morphline對象的步驟)進行解析。

          后臺服務

          日志解析這邊只是給解析任務提供了 元數據 ,真正的解析由后臺的解析任務來完成,我們將類似的這些允許在后臺的所有任務籠統得歸結為 后臺服務

          后臺服務遵循:任務組->任務->工作線程的層次性的組織方式。按照服務的業務類型(說白了就是同一套處理邏輯),將其劃分為不同的任務組(比如:ETL組、日志索引組等),不同的任務組下會有 至少 一個任務,比如ETL任務組下就會有很多個任務(比如:nginx訪問日志解析任務、mysql慢查詢日志解析任務等),而某一個任務下又存在至少一個工作線程。

          任務元數據

          在管控臺有一個后臺服務模塊,專門用于管理任務對象以及它們的元數據。

          執行任務的工作者線程本身是無狀態的,它們在啟動的時候會去Zookeeper上下載它們執行任務所需要的元數據。

          熱備機制

          通常,為了服務的可用性我們會為每個任務配備不少于一個工作者線程。當然,這里的 不少于一個 并不只是基于一個運行后臺服務的JVM進程或一個主機節點來計數的,而是針對由多個節點組成的集群而言。

          這通過一個配置來實現:

          </tr> </tbody> </table>

          它的意義是:對每個task而言,啟動的最少的worker線程數。如果一個主機節點上啟動兩個后臺服務的JVM進程,那么這個task就會對應4個工作者線程。

          正常情況下,每個任務在同一時刻只有一個處于active狀態的工作者線程,而其他搶占失敗的都會將自己切換為standby模式,作為備援隨時待命。

          這個機制是如何實現的?這得益于Zookeeper提供的 臨時順序 節點。

          當多個工作者線程去競爭一個任務的時候,它們首先去該任務的path下創建一個子path,并注冊自己的主機等信息。注意這里創建的子path的類型不同于其他Zookeeper使用場景的path類型(其他path通常都是持久型的),它是臨時順序的。這兩個屬性非常重要:

          1 
          worker.minimumNumPerTask=2 
sesese色