簡單、省錢、快速:Playtomic由.NET改用Node和Heroku
譯文【51CTO快譯】Playtomic是一項游戲分析服務,這項服務應用于每天大約2000萬人在玩的約8000個手機游戲、互聯(lián)網(wǎng)游戲和可下載游戲。
下面是Playtomic***執(zhí)行官Ben Lowry接受Hacker News采訪時給出的精辟總結(jié):
昨天就有超過2000萬的人點擊了我的API,點擊次數(shù)達700749252人次,在玩大約8000個采用我那個分析平臺的游戲,玩游戲的時間加起來總共將近600年。這只是昨天的情況。許多不同的瓶頸擺在從事大規(guī)模運營的人的面前,有待克服。就我的使用情況而言,Heroku和NodeJS最終克服了一大堆瓶頸,而且成本非常低。
Playtomic一開始幾乎完全采用了微軟.NET和Windows架構(gòu),這套架構(gòu)運行了3年,后來被換成了使用NodeJS全面改寫的架構(gòu)。在使用期間,整個平臺從原來一臺服務器上的共享空間,擴展到一臺完全專用的服務器,后來擴展到第二臺專用服務器,隨后API服務器被卸載到一家虛擬專用服務器(VPS)提供商,再后來擴展到四六家相當大的VPS。***,API服務器選用了Hivelocity的8臺專用服務器,每臺專用服務器搭載采用超線程技術(shù)的八核處理器、8GB內(nèi)存以及運行API堆棧3個或4個實例的雙500GB磁盤。
這些服務器經(jīng)常同時服務于30000至60000個游戲玩家,每秒收到的請求多達1500個,通過DNS輪詢技術(shù)(DNS round robin),實現(xiàn)負載均衡機制。
今年7月份,整批服務器被換成了使用NodeJS改寫的服務器,這些服務器托管在Heroku處,節(jié)省了大筆費用。
借助NodeJS擴展Playtomic
遷移過程涉及兩個部分:
1.專門使用平臺即服務(PaaS):PaaS的優(yōu)勢包括:價格低、使用方便、可以利用提供商的負載均衡機制以及降低總的復雜性。缺點包括:NodeJS沒有New Relic應用性能管理工具,崩潰處理起來很棘手,以及平臺總體上欠成熟。
2.由.NET改用NodeJS:原來的架構(gòu)是ASP.NET / C#,帶本地MongoDB實例,一項服務在本地預處理事件數(shù)據(jù),并發(fā)送至集中式服務器有待完成,改為Heroku + Redis上的NodeJS以及SoftLayer上的預處理(參閱Catalyst程序)。
專門使用PaaS
降低復雜性方面成效顯著。我們有8臺專用服務器,每臺服務器在我們的托管合作伙伴Hivelocity運行API的3個或4個實例。每臺服務器運行一小組軟件,包括:
■MongoDB實例
■日志預處理服務
■監(jiān)控服務
■API站點的IIS
通過FTP腳本來完成部署工作,該腳本將新的API站點版本上傳到了所有服務器。服務部署起來比較煩人,不過不常變化。
說到日志數(shù)據(jù)被預處理并發(fā)送之前先暫時存起來,MongoDB是個差強人意的選擇。雖然它在速度方面提供了巨大優(yōu)勢:起初只要寫入到內(nèi)存,這意味著寫入請求幾乎立即被“完成”——這比Windows上的常用消息隊列出色得多,但是根本無法收回已刪除數(shù)據(jù)留下的空間;這意味著,要是不經(jīng)常加以壓縮,數(shù)據(jù)庫大小會迅速增加到100GB以上。
PaaS提供商的優(yōu)勢相當顯而易見,它們似乎都很相似,不過Heroku和Salesforce最讓人放心,因為它們似乎是最成熟的,而且得到了廣泛的技術(shù)支持。
遷移到PaaS面臨的主要挑戰(zhàn)在于改變這個觀念:我們可以讓輔助軟件連同網(wǎng)站一起運行,就像之前在專用服務器上那樣。大多數(shù)平臺提供了你可以充分利用的某種background worker線程,但是這意味著你要通過第三方服務或看似沒有必要的服務器,傳送來自萬維網(wǎng)線程的數(shù)據(jù)和任務。
我們最終選擇了Softlayer處的一臺大型服務器,它運行十幾個專用的Redis實例和一些中間件,而不是background worker。Heroku并不針對出站帶寬收費,Softlayer并不針對入站帶寬收費,這有效地避免了所需的大量帶寬。
由.NET改用NodeJS
在服務器端處理JavaScript是個有利也有弊的過程。一方面,不用拘泥形式和沒有樣板帶來了自由。而另一方面,沒有New Relic管理工具,也沒有編譯錯誤,這就加大了各方面的難度。
兩個主要的優(yōu)勢讓NodeJS特別適用于我們的API。
1. background worker與Web服務器在同一個線程和內(nèi)存中。
2.與Redis和MongoDB建立起了持久式、共享式的連接。
Background worker
NodeJS有這項非常有用的功能:可以獨立于請求而繼續(xù)工作,這樣你可以預取數(shù)據(jù)及其他操作,讓你可以很早終止請求,然后完成處理請求的任務。
對我們來說特別有優(yōu)勢的地方是,可以在內(nèi)存中復制整批的MongoDB集合,定期刷新,那樣全部的工作類都可以訪問目前數(shù)據(jù),沒必要用到外部數(shù)據(jù)庫或本地/共享緩存層。
我們在下列方面使用這項功能,每秒可以少接受100次至1000次的數(shù)據(jù)庫查詢:
■主API上的游戲配置數(shù)據(jù)
■數(shù)據(jù)導入API上的API證書
■開發(fā)人員用來存儲配置或其他數(shù)據(jù)、熱裝入到游戲中的GameVars功能
■游戲積分榜(Leaderboard)積分表(不包括積分)
基本模式如下:
- var cache = {};
- module.exports = function(request, response) {
- response.end(cache[“x”]);
- }
- function refresh() {
- // 從數(shù)據(jù)庫提取更新后的數(shù)據(jù),存儲在緩存對象中
- cache[“x”] = “foo”;
- setTimeout(refresh, 30000);
- }
- refresh();
其優(yōu)勢在于每個dyno或?qū)嵗皇敲總€用戶有一條通道連接至你的后端數(shù)據(jù)庫,還有速度非??斓谋镜貎?nèi)存緩存總是存有新數(shù)據(jù)。dyno是在Heroku上運行的單一Web進程,它每次能夠服務于一次Web請求(網(wǎng)頁瀏覽)。
要注意的地方是,你的數(shù)據(jù)集必須很小,這與其他數(shù)據(jù)都在同一個線程上操作,所以你要注意,盡量避免堵塞線程或處理過于繁重的處理器工作。
持久性連接
對我們的API而言,NodeJS較之.NET的另一大好處是持久性數(shù)據(jù)庫連接。使用.NET來連接的傳統(tǒng)方法是,打開你的連接,進行操作,之后你的連接返回到連接池,以便馬上重復使用,或者如果不再需要,就過期作廢。
這很常見;除非你遇到并發(fā)性很高的環(huán)境,否則它完全可行。在并發(fā)性很高的環(huán)境下,連接池無法足夠快地重新使用連接,這意味著它會生成新的連接,而數(shù)據(jù)庫服務器就不得不擴展,以便處理這些新連接。
在Playtomic,我們通常同時有幾十萬個游戲玩家在發(fā)送事件數(shù)據(jù),這些事件數(shù)據(jù)需要推送回到我們在一個不同數(shù)據(jù)中心的Redis實例;如果使用.NET,那就需要建立大量的連接——這就是為什么我們當初在每一臺舊的專用服務器上本地運行MongoDB。
借助NodeJS,我們每個dyno/實例就有一個連接,負責推送某個dyno收到的所有事件數(shù)據(jù)。它不依賴諸如此類的請求模式:
- var redisredisclient = redis.createClient(….);
- module.exports = function(request, response) {
- var eventdata = “etc”;
- redisclient.lpush(“events”, eventdata);
- }
***結(jié)果
高負載:
上一分鐘的請求:
- _exceptions: 75 (0.01%)
- _failures: 5 (0.00%)
- _total: 537,151 (99.99%)
- data.custommetric.success: 1,093 (0.20%)
- data.levelaveragemetric.success: 2,466 (0.46%)
- data.views.success: 105 (0.02%)
- events.regular.invalid_or_deleted_game#2: 3,814 (0.71%)
- events.regular.success: 527,837 (98.25%)
- gamevars.load.success: 1,060 (0.20%)
- geoip.lookup.success: 109 (0.02%)
- leaderboards.list.success: 457 (0.09%)
- leaderboards.save.missing_name_or_source#201: 3 (0.00%)
- leaderboards.save.success: 30 (0.01%)
- leaderboards.saveandlist.success: 102 (0.02%)
- playerlevels.list.success: 62 (0.01%)
- playerlevels.load.success: 13 (0.00%)
這些數(shù)據(jù)來自在后臺運行的針對每個實例的某種負載監(jiān)控系統(tǒng),把計數(shù)器推送到Redis,然后它們被匯集起來,存儲在MongoDB中,你可以在https://api.playtomic.com/load.html看到實際過程。
該數(shù)據(jù)有幾種不同類別的請求:
■事件,檢查來自MongoDB的游戲配置、執(zhí)行GeoOP查詢(采用了非??斓拈_源實現(xiàn)方式,詳見https://github.com/benlowry/node-geoip-native),然后推送至Redis。
■GameVars、積分榜和玩家關(guān)卡都檢查來自MongoDB的游戲配置,然后檢查任何相關(guān)的MongoDB數(shù)據(jù)庫。
■數(shù)據(jù)查詢被代理給Windows服務器,那是由于NodeJS對存儲過程的支持很糟糕。
結(jié)果是100000個并發(fā)用戶給Redis帶來的負載特別輕,每分鐘有500000次至700000次lpush(在另一端被取出來)。
- 1 [|| 1.3%] 任務:83個;4個運行中
- 2 [||||||||||||||||||| 19.0%] 負載平均:1.28 1.20 1.19
- 3 [|||||||||| 9.2%] 正常運行時間:12天21小時48分33秒
- 4 [|||||||||||| 11.8%]
- 5 [|||||||||| 9.9%]
- 6 [||||||||||||||||| 17.7%]
- 7 [||||||||||||||| 14.6%]
- 8 [||||||||||||||||||||| 21.6%]
- 9 [|||||||||||||||||| 18.2%]
- 10 [| 0.6%]
- 11 [ 0.0%]
- 12 [||||||||| 9.8%]
- 13 [|||||||||| 9.3%]
- 14 [|||||| 4.6%]
- 15 [|||||||||||||||| 16.6%]
- 16 [||||||||| 8.0%]
- Mem[||||||||||||||| 2009/24020MB]
- Swp[ 0/1023MB]
- PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
- 12518 redis 20 0 40048 7000 640 S 0.0 0.0 2:21.53 `- /usr/local/bin/redis-server /etc/redis/analytics.conf
- 12513 redis 20 0 72816 35776 736 S 3.0 0.1 4h06:40 `- /usr/local/bin/redis-server /etc/redis/log7.conf
- 12508 redis 20 0 72816 35776 736 S 2.0 0.1 4h07:31 `- /usr/local/bin/redis-server /etc/redis/log6.conf
- 12494 redis 20 0 72816 37824 736 S 1.0 0.2 4h06:08 `- /usr/local/bin/redis-server /etc/redis/log5.conf
- 12488 redis 20 0 72816 33728 736 S 2.0 0.1 4h09:36 `- /usr/local/bin/redis-server /etc/redis/log4.conf
- 12481 redis 20 0 72816 35776 736 S 2.0 0.1 4h02:17 `- /usr/local/bin/redis-server /etc/redis/log3.conf
- 12475 redis 20 0 72816 27588 736 S 2.0 0.1 4h03:07 `- /usr/local/bin/redis-server /etc/redis/log2.conf
- 12460 redis 20 0 72816 31680 736 S 2.0 0.1 4h10:23 `- /usr/local/bin/redis-server /etc/redis/log1.conf
- 12440 redis 20 0 72816 33236 736 S 3.0 0.1 4h09:57 `- /usr/local/bin/redis-server /etc/redis/log0.conf
- 12435 redis 20 0 40048 7044 684 S 0.0 0.0 2:21.71 `- /usr/local/bin/redis-server /etc/redis/redis-servicelog.conf
- 12429 redis 20 0 395M 115M 736 S 33.0 0.5 60h29:26 `- /usr/local/bin/redis-server /etc/redis/redis-pool.conf
- 12422 redis 20 0 40048 7096 728 S 0.0 0.0 26:17.38 `- /usr/local/bin/redis-server /etc/redis/redis-load.conf
- 12409 redis 20 0 40048 6912 560 S 0.0 0.0 2:21.50 `- /usr/local/bin/redis-server /etc/redis/redis-cache.conf
就每分鐘1800至2500次創(chuàng)建、讀取、更新和刪除(CRUD)而言,這是非常輕的MongoDB負載:
- insert query update delete getmore command flushes mapped vsize res faults locked % idx miss % qr|qw ar|aw netIn netOut conn time
- 2 9 5 2 0 8 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 7k 116 01:11:12
- 1 1 5 2 0 6 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 2k 3k 116 01:11:13
- 0 3 6 2 0 8 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 6k 114 01:11:14
- 0 5 5 2 0 12 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 5k 113 01:11:15
- 1 9 7 2 0 12 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 4k 6k 112 01:11:16
- 1 10 6 2 0 15 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 1|0 4k 22k 111 01:11:17
- 1 5 6 2 0 11 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 19k 111 01:11:18
- 1 5 5 2 0 14 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 3k 111 01:11:19
- 1 2 6 2 0 8 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 2k 111 01:11:20
- 1 7 5 2 0 9 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 2k 111 01:11:21
- insert query update delete getmore command flushes mapped vsize res faults locked % idx miss % qr|qw ar|aw netIn netOut conn time
- 2 9 8 2 0 8 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 5k 111 01:11:22
- 3 8 7 2 0 9 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 9k 110 01:11:23
- 2 6 6 2 0 10 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 4k 110 01:11:24
- 2 8 6 2 0 21 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 93k 112 01:11:25
- 1 10 7 2 3 16 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 4m 112 01:11:26
- 3 15 7 2 3 24 0 6.67g 14.8g 1.23g 0 0.2 0 0|0 0|0 6k 1m 115 01:11:27
- 1 4 8 2 0 10 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 2m 115 01:11:28
- 1 6 7 2 0 14 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 3k 115 01:11:29
- 1 3 6 2 0 10 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 103k 115 01:11:30
- 2 3 6 2 0 8 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 12k 114 01:11:31
- insert query update delete getmore command flushes mapped vsize res faults locked % idx miss % qr|qw ar|aw netIn netOut conn time
- 0 12 6 2 0 9 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 31k 113 01:11:32
- 2 4 6 2 0 8 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 9k 111 01:11:33
- 2 9 6 2 0 7 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 3k 21k 111 01:11:34
- 0 8 7 2 0 14 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 4k 9k 111 01:11:35
- 1 4 7 2 0 11 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 3k 5k 109 01:11:36
- 1 15 6 2 0 19 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 5k 11k 111 01:11:37
- 2 17 6 2 0 19 1 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 6k 189k 111 01:11:38
- 1 13 7 2 0 15 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 1|0 5k 42k 110 01:11:39
- 2 7 5 2 0 77 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 2|0 10k 14k 111 01:11:40
- 2 10 5 2 0 181 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 21k 14k 112 01:11:41
- insert query update delete getmore command flushes mapped vsize res faults locked % idx miss % qr|qw ar|aw netIn netOut conn time
- 1 11 5 2 0 12 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 0|0 4k 13k 116 01:11:42
- 1 11 5 2 1 33 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 3|0 6k 2m 119 01:11:43
- 0 9 5 2 0 17 0 6.67g 14.8g 1.22g 0 0.1 0 0|0 1|0 5k 42k 121 01:11:44
- 1 8 7 2 0 25 0 6.67g 14.8g 1.22g 0 0.2 0 0|0 0|0 6k 24k 125 01:11:45
原文鏈接:http://highscalability.squarespace.com/blog/