简单、省钱、快速:Playtomic由.NET改用Node和Heroku

译文
运维 系统运维
Playtomic是一项游戏分析服务,这项服务应用于每天大约2000万人在玩的约8000个手机游戏、互联网游戏和可下载游戏。本文是Playtomic首席执行官Ben Lowry接受Hacker News采访时给出的精辟总结。

【51CTO快译】Playtomic是一项游戏分析服务,这项服务应用于每天大约2000万人在玩的约8000个手机游戏、互联网游戏和可下载游戏。

[[100495]]

下面是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)积分表(不包括积分)

基本模式如下:

  1. var cache = {};   
  2. module.exports = function(request, response) {  
  3.    response.end(cache[“x”]);  
  4. }  
  5. function refresh() {  
  6.    // 从数据库提取更新后的数据,存储在缓存对象中  
  7.    cache[“x”] = “foo”;  
  8.    setTimeout(refresh, 30000);  
  9. }  
  10. refresh(); 

其优势在于每个dyno或实例而不是每个用户有一条通道连接至你的后端数据库,还有速度非常快的本地内存缓存总是存有新数据。dyno是在Heroku上运行的单一Web进程,它每次能够服务于一次Web请求(网页浏览)。

要注意的地方是,你的数据集必须很小,这与其他数据都在同一个线程上操作,所以你要注意,尽量避免堵塞线程或处理过于繁重的处理器工作。

持久性连接

对我们的API而言,NodeJS较之.NET的另一大好处是持久性数据库连接。使用.NET来连接的传统方法是,打开你的连接,进行操作,之后你的连接返回到连接池,以便马上重复使用,或者如果不再需要,就过期作废。

这很常见;除非你遇到并发性很高的环境,否则它完全可行。在并发性很高的环境下,连接池无法足够快地重新使用连接,这意味着它会生成新的连接,而数据库服务器就不得不扩展,以便处理这些新连接。

在Playtomic,我们通常同时有几十万个游戏玩家在发送事件数据,这些事件数据需要推送回到我们在一个不同数据中心的Redis实例;如果使用.NET,那就需要建立大量的连接——这就是为什么我们当初在每一台旧的专用服务器上本地运行MongoDB。

借助NodeJS,我们每个dyno/实例就有一个连接,负责推送某个dyno收到的所有事件数据。它不依赖诸如此类的请求模式:

  1. var redisredisclient  = redis.createClient(….);   
  2. module.exports = function(request, response) {  
  3.  
  4.    var eventdata = “etc”;  
  5.  
  6.    redisclient.lpush(“events”, eventdata);  
  7.  

  ***结果

  高负载:

  上一分钟的请求:

  1. _exceptions: 75 (0.01%)   
  2. _failures: 5 (0.00%)   
  3. _total: 537,151 (99.99%)    
  4. data.custommetric.success: 1,093 (0.20%)   
  5. data.levelaveragemetric.success: 2,466 (0.46%)   
  6. data.views.success: 105 (0.02%)   
  7. events.regular.invalid_or_deleted_game#2: 3,814 (0.71%)   
  8. events.regular.success: 527,837 (98.25%)   
  9. gamevars.load.success: 1,060 (0.20%)  
  10. geoip.lookup.success: 109 (0.02%)   
  11. leaderboards.list.success: 457 (0.09%)   
  12. leaderboards.save.missing_name_or_source#201: 3 (0.00%)  
  13. leaderboards.save.success: 30 (0.01%)    
  14. leaderboards.saveandlist.success: 102 (0.02%)    
  15. playerlevels.list.success: 62 (0.01%)    
  16. 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  [||                                                    1.3%] 任务:83个;4个运行中  
  2. 2  [|||||||||||||||||||                                            19.0%] 负载平均:1.28 1.20 1.19   
  3.  3  [||||||||||                                                9.2%] 正常运行时间:12天21小时48分33秒  
  4.  4  [||||||||||||                                              11.8%]  
  5. 5  [||||||||||                                                9.9%]  
  6.  6  [|||||||||||||||||                                           17.7%]  
  7.  7  [|||||||||||||||                                            14.6%]  
  8.  8  [|||||||||||||||||||||                                          21.6%]  
  9.  9  [||||||||||||||||||                                           18.2%]  
  10.  10 [|                                                   0.6%]  
  11.  11 [                                                   0.0%]  
  12.  12 [|||||||||                                                9.8%]  
  13.  13 [||||||||||                                               9.3%]  
  14.  14 [||||||                                                 4.6%]  
  15.  15 [||||||||||||||||                                            16.6%]  
  16.  16 [|||||||||                                                8.0%]  
  17.  Mem[|||||||||||||||                                         2009/24020MB]  
  18. Swp[                                                   0/1023MB]  
  19.  
  20. PID USER     PRI  NI VIRT   RES   SHR  S  CPU% MEM%   TIME+  Command  
  21. 12518 redis     20   0 40048  7000   640 S  0.0  0.0  2:21.53  `- /usr/local/bin/redis-server /etc/redis/analytics.conf  
  22. 12513 redis     20   0 72816 35776   736 S  3.0  0.1  4h06:40  `- /usr/local/bin/redis-server /etc/redis/log7.conf  
  23. 12508 redis     20   0 72816 35776   736 S  2.0  0.1  4h07:31  `- /usr/local/bin/redis-server /etc/redis/log6.conf  
  24. 12494 redis     20   0 72816 37824   736 S  1.0  0.2  4h06:08  `- /usr/local/bin/redis-server /etc/redis/log5.conf  
  25. 12488 redis     20   0 72816 33728   736 S  2.0  0.1  4h09:36  `- /usr/local/bin/redis-server /etc/redis/log4.conf  
  26. 12481 redis     20   0 72816 35776   736 S  2.0  0.1  4h02:17  `- /usr/local/bin/redis-server /etc/redis/log3.conf  
  27. 12475 redis     20   0 72816 27588   736 S  2.0  0.1  4h03:07  `- /usr/local/bin/redis-server /etc/redis/log2.conf  
  28. 12460 redis     20   0 72816 31680   736 S  2.0  0.1  4h10:23  `- /usr/local/bin/redis-server /etc/redis/log1.conf  
  29. 12440 redis     20   0 72816 33236   736 S  3.0  0.1  4h09:57  `- /usr/local/bin/redis-server /etc/redis/log0.conf  
  30. 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  
  31. 12429 redis     20   0  395M  115M   736 S 33.0  0.5 60h29:26  `- /usr/local/bin/redis-server /etc/redis/redis-pool.conf  
  32. 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  
  33. 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负载:

  1. insert  query update delete getmore command flushes mapped  vsize    res faults locked % idx miss %     qr|qw   ar|aw  netIn netOut  conn       time   
  2. 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   
  3. 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   
  4. 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   
  5. 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   
  6. 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   
  7. 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   
  8. 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   
  9. 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   
  10. 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   
  11. 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   
  12.  
  13. insert  query update delete getmore command flushes mapped  vsize    res faults locked % idx miss %     qr|qw   ar|aw  netIn netOut  conn       time   
  14. 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   
  15. 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   
  16. 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   
  17. 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   
  18. 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   
  19. 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   
  20. 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   
  21. 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   
  22. 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   
  23. 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   
  24.  
  25. insert  query update delete getmore command flushes mapped  vsize    res faults locked % idx miss %     qr|qw   ar|aw  netIn netOut  conn       time   
  26. 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   
  27. 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   
  28. 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   
  29. 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   
  30. 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   
  31. 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   
  32. 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   
  33. 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   
  34. 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   
  35. 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   
  36.  
  37. insert  query update delete getmore command flushes mapped  vsize    res faults locked % idx miss %     qr|qw   ar|aw  netIn netOut  conn       time   
  38. 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   
  39. 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   
  40. 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   
  41. 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/2012/10/15/simpler-cheaper-faster-playtomics-move-from-net-to-node-and.htm

责任编辑:黄丹 来源: 51CTO.com
相关推荐

2011-12-25 20:16:41

应用

2019-09-23 13:30:10

5G运营商共享

2009-08-20 18:44:54

C#和ADO.NET

2009-08-18 16:57:24

VB.NET和C#

2009-11-11 13:38:04

ADO.NET sql

2016-09-30 15:40:36

容器虚拟化

2009-10-09 16:00:34

VB6.0开发

2009-08-25 14:42:41

由C++转向C#

2022-09-05 09:37:38

Linux发行版

2009-07-28 16:08:43

ASP.NET AJA

2016-08-25 21:28:04

前端node截图

2021-09-11 19:04:38

.NetSoapCore协议

2012-06-20 14:34:03

jQuery

2009-11-03 14:13:23

VB.NET C#

2024-01-09 08:50:32

LiteDB数据库NoSQL

2009-10-09 16:11:33

VB.NET语法

2010-12-14 11:00:34

Salesforce.HerokuVMforce

2009-09-11 11:30:53

Net60C#.NET

2012-08-15 10:02:17

2009-11-10 11:04:09

VB.NET数据类型
点赞
收藏

51CTO技术栈公众号