你是不是曾經很好奇bitly如何實現營利了?一個URL縮短工具怎麼可能那麼難寫?Sean O'Connor,作爲Bitly首席應用開發人員,在Bacon討論會的一次發言中給出了關於bilty如何營利的答案。
Sean解釋到,寫一個可用的網址縮短工具是很容易,但是要寫一個大規模的並且高可用的則並不如想象那麼容易。Bitly並不是通過將URL縮短作爲一個服務來實現營利的,它的贏利來自一款數據分析產品,這個數據分析工具將URL點擊數據和他們從網絡上爬取的數據做對比,幫助他們的客戶找出什麼類型用戶關注了那些網頁。
數據分析產品開始是作爲一個爬取web服務器日誌的後端服務,這些日誌包含了來自注解鏈接的數據和cookie數據,這些數據包括用戶從哪裏點擊了鏈接,哪個用戶點擊了這個鏈接,鏈接的內容是什麼等等信息。但是所有的鏈接都回到了網站的域名。這樣的話,如果讓鏈接轉向你另一個域名,那麼第三方可以分析這些數據,這個主意聽起來很可怕,但是它也是一種天才的想法。
這個話題並不是針對Bitly的架構,這是一個關於分佈式系統和如何使用分佈式系統去解決一系列問題的本質探索。或許從他的發言中我最喜歡的是這句:
我同樣非常喜歡他的“爲什麼事件式消息比命令式消息好”的解釋,我之前從未聽過類似的說法。Sean從實踐出發,如果你嘗試從單主機擴展到集羣模式,這個演講值得觀看。這裏讓我們看看Sean如何解釋分佈式系統。
統計
- 每月60億的點擊量
- 每月6億的縮短服務
- 50人的公司,大概20個工程師
- 400臺服務器,並不是所有400臺服務器都用來處理重定向,大約30臺服務器處理所有來自外部的輸入流量,包括網址縮短、重定向、API請求、網頁的用戶界面等等。其餘的用來處理各種服務,如排序和組織用戶數據,或者提供各種不同形式的處理和分析(數據庫、metrics、網絡爬取、文本分析等等)
- 每月爬1億網頁
來源
平臺
注意,以下這些只是在發言中被提及的一些技術,並不是一個全面的列表。
- HDFS
- S3
- Nagios
- Bilty使用的實時分佈式消息系統是Nsq。
- Bitly使用hostpool管理一系列的主機。
- 演講的結尾部分被剪掉了,提及bilty使用了一些不同了類型的數據庫。
論分佈式系統的本質
1. 高可用的應該就是分佈式的,爲了獲得一定的可用性,就必須實現地理位置多元性。按照定義,如果你在不同的地區有獨立的操作系統,它們就必須是分佈式的。
2. 創建分佈式系統的老方法。建立抽象讓你以爲事情不是分佈式的。
- 例如,NetApp,創建了無限容量硬盤的錯覺。
- Oracle作爲一個高可用的數據庫,讓你以爲世界不一樣了,但實際上不是。
- 兩者都解決了實際問題,但是都很昂貴,所以做一些不一樣的事情吧。
3. 新方法通過接受分佈式系統固有的特性來處理問題,將這些抽象爲工具提供。重大轉變在於你如何看待事情,因爲抽象更接近於你構建的現實,你可以做更多有力的權衡取捨並因此更高效的去完成某些事情。
4. 帶有一個存儲器的4個機器總是比帶有4個存儲器的1個機器要便宜,這就意味着分佈式系統是通往大規模和獲取高可用的有效方式。
5. 因爲我們從一臺機器轉向了N臺機器,分佈式系統的問題就出現了。
- 組件併發:A機器和B機器同時工作,這就是如何獲取橫向擴展能力。很強大但是成本是需要在不同機器間協調。例如,當鎖數據使機器互相等待彼此,這就不能算是併發了。
- 缺少全局clock:每個機器有一個不完美的clock,當有超過一臺機器並且每臺機器有它自己的時間定義,這就意味着發生在不同機器上的事件不能基於時間排序,對bilty而言如果事件相差1到2秒,它們就不清楚哪個先發生了。
- 故障獨立發生:如果A發生故障了,B應該繼續工作。
6. 有些問題,比如分析數據太大而不能用一臺機器來處理,或者至少這麼一臺機器不在預算內。
實現分佈式系統的策略
面向服務的架構
這兒沒有一個龐大的Bitly應用,只有很多通過網絡通信的小服務。
- 它是一個抽象,從代碼來看更像是函數,但是是系統級別的。
- 你不需要記住系統的所有細節,只需要關注API邊界與結構。
- 非常有益於開發和運營
- Bitly是一個運行了6年的老公司,有很多的代碼。你並不需要查看每一行的代碼,只需要運用這些服務。
- 設計良好的服務只有數百行的代碼。
- 從運營上看,非常容易定位到哪個系統出現了問題,然後你可以仔細檢查該系統來發現問題的所在。
- 故障現在意味着功能受限而不是服務停止。一般來說,每個服務都是設計來解決特定的問題。因爲它們和自己的持久層是完全獨立的,如果其中一個故障了,唯一丟失的是解決那個問題的能力。但是整體的服務仍然是運行中的。Metric系統停止服務決不會影響URL縮短請求。
異步消息
- 發送消息而不用等待接收者的迴應。
- 在組件間刪除隊列相當容易了,如果A發送消息給B,而B出故障了,這些消息就會排隊等候,當B恢復後繼續處理。
- 更多的錯誤處理方式。如果一臺機器故障了,消息只是會延遲一些處理,A並不需要知道或者關心B的故障。
- 不利因素是操作執行的不如同步請求自然。
- 一個URL縮短請求是需要完全同步處理的,因爲:
- 速度。希望能儘快地返回回覆給用戶。例如,不想處理隊列備份。
- 一致性。不希望返回同一個縮短的URL給多個用戶,與其給予用戶無效鏈接不如返回一個錯誤。
- Metrics系統是完全異步的。當Bitly的一個鏈接被點擊,並通過HTTP返回,轉換的數據被裝入隊列用來做數據分析和其他下游系統。如果在處理metrics的過程中有一點延遲,也不會有重大影響。
- 消息可以被看作命令或者事件。
- 命令是“做X”。
- 事件則是“X”發生了。
- 事件成了比命令更加有用的抽象。
- 更好的系統間隔離。一個命令意味着必須知道誰收到了該命令,否則它沒有任何意義。說某個事件發生則意味着我們並不需要關心誰消費了該消息。只需要通知X發生了,其他服務則可以做對應的增量更新,或者相應的操作。
- 事件通常映射爲多個消費者處理的消息。當一個Bitly URL被解碼爲HTTP重定向,消息會發送給多個服務:一個持久化服務獎它保存到HDFS及S3;一個實時的分析服務;更長一點的離線歷史分析服務;一個註解服務。解碼服務只需要把這個時間發佈,而不需要去了解任何下游服務。而下游服務關心的只是捕獲這個服務,而不管誰給它發送。
- 非常容易地添加新的消費者。可以建立一個新的服務,與某個事件關聯,生產者並不知道也不關心。一個服務如何處理事件的變化同樣的不關心生產者。生產者和消費者是分離的,只通過指定的事件狀態關聯。
使服務更好的運行
- 在服務間使用backpressure。如果一個服務處於繁忙狀態,它就要告訴其他的請求服務節流它們的請求,從而減少過載服務的壓力。例如,指數退避(exponential backoff),幫助阻止了級聯錯誤,同樣幫助系統更快地恢復。舉個例子,一個依賴緩存的服務需要預熱時間,如果請求服務在預熱期間不具備back-off,那麼數據庫可能會崩潰。
- 繞過故障。例如,服務間的負載均衡。Bitly使用hostpool來管理一系列的主機。客戶端向主機請求服務,如果發生故障,客戶端則會通知該請求對應的hostpool。給予反饋,hostpool可以管理主機分配路由給健康的主機。
監控
1. 如果只有一兩臺服務器想知道哪臺壞掉了並不難,但是如果有數百臺的主機你就需要幫助了。
2. 使用類似Nagios的工具來檢查主機狀況,檢查狀態比如“機器是否還在運轉”?
3. 運行完整性檢查。例如,服務是響應了但是返回的數據被破壞了。
4. 集中化的日誌。這個非常重要因爲你可以檢測跨不同主機之間的故障。如果一個用戶造成了所有的錯誤,那麼從一臺又一臺的機器中檢測到錯誤信息將會非常困難。集中化日誌式使檢測整體的錯誤變得更容易,就像所有的錯誤都來自同一個IP地址。
5. 時間到達正確的人,你如何顯示來自工具的信息。
- 例如,Nsq,擁有一個很好用的管理界面來反饋請求是如何運行的,因此你可以知道隊列備份了是因爲某臺主機。
- 部署系統的用戶界面使部署系統和查看部署歷史都變得很容易。如果Nagios出問題了,而某個東西又剛剛不熟,那麼它肯定脫不了關係。
經驗總結
1. 知識就是力量。你知道的關於事情的屬性越多,就越容易做出更好的決定使工作變得更高效,效率意味着你可以以更低的成本使系統更大更快。
2. 爲抽象漏洞建立解決方案。如果你使用抽象的一層來隱藏分佈式的特性,最終必將失敗,代碼必須發現並處理任何漏洞。
3. 儘可能的放在一個機器裏,如果你不需要一個分佈式系統那麼就不要創建了,要創建它們很複雜並且昂貴。
4. 在面向服務架構中,故障意味着功能受限而不是停機。
5. SOA+隊列+異步消息真的非常強大。這種方式分離了組件,使工作併發進行,使故障獨立發生,同時,使組件很容易解釋這些行爲。
6. 當速度和一致性是至關重要的時,使用同步請求。返回給用戶錯誤信息而不是很慢或者錯誤的答案。
7. 事件式的消息比命令式的消息要更好些。它們使得系統間更好的隔離開,更自然的支持多個消費者。 助保持服務的專注性,而不用擔心服務功能之外的事情。
8. 註解而不是過濾。在生產等級適用過濾將疲於應對下游服務關注的鏈接例子是公共和私有的鏈接,過濾私有鏈接意味着對這些私有鏈接感興趣的服務無法獲得這些它們需要的鏈接。註解私有或者公共的鏈接讓服務只處理它們關心的事件。
9. 使服務更好的運行,使用back pressure防止服務過載並繞過故障的服務。
10. 如果沒有Nagios來檢查,就算幾乎可以確定損壞了,你都不能知曉。
11. 工具應該對向人們展示信息,使正確的信息在正確的時間到達正確的人。