【51CTO快译】Playtomic是一项游戏分析服务,这项服务应用于每天大约2000万人在玩的约8000个手机游戏、互联网游戏和可下载游戏。
下面是Playtomic***执行官Ben Lowry接受Hacker News采访时给出的精辟总结:
昨天就有超过2000万的人点击了我的API,点击次数达700749252人次,在玩大约8000个采用我那个分析平台的游戏,玩游戏的时间加起来总共将近600年。这只是昨天的情况。许多不同的瓶颈摆在从事大规模运营的人的面前,有待克服。就我的使用情况而言,Heroku和NodeJS最终克服了一大堆瓶颈,而且成本非常低。
Playtomic一开始几乎完全采用了微软.NET和Windows架构,这套架构运行了3年,后来被换成了使用NodeJS全面改写的架构。在使用期间,整个平台从原来一台服务器上的共享空间,扩展到一台完全专用的服务器,后来扩展到第二台专用服务器,随后API服务器被卸载到一家虚拟专用服务器(VPS)提供商,再后来扩展到四六家相当大的VPS。***,API服务器选用了Hivelocity的8台专用服务器,每台专用服务器搭载采用超线程技术的八核处理器、8GB内存以及运行API堆栈3个或4个实例的双500GB磁盘。
这些服务器经常同时服务于30000至60000个游戏玩家,每秒收到的请求多达1500个,通过DNS轮询技术(DNS round robin),实现负载均衡机制。
今年7月份,整批服务器被换成了使用NodeJS改写的服务器,这些服务器托管在Heroku处,节省了大笔费用。
借助NodeJS扩展Playtomic
迁移过程涉及两个部分:
1.专门使用平台即服务(PaaS):PaaS的优势包括:价格低、使用方便、可以利用提供商的负载均衡机制以及降低总的复杂性。缺点包括:NodeJS没有New Relic应用性能管理工具,崩溃处理起来很棘手,以及平台总体上欠成熟。
2.由.NET改用NodeJS:原来的架构是ASP.NET / C#,带本地MongoDB实例,一项服务在本地预处理事件数据,并发送至集中式服务器有待完成,改为Heroku + Redis上的NodeJS以及SoftLayer上的预处理(参阅Catalyst程序)。
专门使用PaaS
降低复杂性方面成效显著。我们有8台专用服务器,每台服务器在我们的托管合作伙伴Hivelocity运行API的3个或4个实例。每台服务器运行一小组软件,包括:
■MongoDB实例
■日志预处理服务
■监控服务
■API站点的IIS
通过FTP脚本来完成部署工作,该脚本将新的API站点版本上传到了所有服务器。服务部署起来比较烦人,不过不常变化。
说到日志数据被预处理并发送之前先暂时存起来,MongoDB是个差强人意的选择。虽然它在速度方面提供了巨大优势:起初只要写入到内存,这意味着写入请求几乎立即被“完成”——这比Windows上的常用消息队列出色得多,但是根本无法收回已删除数据留下的空间;这意味着,要是不经常加以压缩,数据库大小会迅速增加到100GB以上。
PaaS提供商的优势相当显而易见,它们似乎都很相似,不过Heroku和Salesforce最让人放心,因为它们似乎是最成熟的,而且得到了广泛的技术支持。
迁移到PaaS面临的主要挑战在于改变这个观念:我们可以让辅助软件连同网站一起运行,就像之前在专用服务器上那样。大多数平台提供了你可以充分利用的某种background worker线程,但是这意味着你要通过第三方服务或看似没有必要的服务器,传送来自万维网线程的数据和任务。
我们最终选择了Softlayer处的一台大型服务器,它运行十几个专用的Redis实例和一些中间件,而不是background worker。Heroku并不针对出站带宽收费,Softlayer并不针对入站带宽收费,这有效地避免了所需的大量带宽。
由.NET改用NodeJS
在服务器端处理JavaScript是个有利也有弊的过程。一方面,不用拘泥形式和没有样板带来了自由。而另一方面,没有New Relic管理工具,也没有编译错误,这就加大了各方面的难度。
两个主要的优势让NodeJS特别适用于我们的API。
1. background worker与Web服务器在同一个线程和内存中。
2.与Redis和MongoDB建立起了持久式、共享式的连接。
Background worker
NodeJS有这项非常有用的功能:可以独立于请求而继续工作,这样你可以预取数据及其他操作,让你可以很早终止请求,然后完成处理请求的任务。
对我们来说特别有优势的地方是,可以在内存中复制整批的MongoDB集合,定期刷新,那样全部的工作类都可以访问目前数据,没必要用到外部数据库或本地/共享缓存层。
我们在下列方面使用这项功能,每秒可以少接受100次至1000次的数据库查询:
■主API上的游戏配置数据
■数据导入API上的API证书
■开发人员用来存储配置或其他数据、热装入到游戏中的GameVars功能
■游戏积分榜(Leaderboard)积分表(不包括积分)
基本模式如下:
- var cache = {};
- module.exports = function(request, response) {
- response.end(cache[“x”]);
- }
- function refresh() {
- // 从数据库提取更新后的数据,存储在缓存对象中
- cache[“x”] = “foo”;
- setTimeout(refresh, 30000);
- }
- refresh();
其优势在于每个dyno或实例而不是每个用户有一条通道连接至你的后端数据库,还有速度非常快的本地内存缓存总是存有新数据。dyno是在Heroku上运行的单一Web进程,它每次能够服务于一次Web请求(网页浏览)。
要注意的地方是,你的数据集必须很小,这与其他数据都在同一个线程上操作,所以你要注意,尽量避免堵塞线程或处理过于繁重的处理器工作。
持久性连接
对我们的API而言,NodeJS较之.NET的另一大好处是持久性数据库连接。使用.NET来连接的传统方法是,打开你的连接,进行操作,之后你的连接返回到连接池,以便马上重复使用,或者如果不再需要,就过期作废。
这很常见;除非你遇到并发性很高的环境,否则它完全可行。在并发性很高的环境下,连接池无法足够快地重新使用连接,这意味着它会生成新的连接,而数据库服务器就不得不扩展,以便处理这些新连接。
在Playtomic,我们通常同时有几十万个游戏玩家在发送事件数据,这些事件数据需要推送回到我们在一个不同数据中心的Redis实例;如果使用.NET,那就需要建立大量的连接——这就是为什么我们当初在每一台旧的专用服务器上本地运行MongoDB。
借助NodeJS,我们每个dyno/实例就有一个连接,负责推送某个dyno收到的所有事件数据。它不依赖诸如此类的请求模式:
- var redisredisclient = redis.createClient(….);
- module.exports = function(request, response) {
- var eventdata = “etc”;
- redisclient.lpush(“events”, eventdata);
- }
***结果
高负载:
上一分钟的请求:
- _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%)
这些数据来自在后台运行的针对每个实例的某种负载监控系统,把计数器推送到Redis,然后它们被汇集起来,存储在MongoDB中,你可以在https://api.playtomic.com/load.html看到实际过程。
该数据有几种不同类别的请求:
■事件,检查来自MongoDB的游戏配置、执行GeoOP查询(采用了非常快的开源实现方式,详见https://github.com/benlowry/node-geoip-native),然后推送至Redis。
■GameVars、积分榜和玩家关卡都检查来自MongoDB的游戏配置,然后检查任何相关的MongoDB数据库。
■数据查询被代理给Windows服务器,那是由于NodeJS对存储过程的支持很糟糕。
结果是100000个并发用户给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次创建、读取、更新和删除(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/