java
K歌亭是唱吧的一條新業務線,旨在提供線下便捷的快餐式K歌方式,用戶能夠在一個電話亭大小的空間裏完成K歌體驗。K歌亭在客戶端有VOD、微信和Web共三個交互入口,業務複雜度較高,如長鏈接池服務、用戶系統服務、商戶系統、增量更新服務、ERP等。對於服務端的穩定性要求也很高,由於K歌亭擺放地點不固定,不少場所的運營活動會形成突發流量。python
爲了快速開發上線,K歌亭項目最初採用的是傳統的單體式架構,可是隨着時間的推移,需求的迭代速度變得很快,代碼冗餘變多,常常會出現牽一髮動全身的改動。重構不但會花費大量的時間,並且對運維和穩定性也會形成很大的壓力;此外,代碼的耦合度高,新人上手較困難,每每須要通讀大量代碼纔不會踩進坑裏。git
鑑於上述弊端,咱們決定接下來的版本里採用微服務的架構模型。從單體式結構轉向微服務架構中會持續碰到服務邊界劃分的問題:好比,咱們有user 服務來提供用戶的基礎信息,那麼用戶的頭像和圖片等是應該單獨劃分爲一個新的service更好仍是應該合併到user服務裏呢?若是服務的粒度劃分的過粗,那就回到了單體式的老路;若是過細,那服務間調用的開銷就變得不可忽視了,管理難度也會指數級增長。目前爲止尚未一個能夠稱之爲服務邊界劃分的標準,只能根據不一樣的業務系統加以調節,目前K歌亭拆分的大原則是當一塊業務不依賴或極少依賴其它服務,有獨立的業務語義,爲超過2個的其餘服務或客戶端提供數據,那麼它就應該被拆分紅一個獨立的服務模塊。github
在採用了微服務架構以後,咱們就能夠動態調節服務的資源分配從而應對壓力、服務自治、可獨立部署、服務間解耦。開發人員能夠自由選擇本身開發服務的語言和存儲結構等,目前總體上使用PHP作基礎的Web服務和接口層,使用Go語言來作長鏈接池等其餘核心服務,服務間採用thrift來作RPC交互。數據庫
唱吧K歌亭的微服務架構採用了Mesos和Marathon做爲容器編排的工具。在咱們選型初期的時候還有三個其餘選擇,Kubernetes、 Swarm、 DC/OS:後端
咱們採用了etcd做爲服務發現的組件,etcd是一個高可用的分佈式環境下的 key/value 存儲服務。在etcd中,存儲是以樹形結構來實現的,非葉結點定義爲文件夾,葉結點則是文件。咱們約定每一個服務的根路徑爲/v2/keys/service/$service_name/,每一個服務實例的實際地址則存儲於以服務實例的uuid爲文件名的文件中,好比帳戶服務account service當前啓動了3個能夠實例,那麼它在etcd中的表現形式則以下圖:服務器
當一個服務實例向etcd寫入地址成功時咱們就能夠認爲當前服務實例已經註冊成功,那麼當這個服務實例因爲種種緣由down掉了以後,服務地址天然也須要失效,那麼在etcd中要如何實現呢?微信
注意,圖中的每一個文件有一個ttl值,單位是秒,當ttl的值爲0時對應的文件將會被etcd自動刪除。當每一個服務實例啓動以後第一次註冊時會把存活時間即ttl值初始化爲10s,而後每隔一段時間去刷新ttl,用來像向etcd彙報本身的存活,好比7s,在這種狀況下基本啥上能夠保證服務有效性的更新的及時性。若是在一個ttl內服務down掉了,則會有10s鐘的時間是服務地址有效;而服務自己不可用,這就須要服務的調用方作相應的處理,好比重試或這選擇其它服務實例地址。網絡
咱們服務發現的機制是每一個服務自注冊,即每一個服務啓動的時候先獲得宿主機器上面的空閒端口;而後隨機一個或多個給本身並監聽,當服務啓動完畢時開始向etcd集羣註冊本身的服務地址,而服務的使用者則從etcd中獲取所需服務的全部可用地址,從而實現服務發現。
同時,咱們這樣的機制也爲容器以HOST的網絡模式啓動提供了保證。由於BRIDGE模式確實對於網絡的損耗太大,在最開始就被咱們否決了,採用了HOST模式以後網絡方面的影響確實不是很大。
咱們選擇Prometheus彙總監控數據,用ElasticSearch彙總日誌,主要的緣由有:
Mesos Exporter,是Prometheus開源的項目,能夠用來收集容器的各項運行指標。咱們主要使用了對於Docker容器的監控這部分功能,針對每一個服務啓動的容器數量,每一個宿主機上啓動的容器數量,每一個容器的CPU、內存、網絡IO、磁盤IO等。而且自己他消耗的資源也不多,每一個容器分配0。2CPU,128MB內存也毫無壓力。
在選擇Mesos Exporter以前,咱們也考慮過使用cAdvisor。cAdvisor是一個Google開源的項目,跟Mesos Exporter收集的信息八成以上都是相似的;並且也能夠經過image字段也能夠變相實現關聯服務與容器,只是Mesos exporter裏面的source字段能夠直接關聯到marathon的application id,更加直觀一些。同時cAdvisor還能夠統計一些自定義事件,而咱們更多的用日誌去收集相似數據,再加上Mesos Exporter也能夠統計一些Mesos自己的指標,好比已分配和未分配的資源,因此咱們最終選擇了Mesos Exporter。
以下圖,就是咱們監控的部分容器相關指標在Grafana上面的展現:
Node exporter,是Prometheus開源的項目,用來收集物理機器上面的各項指標。以前一直使用Zabbix來監控物理機器的各項指標,此次使用NodeExporter+Prometheus主要是出於效率和對於容器生態的支持兩方面考慮。時序數據庫在監控數據的存儲和查詢的效率方面較關係數據庫的優點確實很是明顯,具體展現在Grafana上面以下圖:
Filebeat是用來替換Logstash-forwarder的日誌收集組件,能夠收集宿主機上面的各類日誌。咱們全部的服務都會掛載宿主機的本地路徑,每一個服務容器的會把本身的GUID寫入日誌來區分來源。日誌經由ElasticSearch彙總以後,聚合的Dashboard咱們統一都會放在Grafana上面,具體排查線上問題的時候,會用Kibana去查看日誌。
Prometheus配置好了報警以後能夠經過AlertManager發送,可是對於報警的聚合的支持仍是很弱的。在下一階段咱們會引入一些Message Queue來本身的報警系統,增強對於報警的聚合和處理。
ElastAlert是Yelp的一個Python開源項目,主要的功能是定時輪詢ElasticSearch的API來發現是否達到報警的臨界值,它的一個特點是預約義了各類報警的類型,好比frequency、change、flatline、cardinality等,很是靈活,也節省了咱們不少二次開發的成本。
對於一套微服務的系統結構來講,最大的難點並非實際業務代碼的編寫,而是服務的監控和調試以及容器的編排。微服務相對於其餘分佈式架構的設計來講會把服務的粒度拆到更小,一次請求的路徑層級會比其餘結構更深,同一個服務的實例部署很分散,當出現了性能瓶頸或者bug時如何第一時間定位問題所在的節點極爲重要,因此對於微服務來講,完善的trace機制是系統的核心之一。
目前不少廠商使用的trace都是參考2010年Google發表的一篇論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》來實現的,其中最著名的當屬twitter的zipkin,國內的如淘寶的eagle eye。因爲用戶規模量級的逐年提高,分佈式設計的系統理念愈來愈爲各廠商所接受,因而誕生了trace的一個實現標準opentracing ,opentracing標準目前支持Go、JavaScript、Java、 Python、Objective-C、C++六種語言。 由sourcegraph開源的appdash是一款輕量級的,支持opentracing標準的開源trace組件,使用Go語言開發K歌亭目前對appdash進行了二次開發,並將其做爲其後端trace服務(下文直接將其稱之爲Ktrace),主要緣由是appdash足夠輕量,修改起來比較容易。唱吧K歌亭業務的膠水層使用PHP來實現,appdash提供了對protobuf的支持,這樣只須要咱們本身在PHP層實現middleware便可。
在trace系統中有以下幾個概念
(1)Annotation
一個annotation是用來即時的記錄一個事件的發生,如下是一系列預約義的用來記錄一次請求開始和結束的核心annotation
其餘的annotation則在整個請求的生命週期裏創建以記錄更多的信息 。
(2)Span
由特定RPC的一系列annotation構成Span序列,span記錄了不少特定信息如 traceId, spandId, parentId和RPC name。
Span一般都很小,例如序列化後的span一般都是kb級別或者更小。 若是span超過了kb量級那就會有不少其餘的問題,好比超過了kafka的單條消息大小限制(1M)。 就算你提升kafka的消息大小限制,過大的span也會增大開銷,下降trace系統的可用性。 所以,只存儲那些能表示系統行爲的信息便可。
(3)Trace
一個trace中全部的span都共享一個根span,trace就是一個擁有共同traceid的span的集合,全部的span按照spanid和父spanid來整合成樹形,從而展示一次請求的調用鏈。
目前每次請求由PHP端生成traceid,並將span寫入Ktrace,沿調用鏈傳遞traceid,每一個service本身在有須要的地方埋點並寫入Ktrace。舉例以下圖:
每一個色塊是一個span,代表了實際的執行時間,一般的調用層級不會超過10,點擊span則會看到每一個span裏的annotation記錄的不少附加信息,好比服務實例所在的物理機的IP和端口等,trace系統的消耗通常不會對系統的表現影響太大,一般狀況下能夠忽略,可是當QPS很高時trace的開銷就要加以考量,一般會調整採樣率或者使用消息隊列等來異步處理。不過,異步處理會影響trace記錄的實時性,須要針對不一樣業務加以取捨。
目前K歌亭在生產環境裏的QPS不超過1k,因此大部分的記錄是直接寫到ktrace裏的,只有歌曲搜索服務嘗試性的寫在kafka裏,由mqcollector收集並記錄,ktrace的存儲目前只支持MySQL。一個好的trace設計能夠極快的幫你定位問題,判斷系統的瓶頸所在。
在服務訪問峯值的出現時,每每須要臨時擴容來應對更多的請求。除了手動經過Marathon增長容器數量以外,咱們也設計實現了一套自動擴縮容的系統來應對。咱們擴縮容的觸發機制很直接,根據各個服務的QPS、CPU佔用、內存佔用這三個指標來衡量,若是三個指標有兩個指標達到,即啓動自動擴容。咱們的自動擴容系統包括3個模塊:
在唱吧,咱們使用Jenkins做爲持續集成的工具。主要緣由是咱們想在本身的機房維護持續集成的後端,因此放棄了Travis之類的系統。
在實施持續集成的工做過程當中,咱們碰到了下列問題:
基於以上問題,咱們選擇使用Mesos和Marathon來管理Jenkins集羣,把Jenkins Master和Jenkins Slave都放到Docker容器裏面,能夠很是有效的解決以上問題。基礎架構以下圖:
基於上述的基礎架構,咱們定義了咱們本身的持續集成與持續交付的流程。其中除了大規模使用Jenkins與一些自定製的Jenkins插件以外,咱們也本身研發了本身的部署系統——HAWAII。
在HAWAII中能夠很直觀的查看各個服務與模塊的持續集成結果,包括最新的版本,SCM revision,測試結果等信息,而後選擇相應的版本去部署生產環境。
在部署以前,能夠查看詳細的測試結果和與線上版本的區別,以及上線過程當中的各個步驟運行的狀態。
基於上述基礎架構,咱們的CI/CD流程以下:
隨着互聯網的高速發展,各個公司都面臨着巨大的產品迭代壓力,如何更快的發佈高質量的產品,也是每一個互聯網公司都面臨的問題。在這個大趨勢下,微服務與DevOps的概念應運而生,在低耦合的同時實現高聚合,也對新時代的DevOps提出了更高的技術與理念要求。
這也是咱們公司在這個新的業務線上面進行,進行嘗試的主要緣由之一。對於微服務、容器編排、虛擬化、DevOps這些領域,咱們一步一步經歷了從無到有的過程,因此不少方面都是本着從知足業務的目標來儘可能嚴謹的開展,包括全部的服務與基礎架構都進行了高併發且長時間的壓力測試。
在下一步的工做中,咱們也有幾個核心研究的方向與目標,也但願能跟你們一塊兒學習與探討:
鈕博彥,唱吧高級研發經理,負責唱吧測試開發、持續集成和DevOps等工做。從2007年開始曾就任於微軟中國、雅虎北研等公司。一直專一於提高研發總體質量與效率,以及自動化測試與持續集成的架構設計。
劉宇桐,唱吧研發經理,負責唱吧創新產品部的服務端開發。曾就任於人人網,創新工場。職業貓奴,專一於研究服務端架構及開發流程等相關工做。
感謝木環對本文的審校。