為什么我要選擇erlang+go進行服務器架構
估計很多同學看到這里都會覺得迷惑,go的大名已經如雷貫耳了,但是erlang?這個東東是神馬?難道是編程語言?怎么從來沒聽說過。
這里請允許我先介紹一下使用Erlang開發的比較有名的應用:
一:whatsapp
只憑32個技術人員,如何應付4.5億的用戶?對于剛剛被非死book用190億美元收購的WhatsApp來說,答案是Erlang——一種誕生于上世紀80年代的編程語言,終于在此時走到了聚光燈下。
這個應用把erlang的特性發揮到了極致,利用到了它最好的vm、 集群基礎設施、數據庫mnesia, 消除了非常多的數據Scale、內存池和鎖的問題, 提到的技術和修正點非常值得我們參考。
雖然大部分的解決方法我們在日常都差不多用過。但是他很系統的整理出來,用在商業系統了,這是個非常大的飛躍。
可以服務4.5億用戶的高可靠架構:
需要注意的是, WhatsApp的整體架構并未公開,這里僅僅是從不同信息源中獲取不同的片段。Rick Reed的講座主要分享了使用Erlang實現單服務器200萬連接數,雖然很有價值,但是并不是整個應用架構
這些統計是當下系統的一些數據,更多針對數據存儲、消息、meta-clustering以及新加入的BEAM/OTP補丁。
·4.5億的活躍用戶,并且是史上最快達到這個數字的公司
·32個工程師,平均每人支撐1400萬活躍用戶
·每天收發跨7個平臺的500億消息
·平均每天注冊用戶過百萬
·0廣告開銷
·800萬投資
·數百個節點
·8000+核心
·數百TB內存
·每秒Erlang消息超過7000萬
·在2011年,WhatsApp單服務器取得 100萬個tcp會話,同時還有內存和CPU剩余。在2012年,tcp會話發展到了200萬。
2013年WhatsAppf發表twriter聲明70億消息入站,110億消息出戰,即每天處理180億消息,偉大的2013!
二百多萬的長連接push服務器:
whatsapp數據集mnesia的規模:
生產系統的數據:
每秒的消息數:
發展歷程:
1. WhatsApp服務器基本上完全使用Erlang實現
·做后端消息路由的服務器系統使用Erlang實現
·值得炫耀的是,如此龐大數量的活躍用戶只使用非常少的服務器來管理,團隊一致認為這很大程度上歸功于Erlang。
·值得注意的是,非死book Chat就是在2009年使用Erlang開發,他們棄用Erlang的原因是難以招聘到優秀的程序員。
2. WhatsApp服務器最早從Ejabberd開始
·Ejabberd是個非常出名的開源Jabber服務器,使用Erlang實現。
·最初選用它的原因是開放、廣受開發者關注、易于開始以及Erlang在大型通信系統上的長期口碑。
·接下來的許多年一直從事Ejabberd的重寫和修改,包括從XMPP轉換到內部開發協議、調整代碼庫以及重設計一些核心組件,對Erlang VM做了大量的修改以獲得高性能。
3. 為了應對每天500億消息,工作重心被放到可靠系統的打造上,貨幣化對于我們來說還是件遙遠的事情。
4. 系統的健康狀況主要看隊列的長度,每個節點上消息隊列的長度都會被一直監控,超過預先設置的臨界值則會發出提醒,多個警報發生則標志著系統進入了下一個瓶頸。
5. 通過上傳圖片、音頻、視頻到一個HTTP服務器上來發送多媒體消息,然后將鏈接與Base64編碼的縮略圖一起添加到內容(如果可用)。
6. 有些代碼基本上每天都在變化,通常情況下是一天幾次;當然,峰值期間必須避開的。Erlang非常適用于將修改或者是新功能添加到產品,熱加載意味著無需重新啟動就可以實現修改,錯誤可以很快的得到解決,同樣通過熱加載,系統變得更加松耦合,這可以讓更新快速的發布。
7. WhatsApp 使用了什么樣的協議?WhatsApp服務器池使用了SSL Socket,在客戶端重新連接對消息進行檢索之前,所有消息都會在服務器上排隊。消息的成功檢索會發回給WhatsApp服務器,它將會被重新轉發給原始發送者;一旦客戶端成功接收這條消息,它就會在服務器存儲中擦除。
8. WhatsApp 注冊程序的內部工作機制是什么樣的?WhasApp依賴電話IMEI號碼來建立用戶名/密碼,這點在最近已經修改。WhatsApp現在會讓應用發送一個包含5位數Pin的一般請求,然后給這個電話號碼發送一個SMS,這意味著WhatsApp客戶端不再受限于某臺手機。基于Pin的號碼,應用會從 WhatsApp請求一個唯一的鍵,這個鍵將作為未來的使用密碼,這同樣意味著在新的設備上注冊后會無效原有設備上的鍵。
結果
開始時每個服務器有20萬個并發連接。
第一個瓶頸出現每臺服務器42.5萬個連接的時候。系統遇到了很多沖突,工作停止了。安裝調度器檢測有多少有用的任務被停止、睡眠,或回轉了。在加載時,它開始遇到睡眠鎖,整個系統只用35-45%的CPU利用率,但調度程序的CPU利用率卻達到了95%。
第一輪修復使連接數超過100萬個。
VM利用率為76%,CPU利用率為73%,BEAM模擬器利用率為45%,與用戶百分比很吻合,這是件好事,因為模擬器得和用戶一樣。
通常CPU利用率并不是好的評估方法,因為可能由于調度程序使用CPU導致系統看起來很忙。
一個月以后解決了瓶頸,每個服務器連接數達到200萬個。
BEAM利用率為80%,與FreeBSD開始分頁的情況接近。CPU利用率大致相同,有兩倍的連接數。調度程序遇到了沖突,但運行得很好。
看來測試可以暫停了,這時開始分析Erlang代碼。
最初每個連接有兩個Erlang進程,消減為一個。
用計時器完成一些工作。
在每個服務器有280萬連接時達到頂峰
571k pkts/sec, >200k dist msgs/sec
做一些內存優化,VM加載下降到70%。
嘗試過將連接數增加到300萬,但沒有成功。
·當系統遇到故障時,查看長消息隊列(單個消息隊列或消息隊列總和)。
·將每個進程的消息隊列統計添加到BEAM設備上。包括發送/接收了多少條消息以及發送/接收的速度。
二:RabbitMq
這個相信大家都聽說過,世界上最好的企業消息隊列系統之一。
三:Web框架
Mochiweb,CowBoy等
四:電信級別的應用
愛立信等電信公司
五:游戲服務器領域的大范圍應用
特別是在頁游和手游領域,erlang簡直如魚得水,用erlang開發出的千萬級流水游戲也是數不勝數
六:數據庫
CouchDB,Riak等
七:其他領域的應用
目前據我所知,在銀行業務,醫療業務,云業務領域都可以看到erlang活躍的身影.
為什么我要選擇Erlang呢?
一、erlang特別適合中小團隊創業:
erlang有異常成熟、經過電信級別大規模驗證的OTP應用庫,只需要很簡單的代碼就能建立起異常穩定、容錯性強、擴展性強、高并發的服務器框架,這也是erlang最寶貴的核心價值所在。
二、erlang是天生的并發語言:
erlang的并發特性是語言級別的,從開發伊始就采用了CSP并發模式, 以進程為單位,進程間沒有共享內存,變量不可變的實現方式保證了無鎖的并發模型,因此也是異常高效的,換句話說:你只要像平常一樣寫代碼就能并發,完全不用操心任何底層實現,你的代碼能完美的并行運行在多核服務器上,如果你能寫出漂亮的并發級別的算法和代碼(盡量少的順序代碼),那在32核機器上就能跑出 32倍性能!!!! Go 語言的并發模型也是取經于Erlang,但是我認為Erlang的并發模型更優秀,因為進程間完全沒有共享內存,完全無鎖。
三、再介紹下我當初的業務需求:
一款多人在線游戲,一個玩家走一步都要把消息廣播給同屏的玩家,玩家聊天,戰斗更涉及到大量的消息廣播;如何應對?再有一個及其普通卻不太容易搞定的的需求:在線玩家列表怎么實現?是啊,你是不是在想用哪種鎖合適?提到的兩個場景的關鍵詞是:高并發,大量廣播;可能你還會想到"鎖".
我嘗試過在.net下使用完成端口+TPL庫+protocol buffer來完成上面的功能,但是并沒有通過測試的檢驗,測試模型是聊天.在收發消息方面,客戶端和服務器一對一的收發壓力不大,但是一旦開啟廣播,壓力一下就上去了.對象的頻繁創建會導致垃圾回收,而垃圾回收會導致CPU和內存都飄忽不定,中間加入對象池會得到一定緩解,但是不能徹底解決問題,然后想到的就是人為干預垃圾回收,判斷標準是什么呢?那就是用PerformanceCounter吧,結果發現PerformanceCounter一次調用分配的內存相當大!最后一版的結果是:聊天室模型,一人說話廣播給所有人,300人在線能夠穩定,人數一多就開始不淡定了.這些都是經過量化分析得出的結果,使用的工具是Visual Studio2010中的Performace Profile工具.
需要解決的第二個問題就是并發加鎖,最簡單的測試模型就是在線玩家列表.這個問題同樣困擾了我很久,嘗試各種鎖,還是在拋異常,要么就是性能的下降,問題此起彼伏.后續還要解決TCP通信的數據格式,以及粘包等問題......
項目時間緊張,存在的風險很多,要盡快把技術方案確定下來然后去推進別的事情;但是可供選擇的方案有C++和Erlang.坦白講我和團隊的基礎如果使用C++方案,一定能搞出來,但是排錯和性能優化將是一個巨大的挑戰.那么Erlang呢?從開篇引用的那段文字看,好像這就是我需要的,簡單了解了一下語法,還是很驚喜,由于之前對F#有過接觸,一下感覺很親切.而且我特別關注到:
優點:
1.面向并發,有成熟而且久經考驗的框架可供使用,網絡部分已經經過了良好封裝
2.內存緩存解決方案進程字典,前者的讀寫速度是50NS-100Ns級別的
3.對二進制數據解析的語法是直觀,簡單,強大(游戲中有大量的二進制數據要處理
4.沒有共享內存! 沒有鎖!(我們在代碼中沒有過顯示使用鎖)
缺點
1.從一種語言過渡到另一種語言,會有各種不爽:
2.控制邏輯簡單只有if 和 case ,而且有if沒有else,沒有continue break goto
3.包括kernel庫和standlib庫在內,很多函數和變量的命名和傳統語言不一樣
因此我們就決定了采用erlang來重新寫一套全新的架構,事實證明當初的決定是無比正確的,一個極少需要重啟、能熱更、穩定的游戲服務器實在是太重要了,而且開發過程和維護是如此的快速和輕松,我們的團隊一致認為:從來沒有想過開發會是這么一件愉快的事情!
既然Erlang已經被我“吹”的快飛起來了,為什么還要使用Go?
鑒于Go語言已經婦孺皆知了,我也就不介紹了,大概說說我自己的情況,我這人沒啥其他興趣愛好,業余時間絕大部分都花費在所謂的“程序員要不停的學習才不會落伍”上,因此在11年的時候,知道了go,斷斷續續學習了一年后,Go1.1版本出來后,發現改進很大,就開始認真研究并常年混跡在google-group及國外大牛的博客世界中,自我感覺還可以。當然我絕對不是Go的“朝圣者”,也發現Go確實不是非常完美,具體可以參見“為什么我要放棄Go“,此文作者的觀點我雖然不敢完全茍同,但是有些觀點還是贊同的,比如說很多Go愛好者是非常護短的,如果你敢說什么“壞話”,就等著被查水表吧 ;)。
由于Erlang和Go都是非常棒的語言,這里就出現一個問題:二選其一還是物盡其用?經過深思熟慮后,我和團隊選擇了后者。首先,erlang的OTP寫服務器并發框架非常之簡單、穩定且高性能,erlang的Mnesia數據庫也是很輕量:速度很快,分布式簡單,使用起來也很原生態(是Erlang標準庫支持的),所有的這些都能把程序員從繁瑣的工作中解放出來,但是,erlang 也有個挺重要的問題(在不同業務場景中此問題也許很突出,也可能完全無關緊要,至少85%的情況下不算一個問題):它是虛擬機語言,對于順序代碼的執行速度只有C的七分之一,雖然可以利用多核的優勢,但是在大型mmorpg中,消息密集時,CPU的瓶頸還是挺明顯的,會影響玩家順暢的體驗感覺 (ARPG)。
因此我就想如果邏輯這部分用Go來寫,是不是可以很好的利用這兩個語言的優點進行互補?心動不如行動,由于我們的erlang游戲架構的藕合度還是挺低的,因此分離出來地圖服務器,用Go重新實現了下,通過socket跟erlang架構部分進行通信,發現效果異常之好,Go的性能、并發的原生支持再配合上erlang寫游戲框架,在性能上已經絕不亞于C++框架,但是后者大家都懂,中關村程序員據說平均壽命50多歲,很大的一部分原因是因為這個。
以后的路怎么走?
混合型編程會是以后的主流,因為沒有哪個語言是完美的,包括被眾多“朝圣者”所推崇的Go,如果我們能根據自己的業務場景,選對合適的語言,那不敢說事半功 10倍,至少事半功倍應該是有的,所以不要被主流語言(Java,C++)禁錮了我們的世界,局限了我們的創新,如果能做到輕松愉快的開發,那這個世界該多美好!!