本文是野狗资深工程师廖斌旭在“iGeek Camp”北京站第4期上进行的《基于Flume的野狗实时日志系统的演进和优化》的演讲实录,主要分为两个部分:野狗日志系统的架构演进和优化方案。 野狗官博:https://blog.wilddog.com/ 野狗官网:https://www.wilddog.com/ 公众订阅号:wilddogbaas
在讲解日志架构之前,先介绍一下我们野狗的业务。我们的业务共有两类SDK,基于两种连接技术。第一类是WebSocket长连接或用Long polling模拟的长连接,另外一类是HTTP REST短连接。 大家看这张图,野狗的业务架构分为两层,第一层是接入层,包括NodeJs接入层、Nginx Rest API接入层;第二层是核心业务处理层,包括数据处理和Push server同步推送。SDK通过长连接或短连接与接入层相连接。所有的请求都通过SDK发送到接入层,接入层转化协议,然后转发到业务处理层。所以接入层是用户访问的入口,天生适合做流控和统计。 野狗的日志业务主要分为两类:日常日志和计费服务。日常统计包括UV、PV以及客户的API操作记录。后面又因为业务需求,增加了计费服务,主要包括流量统计和连接数统计。 我们的第一版,很简单。使用crontab命令来定时执行rsync命令,同步日志文件。 从这张图可以看到,接入层通过打本地日志的方式,把请求信息记录下来。然后使用crontab定时执行rsync同步日志到“日志归档服务器”。处理统计业务的服务“cloud-stat”,定时从“日志归档服务器”获取日志文件,然后把计算的流量统计结果保存到数据库中。但是设计架构的目标并不仅仅是完成需求,同时还要知道它的优势和劣势,才能了解架构下一步的改进方向。接下来,我们来看看v0.1版本的优缺点。 最突出的优点是简单,使用两条命令就能搞定。使用rsync命令比SCP更灵活,因为前者支持增量拷贝。还有一个优点就是支持数据压缩。
但是因为有了流量数据,所以想做实时流控,还是挺难的。因为定时同步最快能够做到分钟级别,实时性不够。文件在多台服务器之间流转,也增加管理的文件的成本。文件的多副本,同时也会造成存储的浪费。
所以基于实时性的考虑, 我们又改进了日志系统的架构。这就是我们的v0.2版。 介绍v0.2架构之前,先描述下项目背景。因为项目时间非常紧张,所以我们引进了redis做流量数据的缓存。但是这个方案并不是行业的最优的做法,不过当时已经搭建redis集群,正好能够解决实时性的需求。 我们来看下下面这张图。Redis作为生产者消费者模式的缓冲队列,接收“接入层”的流程数据,并交给统计业务消费。NodeJs接入层和Nginx Rest API接入层,把流量数据通过redis的客户端写入Redis集群。统计业务cloud-stat 则定时从redis集群消费。时间从v0.1的最快分钟级别,降到现在5秒内完成日志产生、收集、处理整个过程。这对于我们系统的实时性是一个很大的提升。 经过改进之后,日志系统的延迟降低了,这也是我们v0.2的主要目的。但是,任何系统都不是完美的。日志文件记录我们的最原始的用户访问记录,这些数据我们不能丢。加上redis这套架构,并存两套架构,就在无形中增加了我们的维护成本。Redis缓存在带来高性能的同时,也带来了一个副产品,就是在接入层必须硬编码redis的逻辑。 经历两版的系统改造,我们有了一点心得:一是利用日志干更多的事情,二是日志统计不要侵入原系统。 经过前两版的积累,在v0.3我们决定引入flume服务,来做日志收集。主要体现在两点:一是在日志源服务器部署flume agent,第二个就是增加了中心化的收集服务flume collector。 现在的日志系统分为3层,一是部署在接入层机器上的flume agent,二是中心化收集服务器flume Collector,三是日志处理服务。
接入层生成的日志,由flume agent进程,通过avro rpc发送到flume Collector。日志在collector汇总之后,主动发给第三层日志处理服务。主要包括统计服务cloud-stat、 分析服务analyze和日志归档服务log archive。 前面我们讲了v0.3的架构图,现在详细介绍一下flume。Flume的全名是Apache Flume,最初由 cloudera公司开发。捐给Apache基金会后,命名从flume-og改名为flume-ng,又称为Apache Flume。
下面介绍一下flume相关的几个术语:
Event:一个数据单元,带有一个可选的消息头。
Flow:Event从源点到达目的点的迁移的抽象。
Client:操作位于源点处的Event,将其发送到Flume Agent。
Agent:一个独立的Flume进程,包含组件Source、Channel、Sink。
Source:用来消费传递到该组件的Event。
Channel:中转Event的一个临时存储队列,保存有Source组件传递过来的Event。
Sink:从Channel中消费Event,将Event传递到Flow Pipeline中的下一个Agent。
它的设计目标主要是可靠、可伸缩、易扩展和易管理。下面我们介绍几个常用的解决方案:
一是Flume串联,它适用收集日志的规模较小的场景;
二是Flume多路Agent,能实现更复杂的业务逻辑;
三是Flume 并联,它提高了日志汇聚效率,但是存在单点问题;
四是复杂均衡,主要用于解决单点问题。
介绍完Flume的架构和使用场景。下面讲一讲,Flume在野狗云中的使用以及遇到的问题。野狗云使用Agent的主要收集接入层产生的本地日志文件。前面讲到Source是Agent用于接收数据的一个组件。Flume提供了多种基于日志文件的Source,包括两种:ExecSource和SpoolingDirSource。 ExecSource 的主要作用是,在Agent中执行shell脚本或shell命令,然后通过管道接收前者的数据。我们主要创建了两种接入层的日志。
一是Rest API 接入层流量日志:日志格式是 restapi.access.log, 每天零点把原来的日志改名为 restapi.access.log.date -d “a days ago” “+%Y-%m-%d”
, 然后再创建文件 restapi.access.log。这种机制叫做日志轮转。
二是NodeJs 接入层流量日志:日志格式是 longconnect.log.date “+%Y-%m-%d”
, 每天零点创建按天的日志文件。
在这个过程我们遇见了几个问题。一是当发生日志轮转的时候,因为tail -f命令打开的还是原来的文件描述符,所以就无法获取到当天新日志文件的内容。不过tail命令的–retry选项会定期检查文件名对应的文件描述符的变化。这就解决了我们这个问题。另外一个就是没有办法做到生产速率控制。这个我们在下面的PPT会讲到。 SpoolingDirSource用于监控文件目录变化的,但是实际的使用中会有以下两个问题:一是文件不能写,只能读。二是延迟比较高,需要等待日志定期归档。 为了解决速率控制的问题, 我们用Java程序实现了“模拟tail -F”的功能,主要是使用ExceSource,定时修改Flume的配置文件。另外就是根据我们的需求自定义Source。 Channel的功能是做缓存队列,Flume提供两种Channel。 MemoryChannel使用内存做缓冲队列,所有数据都保存在内存。但是这样做有两个问题:一是可用性差,另一个就是当队列数量小于阈值时,会一直等待被消费。 FileChannel使用磁盘做缓冲队列,所有数据都保存在磁盘。它的一个问题就是吞吐量比较低。 因为我们野狗自身的需求,在要求实时的同时,还要保证一定的可用性。美团的解决方案是提供一种基于内存Channel和FileChannel的Channel。
而我们野狗自己开发了一种基于MemoryChannel的增强Channel,主要增加了队列序列化功能,并且在重启的时候能够做到持久化。另外因为Flume低于阈值时才会触发Channel被消费事件,而我们在流量较低时也有收集日志的需求,所以在原生系统上又增加了空闲超时检测。 谢谢大家。