基于NodeJS的高性能分布式游戏日志系统

大纲:

  • 前言
  • 日志系统架构是怎样的
  • 游戏分析有什么内容
  • 为何要本身架一个系统
  • FEN架构
    • 架构图
    • Fluentd
    • ElasticSearch
    • NodeJS
    • pusher
    • logger
    • analyser
    • 用户界面
  • 总结

前言

最近我司须要作一个统一的游戏日志系统,要求有必定的通用性,能应对公司全部的游戏业务。接下来分享一下此次日志系统的项目经验。node

日志系统架构是怎样的

目前流行的日志系统为ELK,由Beats、Logstash、Elasticsearch、Kibana等组件共同实现,但万变不离其宗,一个基本的日志系统架构相似以下:redis

基本架构

游戏分析有什么内容

游戏分析,与其它服务系统不一样的是,游戏内的系统多是天马行空的,数据类型是多样的,甚至频繁变化的。咱们要在变化中总结到不变的内容,例如系统经济产出,玩家物品消耗,商店购买等进行分析。因此此次的游戏日志系统要知足如下需求:express

  • 记录游戏日志,并随时检索日志;
  • 分析玩家行为:玩家留存相关,玩家物品消耗,商店消耗等有必定复杂度的分析;
  • 能创建一个统一的日志系统:一次性知足将来游戏运营多样性。

为何要本身架一个系统

虽然ELK在安装配置方面不算困难,插件众多,例如Filebeat,读log文件,过滤格式,转发,但谁来生产这些log文件,没有说起。实际上,业务具备多样性,只要有日志文件的地方,它就能够用。例如多数会使用Nginx进行日志收集。咱们也须要考虑到日志生产者的问题,责权分离,须要单独一台机子进行日志采集。缓存

游戏是一种技术与艺术结合的产品,数据庞杂,形态万千,光日志埋点也花很多功夫,但不能所以放弃治疗。好的游戏日志,还能够帮咱们还原玩家玩家画像。游戏更新周期短,数据变化大,须要提供更实时参照报表,为非技术人员更好友的查询界面,才能更好的服务于游戏数据分析。ELK 在这方面,基本解决了采集和储存的问题,但实现分析方面还不能知足咱们的需求。安全

通过一翻思索,咱们能够用现有工具,粘合多个套件,因此,咱们有了如下思路:服务器

  • 日志采集器:

利用Fluented做为日志文件采集器,生产者经过内网HTTP发送到采集器上,那每一个生产者同一内网只要部署一个采集器便可,若是量特别大,能够多个,游戏的功能埋点能够统一;markdown

  • 转发器:

利用NodeJS进行 HTTP 转发便可,前提是能按顺序和分段读取日志文件,结合Fluented间接实现;网络

  • 接收器与实时分析:

接收器能够用Koa实现,Redis进行缓存;同时用NodeJS另一个进程分析和日志入库,分析行为,玩家画像,得出报表,这些非日志源的数据,能够放到MongoDB上,由于这些数据是修改性增加缓慢数据,占用空间不大;数据结构

  • 储存仓库:

ElasticSearch是个很好的选择,能集群,可热增减节点,扩容,还能够全文检索,分词;架构

  • 用户界面:

Kibana针对 ElasticSearch提供良好的分析,结合原有的管理后台系统,咱们本身实现了一套用户界面。

FEN架构

这个框架主要使用到了Fluentd,ElasticSearch,以及NodeJS,我就称它为 FEN 架构吧,以下图。

架构图

FEN架构

上图看出,这样的日志架构和第一个图基本没什么不一样,只是多了后面的分析与分批入库处理,而且大量使用了NodeJS。

注:在这里不会介绍各组件的详细的安装配置方法,网上有太多了,怎样使用好每个组件才是关键。

先介绍咱们用到的工具:

Fluentd

Fluentd是一个彻底开源免费的log信息收集软件,支持超过125个系统的log信息收集。Fluentd在收集源日志方面很是方便并且高性能,经过HTTP GET就能够,这相似于Nginx的日志记录行为。它的优势是,日志文件能够高度定制化,例如咱们这里每5秒生成一个文件,这样每分钟有12个文件,每一个文件体积很是小。为何要这样作?下面会介绍。Fluentd还有很是多的插件,例如直接存入MongoDB,亚马逊云等,要是熟悉Ruby,也能够本身写插件。

ElasticSearch

有人使用MongoDB进行日志收集,是很是不明智的,只有几千万条还能够,若是半个月生产10亿条日志呢?日志文件须要保存一个月甚至更长,那么集群和硬盘维护就很是重要。使用便利性也很重要,例如分词检索,在客服回溯玩家日志,分析游戏 BUG 的时候很是有用。下文的 ES 也是该组件的简称。

NodeJS

NodeJS不适合作 CPU 密集型任务,但在网络应用方面还不错,而且是咱们正好熟悉的。日志系统对实时性要求并不高,延时半小时之内都是容许的,事实上,正常状况延时也就10来秒。下面的读与转发日志的Pusher,收集日志的logger,分析日志并数据落袋为安的的analyser,都是由NodeJS实现的。

下面继续介绍用 NodeJS实现的每个部分:

转发器Pusher

(注:这是一个nodeJS编写的服务。) 上面说到,为何Fluentd使用分割成多个小文件的方式,由于NodeJS在大文件处理方面并不友好,而且要考虑到经过网络发送到另外一台机,转发速度比读慢太多了,因此必须实现续传与断点记录功能。想一想,若是读几百 M 的文件,出现中断后,须要永久记录上次位置,下次再今后处读起,这就增长了程序复杂度。NodeJS虽然有readline模块,但测过发现并不如文件流那样可控,访模块用于交互界面尚可。相反,若是日志分割成多个小文件,则读的速度很是高效,而且每5秒一个文件,哪怕有上万条记录,文件也大不到哪里去,内存也不会占用太多,在断点续传与出错重试方面都能自如应对。若是游戏日志增多,能够增长节点来缓解文件过大的压力。

为何不直接让日志生产者直接发到Koa上?由于效率与带宽。NodeJS的适合作网站,但比专业的HTTP服务器要弱太多,4核心主机面对3000QPS就吃力,更多的关于NodeJS的性能问题,能够参考网络文章。在高并发量下,带宽是个很大的问题,尤为是须要作统一服务,面对的状况是日志机器与游戏并不在同一内网中。在10万日活下,带宽超过了50M,很是吓人,带宽但是很贵的,太高的带宽费用在这里性价比过低了。

Pusher的注意点:
  • 批量转发:不要一条条日志发,采用批量发送。根据单条日志文件大小,若是是 JSON 数据,有10多个字段,那么每次请求发送50~100条发送都是没问题的,也就几十 KB;
  • 串行序顺发送:从时间小的文件,从文件关开始发,等待上一次发送请求完成再执行下一次;
  • 发送失败保存重试:若是某一次请求失败,则保存到另一个文件目录,以时间戳做为文件名,下次重试,尽量保证数据完整性;
  • 每100毫秒读一次文件列表,检查有没有新的日志文件。虽然是每5秒产生一第二天志文件,但有可能出现效率降低致使发送速度跟不上而产生文件积压,即便是空读也是容许的,这不基本不占什么CPU。第100毫秒的间隔不要使用setInterval,应该在上一次文件发送完毕再setTimeout来执行;
  • 发送速度提供可变性,若是下面的logger效率低下,上面的100毫秒能够适当放缓一些。

日志收集器logger

这里咱们使用Koa做为日志采集器。使用Koa,不管在性能仍是开发效率上,都比expressJS高效。咱们还用到了Redis做为缓存,而不是直接在这里作分析任务,是为了尽可能提升与Pusher的对接效率,毕竟日志的生产速度是很快的,但网络传送是相对低效的。

logger的注意点:
  • 使用缓存缓存数据,如Redis;
  • 关注内存:logger与pusher是两台机子,当logger的缓存提高太快,也就是后面的分析与入库速度跟不上了,须要返回消息告知pusher放慢发送速度;
  • 安全验证:简单的方式是pusher发送时能够进行md5验证,logger验签;
  • 若是使用Redis,在Redis 4.0如下,使用list记录每条日志 ID,日志使用hash节省内存。在Redis 3.x不要使用Scan,它有BUG,就是Scan出的数量是没法肯定的,就算明确指定了条数,但有可能出现一次读数万条,也有可能一次读几十条,这对后面的分析器很是不利;
  • Redis记得开启 RDB,以及maxmemory设置,前者能够在出问题时还原状态,后者能够防止出现灾难时资源暴掉,搞崩其它服务;
  • 不管是否是使用Redis,应该使用支持管道,或者批量的方法,如redisio,根据机器效率,如每次满500条就入缓存,不满就100毫秒入一次,减小缓存操做次数能够提升效率;
  • logger能够用pm2的集群模式,提升效率。

注:pm2 3.2.2的集群可能出现集群内端口冲突的吊诡问题,建议用3.0.3或最新版本

分析器analyser:

(注:这也是一个nodeJS编写的服务。) 分析器读取Redis的内容,这里就是单进程的队列操做。到这一步,日志怎么分析,就能够很自由了。

分析器analyser的注意点:

  • 单线程能够确保每一个玩家的日志时间序列;
  • Redis的读取使用管道,一次读取数千条进行分析。参考值:目前每次读3000条进行处理,在4核心中低配置云主机下单线程占用仅为35%左右;
  • 日志存ES:源日志文件能够进行进一步分析或者格式优化,处理后的放ES,ES 就是为集群而生,经过加入子节点能够热扩容,硬盘便宜,因此先作3个节点的集群吧;
  • 配置好ES的索引(mapping),仔细考虑各字段类型,凡是要与搜索条件有关的,例如要查元宝大于多少的,那么元宝字段必须有索引,不然将没法根据该字段查找日志。还有,想要分词的必须使用text类型。日志通常不会进行汇总,由于咱们已经统计大部份内容了,因此能够适当减小doc_value,压缩率等,不然一千万条日志半小时内就吃掉1G硬盘。这须要你好好研究 ES 的索引配置了,后面还得研究 ES 的搜索,由于它比MongoDB的复杂得多,但这很值得;
  • ES和MongoDB的入库,使用批量处理,根据机器性能和系统资源找到合适的批处理数量。参考值,4核下 ES 批量入库1000条效率300ms 左右;
  • ES 配置好内存,默认是1G JVM内存,常常不够用就会崩溃。在配置文件同目录下有个jvm option文件,能够加大JVM,建议至少分配一半以上内存;
  • ES 的写入效率:不要觉得 ES 的输入速度很快,默认它是写一条更新一条索引,也就是必须等把数据更新到索引才会返回,不管使用批量处理仍是单个,日志量大的时候,批处理仅100条也会超过500ms。设置durability为async,不要立刻更新到索引;
  • ES使用别名索引,好处是当你须要重建索引时,能够经过另外从新指向到新的索引,由于 ES 不能修改索引,只能重建;
  • 在分析的时候,先还原玩家画像,对其它数据报表,组织好你的数据结构,数据量小、简单的能够同时放内存中进行计数,并按期条件清理,大的如玩家画像放redis中,按期更新入库。这些数据的缓存方式可使用完整版本,简化问题,减小出现脏数据的可能;同时分析也要注意效率的问题,例若有Mongodb数据的读写,要务必配置到index,不然将引发灾难性效率降低。

用户界面

由于咱们自己有后台管理系统,因此咱们很方便的把用户画像与其它分析点接了入去,在查询玩家行为时,咱们搜索ES,在查询分析报表时,咱们查询MongoDB中的数据。固然咱们也使用了Kibana来知足可能的需求。

总结

目前该日志系统运行1个半月,由纯MongoDB到结合 ES,走了很多弯路,还好如今终于稳定下来。目前在性能方面,logger 与 analyser都在同一台机,平均 CPU 为23%左右,高峰47%左右,说明还有更大的机器压榨空间。

内存方面,在高峰期5G 之内,整体很是平稳没多大波动,其中redis内存使用为800MB之内,但机器是16G,还有很大余量保障。

NodeJS 的脚本中,logger的CPU占用更小,3条进程,每条才3%,每条内存占用不到100MB。analyser 的 CPU 与内存占用多一点,这一点能够经过脚本内的参数调整,例如内存计数的内容清理得更快,使用pm2的话设置max_memory_restart : '4G' 均可以提升稳定性。

以上是我在游戏日志系统中的经验总结。

新的挑战

以上方式,是【本地文件收集】-【推送到】-【分析】-【入库与分析结果保存】,万一须要对已经入库的数据从新分析呢?例如之前有个数据运营漏了放到需求里,须要对某个时期的日志从新分析。

解决思路有2个:

  • 运营人员导出日志,从新分析。缺点是须要大量人工处理;
  • 把日志分析模块独立出来,不要在入库的时候分析,在入库后分析,就是把原来的前分析改成后分析。后分析好处是模块化,统计日志的时间点可选,还能够作到后台界面里随时统计。有点kibana的意味。

参考文献:

注:本文首次发布与简书

相关文章
相关标签/搜索