ticketea如何從一體化轉向多體化架構
ticketea是一個為西班牙、德國、英國等地區的客戶提供在線售票業務的平臺。我們通常會將自己描述為活動組織者的技術伙伴,在整個活動期間為他們以及這些活動的參與者提供幫助。為了正確地理解ticketea的架構的設計原因,有必要指出,售票業務往往會面對爆炸式的訪問量增長。一旦有某個熱門活動開始出票,瘋狂涌入的粉絲很可能會弄垮你的服務器。
ticketea的產品團隊目前有16位成員負責產品的開發與維護,在這16人中有3位設計師、1位QA,其余的都是開發者。這些開發者通常都是全棧工程師或是多面手,不過在團隊中也有一些專家。我們沒有在團隊中設立系統管理員的角色,主要原因有兩點:一是我們依賴于某些AWS服務以托管項目,二是我們都遵從DevOps實踐。無論是新機器的部署與設置,或是編排系統的操作以及內部開發工具的改進,這些工作都是由全體負責,而不是由具體哪一個人負責的。
圍繞著一體化架構出現的第一顆衛星
ticketea目前已經經歷了6年的發展,和許多創業公司一樣,我們也是從一個簡單的、更穩定的技術平臺開始的。但隨著時間的推移以及產品的持續發展,其中的某些部分必須要重做,才能夠跟上不斷增長的業務需求所提出的挑戰。而其它的部分也得到了重構,以改善系統的健壯性與質量。這些改進讓我們實現了高可用性,從而使那些依賴我們服務的組織者能夠進行大型活動的票務銷售。
在三年以前,ticketea的產品基本上還是一個一體化的解決方案,由于當時在技術上的一些限制與優勢的原因而采取了這樣的設計方案。基本的限制包括團隊規模與資金,而優勢則包括讓新特性更快地推向市場、簡化部署操作、保持運行整個產品所必需的基礎設施的規模與投入、并且保證當時的每一位團隊成員都能夠完整地了解整個平臺。
簡單來說,我們有一套API和一個前端的web應用,這比起將全部功能塞到一個單一的web應用中的做法更好。一套獨立的API已經是一個好的開端了。在2013年初,我們需要創建一套能夠滿足我們需求的商業智能解決方案,因此我們創建了Odin,它好像是在這個一體化架構中出現的一顆衛星,雖然它并沒有完全地利用到這套API。之后,我們意識到一點,衛星的出現通常是一個信號,它表示我們應當轉為面向服務的架構(SOA)了。
(點擊放大圖像)
在Odin之后,我們又開發了一個后臺任務執行系統Heracles,它依賴于一個RabbitMQ集群,在當時采用了Python Celery實現。我們廢棄了之前自行開發的一個Ruby任務系統,它已經無法滿足今后的工作需要處理的任務數量與粒度的需求了。
改變架構,選擇分布式
但是,以上提到的這些項目只是首次使用了其它編程語言,并且開始對于某些部分實現了分布式,這始終只是隔靴搔癢而已。團隊當時所面臨的主要挑戰之一是開始分解這個一體性的架構,而做到這一點并不容易。我們在內部將這一過程稱為“從一體化到多體化(multilith)”。我們已經不記得“多體化”這個詞是我們原創的還是從哪里聽到的了,它表現的含義是你準備將一塊巨石進行分解,但通常來說,在一開始時最大的那塊石頭一直分解到最后還是最大的一塊。
(點擊放大圖像)
我們希望在此強調一點,即我們一開始的設計并不是一種糟糕的或者錯誤的設計。我們曾經看到一些開發者在技術會議上表示他們經歷過的一些老系統有多么爛,而他們又是如何明智地從頭開始進行重新設計的故事。就算這些故事是真的,他們在全新設計時也已經掌握了大量的有關問題領域的知識,而這些知識對于新的設計通常會帶來正面的影響。很顯然,創業公司的長期項目開發必然伴隨著一些遺留代碼、技術債和其它問題,但即使我們要分解這個一體性方案,整個系統的引擎也必須持續運轉。因此,我們無法選擇重寫整個系統,而必須將這些問題逐個擊破。
關于選擇哪一部分作為突破口,以及如何啟動新項目,這方面可以找到大量的相關討論。最終我們決定為會場訪問控制系統創建一個新的項目。ticketea所賣出的每張票上面都包含一個QR碼,同時我們還將(為iOS和Android)提供一個名為Checkpoint的會場訪問控制應用。購票者通過它能夠下載驗證會話信息、掃描QR碼、并幫助你發送活動信息。Checkpoint將調用我們的API,當時的這套API沒有經過分解,它包含了全部功能,而只對應一個單一的repository、和一個單一的PHP軟件項目,整個項目很龐大、非常之龐大。
(點擊放大圖像)
但是,我們的存貨系統(活動、會議、出票等等信息)與訪問控制系統之間存在著一些通用的部分,它們之間需要進行同步,那怎樣實現呢?我們在創建Thor系統時依賴于一個主要的組件,即隊列系統。我們決定,通過RabbitMQ暴露庫存API中的各種事件,讓會場訪問控制API從中獲取各種通知信息。Thor再通過工作進程調用RabbitMQ中的信息交換并進行同步。我們創建了一個名為Thor(沒錯,我們借用了北歐神話中雷神索爾的名字)的repository項目。這是我們第一次在ticketea中創建Python API,它完全基于Django和Rest框架進行開發。你可能已經閱讀過許多關于軟件重寫,以及這意味著什么的相關文章了。但對我們來說,這一組件的重寫非常順利,它獲得了極大的成功。說實話,我們并沒有徹底重寫所有代碼,數據模型并沒有很大的改變,并且API終結點也保持不變,但內部實現得到了全面的調整,從而能夠更好地響應并發與大型活動的需求,例如在西班牙的各種節日活動中,能支持幾十萬的參與者。這次的技術切分非常簡單,因為驗證與庫存邏輯本身就是分離的,他們在語義上也適合分割成不同的API。
高可用性方面的關注
高可用性最近是一個很熱門的概念,許多開發者對于它的描述給人的感覺僅僅是他們的團隊如何處理產品的發展。但實際上,想要在分布式系統中實現高可用性需要觀念上的改變,并且還伴隨著額外的成本。舉例來說,之前所討論的架構實現面臨著一些疑問:
- 如果負責對消息進行入隊列操作的API線程在入隊操作之后立即崩潰,卻來不及提交事務,這時該怎么辦? 實現分布式的 ACID 是一項困難的任務,因此你最終會進入一個名為 BASE (基本可用、軟狀態、最終一致性)的新世界,雖然它沒有ACID那么完美,但卻是必須的。為了實現最終一致性,你需要通過某種方式對系統進行重新同步,找到并清除不一致性。
- 如果RabbitMQ掛了怎么辦? 好吧,關于分布式系統有一個經常被人提起的優勢,即你可以避免發生單點失敗的狀況。但是,分布式系統是非常復雜的,而且當發生故障時往往會引起連鎖反應,或者有時會發生多個部分同時故障的情況。因此,我們決定在Thor中還要建立一個同步的REST API。一旦存貨API出現了不能入隊列的情況,它就會轉而調用HTTP請求,以作用后備方案。
- 如果所有組件都掛了呢? 在RabbitMQ和HTTP都不能工作的狀態下該怎么辦?那么你的這一天應當會過得相當充實。你需要為最壞的情況做好準備,因此我們創建了一個遷移工具,這個軟件能夠對庫存數據庫的一部分進行掃描,同時對Thor的數據庫進行檢查,以找到并修復其中不一致的地方。這也是為什么我們經常說,即使你有了一個A團隊,也要準備一個B計劃。
不僅開發者們需要了解如何在一個高可用性的系統中處理這些狀況,并且你的DevOps工具鏈也將變得更為復雜,從而提高了你的運行成本。
選擇分布式在技術上意味著什么
在選擇分布式之前,首先準備好監控工具,這是一個好主意。事實上,我們認為這是一個必要的前提條件。如果你不對系統進行衡量,那么也很可能還沒有為處理分布式系統做好準備。
你需要意識到你將面對更多的項目,這通常意味著比以往更多的機器、更多的技術棧、更復雜的內部依賴以及其他問題。如果沒有監控機制,你將對系統的表現一無所知,因為現在的系統中比起以前存在著更多處于變化的部分。當你的系統各部分都揉合在一起的時候,主要的監控工作就是不斷地ping你的機器,對系統進行心跳檢測。然而,當你面對的是一個分布式系統時,僅僅了解你的系統是否在運行仍不足夠,你還要注意網絡方面的問題,以及你所依賴的服務狀態等等。
即便如此,哪怕你已經對所有可變的部分有所了解,你也不能輕易地斷定整個系統都處于正常工作狀態了。或許你的隊列還能夠工作,但已經超負荷了,又或者你的工作進程已經無法處理所收到的任務了。為了保證一切盡在掌握,你必須做好以下幾點:
- 集中式的日志記錄 :在一個分布式系統中,請求很可能會發送至不同的服務中。為了在不同的系統中找到問題,或是出于衡量指標的原因要對某個用戶的行為進行追蹤,我們需要使用一個唯一的令牌,在所有的日志中都保存這一令牌信息。所有的日志將通過 rsyslog 進行收集,隨后由 Logstash 進行處理,以允許我們對海量的日志記錄進行搜索。
- 錯誤處理 :在ticketea內部,我們使用 Sentry 進行代碼調用棧的日志記錄,這讓我們能夠積極地找到bug并進行修復。
- 圖形化 :我們還使用 Statsd 在 Graphite 中記錄衡量指標,這些指標將在 Grafana 的儀表板中以友好的方式進行顯示。舉例來說,我們能夠很方便地知道在這一段時期我們的支付系統的提供者的響應時間是多少,以此檢測是否出現問題,有時還將促使我們更換提供者。
- 警告系統 :我們并不想整天盯著這些復雜的儀表板。由于我們已經收集了各種良好的指標(下單的次數、后臺作業的隊列大小、cronjobs的心跳檢測數據等等),并且知道該用哪些閥值找到問題,因此我們就開始尋找某種警告系統,并最終找到了 Cabot 。現在,如果系統出了故障,就會有一條短信發給我們,把我們從睡夢中叫醒。
有了以上這些之后,你就能夠知道系統什么時候會出錯了,但并非總能知道錯誤的原因所在。調試與問題的修復比起在一體化系統中更困難了。某些問題并非來源于某個特定的項目,而是存在于某個“無人的荒漠中”,例如連接性故障,或者問題可能出現在你所連接的軟件中。在這些情況下,開發者不得不離開他們所熟悉的環境,去其他項目中進行探索。
一個分布式系統同樣難以在開發環境中復制。在ticketea內部,我們結合使用了 Vagrant 與 Ansible 以設置我們的開發環境,讓他們盡可能地接近于生產系統。
舉例來說,在ticketea系統中,一旦某個用戶購買了一張票,我們就會將這一事件的詳細信息記錄在日志中。我們將通過一張圖表顯示用戶在一天內購票所需時間的平均值,因此我們就能夠判斷系統的性能是否有下降,以及支付系統需要多少時間進行響應。我們將追蹤這張票是免費的還是收費的,以及這張票所對應的活動是否有人數限制。只要系統可用,我們的購票系統就應當保持可用,但它如今需要處理一些不同的用例,因而變得更復雜了。
每次當我們進行部署時,都會在Sentry中跟蹤未處理的代碼異常。我們所擁有的警告系統對于它的閥值有一定的容忍度。這意味著如果購票系統由于邏輯出錯而無法工作,同時又沒有捕獲到異常,那么警告系統將等待一段時間后再通知我們有狀況發生。出于這一原因,每次在部署之后,我們都會仔細地對指標的儀表板進行監控。這樣一來,我們立即就能夠確定新的部署是否破壞了某些特性,或是產生了回歸缺陷而需要進行回滾。
選擇分布式對于團隊來說意味著什么
選擇分布式就意味著團隊要充分地理解整個系統變得更困難了。同時,不必強制每個成員都理解整個系統,這樣就更容易招聘新員工,并且僅讓他們專注于系統的一小部分,從而讓他們更快地上手。
不過,我們相信一個高度關注于產品的團隊是十分重要的,因此我們推出了一些實踐,讓每個人都參與這一循環:
- 兩周長度的SCRUM :這種方法能夠幫助我們讓團隊專注于產品,我們遵循了大部分實踐,例如每日站會、回顧會議以及產品演示,整個團隊通過這些實踐能夠看到產品在其他方面是怎樣改進的。
- 對于重要的架構改變進行討論 :當我們需要對架構進行重要的改動時,這不再是一個人的決定,而是團隊的決定。每個人都需要適應這一點,至少要理解決定背后的原因,并接受實現的方式。在這些討論中,每個人都能夠指出新概念中出現的問題,并且提出替代方案的建議。最后,團隊對于討論的內容將達成共識。
- 路線圖會議 :我們在每個季度至少要開一次會議,以通知全體人員這個季度從業務的角度來看將完成哪些功能。雖然大家都了解全年的產品路線圖,但我們發現這種會議能夠確保大家都處于正軌上,并專注于正確的功能。開發者也能夠得以理解他們工作的目的,以及對于整個公司的意義。
我們也清楚,一旦團隊的規模擴大到了一定程度,再讓整個團隊參與這些會議就比較困難了。不過對于我們目前的規模來說,這種會議還是很有效的。對于一個成熟的團隊來說,應當能夠做到按照自身的需求及規模進行適當的調整。
一旦選擇了分布式系統之后,我們的工作方式也要加以調整。某些成員將轉入一些特定的項目開發,而不是讓所有開發者都去接觸相同的代碼庫。經過一段時間之后,這些成員將成為項目中的專家,我們也很快意識到需要讓開發者們在不同的項目之間進行切換,以減少巴士系數(bus factor,字面上的意思就是有多少個關鍵開發者被車撞了之后會讓項目停擺),并增進知識的共享。
因為不同的項目所用的技術棧不同,有些情況下就需要為開發者培訓新語言的知識。我們盡力保證技術棧的數量能夠控制在一個合理的數量之內。有時,某種技術可以取代一種現有的技術,并且能夠在一定時期內共存,不過這種情況很少發生。
ticketea的開發者們大多數都是全棧工程師。舉例來說,部分開發者的職責既有前端也有后端開發,有些人甚至還可以參與移動開發。顯然有部分開發者在某個領域具有特長,那么其他人都可以向他們詢問技術方面的問題。團隊的成員不僅了解自身的長處,同時也了解他人的長處。
當團隊在持續發展時,整個開發組織也在保持變化。在開始階段,組織中僅包括CTO與開發者的角色。而當開發者的數量不斷增長之后,就誕生了“設計師主管”與“開發者主管”這樣的角色,以分擔CTO的職責,這種方式也讓組織的其他部分能夠保持一個非常扁平的架構。
目前團隊的成員已經超過了15人,我們正開始將團隊組織為由5-7人組成的多個小型非獨立團隊。每個團隊將設立一個主管,他們將與團隊成員密切合作,以幫助他們提高生產力。例如幫助其他開發者調試問題,或僅僅是確保他們正在做正確的事。
總結
分布式系統能夠帶來諸多益處,我們可以針對不同需求的項目采用不同的編程語言。這也可以讓某個項目中成員更容易理解整個項目,但也使他們理解整個系統的架構變得困難了。因此,接口就顯得非常重要。API及其版本控制變得至關重要,因為他們提供了協議與通信點,讓你的基礎設施與團隊都能夠自由伸縮,并且還能夠減少代碼沖突、降低一次發布的規模以及多步驟的發布周期。如果你能夠小心仔細地處理好這些問題,就能夠以優雅地方式面對失敗。舉例來說,即使我們的訪問控制系統在極端情況下掛了,售票系統依然能夠正常運作。
雖然分布式架構有著這些益處,但也帶來了更高的成本。分布式架構難以維護,在進行部署及編排等操作時也將遇到更多的困難。因此,在實現這種架構時,你需要意識到它的益處與成本。
關于作者
a > Miguel Araujo 目前在ticketea擔任開發者主管。當他在馬德里完成了計算機科學專業的課程之后,就作為自由職業者在各個創業公司參與工作。之后,他成為了一名全棧開發者,并積極地為開源軟件貢獻力量。他在三年前加入了ticketea公司,職責是讓ticketea的技術棧采用更現代化的技術,并幫助它進行擴張,在歐洲地區打開新的市場。他熱愛學習、面對工作上的挑戰、以及用電器進行一些修補操作。
Jose Ignacio Galarza 目前在ticketea擔任CTO。他在馬德里獲得了計算機科學專業的學位之后留在了大學里進行研究工作,隨后在一些專注于產品的創業公司中擔任開發者職務。他在三年前加入了ticketea公司,負責改進產品,并將ticketea的技術棧轉變為具備更高伸縮性的分布式系統。他非常愛吃漢堡。
來自: http://www.infoq.com/cn/articles/monolith-to-multilith