2014年下半年左右,去哪兒完成了有關構建私有云服務的技術調研,并最終拍定了Docker/Mesos這一方案。下圖1展示了去哪兒數據平臺的整體架構:
圖1:去哪兒數據平臺的整體架構
該平臺目前已實現了如下多項功能:
1.每天處理約340億/25TB的數據;
2.90%的數據在100ms內完成處理;
3.最長3h/24h的數據回放;
4.私有的Elasticsearch Cloud;
5.自動化監控與報警。
為什么選擇Docker/Mesos
目前為止,這個數據平臺可以說是公司整個流數據的主要出入口,包括私有的Elasticsearch Cloud和監控報警之類的數據。那么為什么選擇Docker/Mesos?
選擇Docker有兩大原因。第一個是打包:對于運維來講,業務打完包之后,每天面對的是用腳本分發到機器上時所出現的各種問題。業務包是一個比較上層的話題,這里不做深入的討論,這里講的“打包”指軟件的Runtime層。如果用Docker的打包機制,把最容易出現問題的Runtime包裝成鏡像并放在registry里,需要的時候拿出來,那么整個平臺最多只執行一個遠程腳本就可以了,這是團隊最看好的一個特性。第二個是運維:Docker取消了依賴限制,只要構建一個虛擬環境或一個Runtime的鏡像,就可以直接拉取到服務器上并啟動相應的程序。此外Docker在清理上也較為簡單,不需要考慮環境卸載不干凈等問題。
以常見的計算框架來說,它們本質上仍然屬于運行在其上的Job的Runtime。綜合上述情況,團隊選擇針對Runtime去打包。
選擇Mesos是因為它足夠簡單和穩定,而且擁有較成熟的調度框架。Mesos的簡單體現在,與Kubernetes相比其所有功能都處于劣勢,甚至會發現它本身都是不支持服務的,用戶需要進行二次開發來滿足實際要求,包括網絡層。不過,這也恰好是它的強項。Mesos本身提供了很多SDN接口,或者是有模塊加載機制,可以做自定義修改,平臺定制功能比較強。所以用Mesos的方案,需要考慮團隊是否可以Hold住整個開發過程。
從框架層面來看,Marathon可以支撐一部分長期運行的服務,Chronos則側重于定時任務/批處理。
以下圖2是Mesos的一個簡單結構圖:
圖2:Mesos結構
數據平臺的最終目標架構如下圖3所示:
圖3:平臺目標
組件容器化與部署
組件的容器化分為JVM容器化和Mesos容器化。JVM容器化需要注意以下幾方面:
潛在創建文件的配置都要注意
1.java.io.tmpdir
2.-XX:HeapDumpPath
3.-Xloggc
-Xloggc會記錄GC的信息到制定的文件中。現在很少有直接用XLoggc配置的了(已經用MXBean方式替代了)。如果有比較老的程序是通過-Xloggc打印GC日志的話,那么要額外掛載volume到容器內。
時區與編碼
1.–env TZ=Asia/Shanghai
2.–volume /etc/localtime:/etc/localtime:ro
3.–env JAVA_TOOL_OPTIONS=”-Dfile.encoding=UTF-8 -Duser.timezone=PRC
時區是另一個注意點。上面所列的三種不同的方法都可以達到目的,其中第一/三個可以寫在Dockerfile里,也可以在docker run時通過–env傳入。第二種只在docker run時通過volume方式掛載。另外,第三種額外設置了字符集編碼,推薦使用此方式。
主動設置heap
1.防止ergonomics亂算內存
這是Docker內部實現的問題。即使給Docker設置內存,容器內通過free命令看到的內存和宿主機的內存是一樣的。而JVM為了使用方便,會默認設置一個人機功能會根據當前機器的內存計算一個堆大小,如果我們不主動設置JVM堆內存的話,很有可能計算出一個超過 Memory Cgroup限制的內存,啟動就宕掉,所以需要注意在啟動時就把內存設置好。
CMS收集器要調整并行度
1.-XX:ParallelGCThreads=cpus
2.-XX:ConcGCThreads=cpus/2
CMS是常見的收集器,它設置并行度的時候是取機器的核數來計算的。如果給容器分配2個CPU,JVM仍然按照宿主機的核數初始化這些線程數量,GC的回收效率會降低。想規避這個問題有兩點,第一點是掛載假的Proc文件系統,比如Lxcfs。第二種是使用類似Hyper的基于Hypervisor的容器。
Mesos容器化要求關注兩類參數:配置參數和run參數。
3.需要關注的配置參數
1.MESOS_systemd_enable_support
2.MESOS_docker_mesos_image
3.MESOS_docker_socket
4.GLOG_max_log_size
5.GLOG_stop_logging_if_full_disk