内容简介:“唯技术”一档专为唯品技术人发声的公众号欢迎投稿!!只要是
摘要:AccessLogWriter是唯品会Dragonfly日志系统众多组件中的一个新成员,用于将Access Log写入HDFS以提供准实时的文件下载和快速查询服务。AccessLogWriter能达到单机150万QPS的处理效率,仅使用6台服务器就支撑了今年616大促时全网包括Nginx, Janus, Osp, Osp-proxy的Access Log流量。
使用Golang实现高吞吐量的Hadoop应用 ,以及 在HDFS上并发写入数万个文件 ,在业界并没有很好的参考案例。本文介绍了唯品会Access Log存储方案,以及AccessLogWriter的实现思路,或许能给你带来一些新的想法。
唯品会技术平台有多种组件会生成访问日志,包括:
-
Nginx
-
Janus网关
-
Osp服务组件及其proxy agent
这些访问日志数量巨大,使用频率又相对较低(通常是出现问题才需要使用),由于公司Dragonfly日志系统的资源限制,一直没有允许访问日志接入。以往开发人员在排查问题要用到访问日志时,只能通过运维同事到服务器拉取,效率很低,特别是不确定需要的log在哪一台服务器的情况下。因此,我们需要一个可以对Access Log提供集中存储和方便访问的低成本方案。
方案设计
总体方案
Access Log在服务器上通过采集客户端上传到Kafka。为最大程度保留用户对Access Log的使用习惯,我们将上传上来的Access Log按一定方式切分成文件,压缩后存储在HDFS,并提供下载和查询界面。
压缩方式选择的是gzip,它相比其它压缩方式有两大优点:
-
可以不断拼接新的数据块,特别适合流式写入;
-
用户操作系统普遍支持解压(唯品会的Windows系统安装了7zip,Linux/Mac则更加方便,不但自带gzip支持,还有zcat/zless等好用的命令)。
缺点则是性能比不上新一代的压缩算法如snappy, lz4等。
文件按照以下规则进行切分并保存:
-
类型
-
应用名
-
主机名
-
日期
-
小时
-
大小(目前按压缩后每100MB滚动)
这里会出现一个问题,以唯品会目前的服务器规模,按照上述规则每小时会生成20000个以上的Access Log文件。众所周知,HDFS擅长处理大文件读写。但是如果缓存一小时的数据再一次写入,需要使用非常大量的内存,而且会带来至少一小时的延迟,这是不可接受的。而要实时写入上万个文件,性能又不能满足。所以我们的方案就是适当延迟的批量写入。似乎简单明了,但为了达到很高的性能,还是需要良好的设计。
语言选型
Java系语言向来是开发Hadoop应用的绝对主流。但对于处理Access Log写入这类高吞吐量场景,从以往项目经验看来,Java的GC问题会是很大的制约。
于是,我们尝试了使用相对偏门的Golang来实现。在本项目实践中, Golang本身语言的简洁、实现并发的优雅、对内存使用的高效、以及编译和调试的便利,这些优势都发挥地淋漓尽致。
最终的实现,大量使用了goroutine和channel,通过有效的设计,总体代码量并不大,1千行左右。
值得一提的是,Dragonfly上提供对全应用access log文件的在线查询及高级统计功能(支持以管道符拼接的grep, awk, sed, wc等操作),其调用API也是通过Golang实现的。
AccessLogWriter设计
AccessLogWriter就是用来处理写入HDFS的组件。下面是它的设计和介绍:
Consumer
负责消费Kafka数据,使用github.com/Shopify/sarama实现。经过测试,单个实例处理能力大约为50万QPS。如果要达到更高性能,则在一个AccessLogWriter上需要启动多个Consumer实例。
我们在日志上传到Kafka时已经设置了以主机名作为partitioning hash key,也就是同一台主机的日志会顺序写入同一个kafka partition,所以我们将收到日志也按照partition id取模输出到对应编号的Parser,就可以保证最终每个主机对应的文件中日志的顺序。
Parser
负责解析日志内容,提取出应用名(在Nginx Access Log中则是host字段,即要访问的域名),主机名,和时间戳。这些信息用于组合生成写入HDFS的文件路径。
我们维护一个全局的map,以写入HDFS的文件路径为key,它的值为一个HdfsTarget对象:
Parser从map中找到或创建HdfsTarget对象,将日志发送到它的Compressor channel中。
在工作模式上,Parser相当于Nginx中的worker process,无状态的处理,输出到下游不做等待立即返回。
Compressor
负责日志压缩,每个文件对应一个Compressor。Compressor中包含了第一级的缓存,默认每1000条日志或者每2分钟,执行批量压缩后输出到Writer。
批量压缩可以提高数据压缩率。一次压缩的日志数越多,压缩率越高,但两者不是线性关系,而且会增加内存使用以及增加延迟,所以需要有一个权衡。
Writer
负责缓存压缩数据并写入HDFS,每个文件对应一个Writer。Writer中包含了第二级的缓存,默认每5MB或每10分钟,将缓存数据flush到HDFS中,并判断是否需要滚动成新的文件。对HDFS写失败会进行重试。
为了避免每隔10分钟出现大量文件同时写HDFS,每个Writer第一次写有一个随机的时间。
小结
AccessLogWriter设计的关键之处,在于 二级缓存 以及 gzip压缩格式 的使用。
二级缓存的设计,使数据在等待写入HDFS之前是以压缩格式缓存的,大大减少了内存的占用,而压缩率又比逐条压缩高的多。
而gzip压缩格式的使用,是压缩数据块可以流式写入的前提。并且压缩数据分块写入,可以在由于异常原因导致个别数据块损坏的情况下,仍能通过头部检索恢复绝大部份的数据。
一些优化
-
日志是以snappy格式压缩上传到Kafka,Consumer需要执行相应的解压。在这里我们定位到了Sarama的snappy解压模块中一个由于slice grow up导致不断进行内存分配的性能问题。由于access log的长度比较固定,我们通过增加一个滑动平均值变量估算解压后的大小,预先指定slice的长度,Consumer的性能有了很大提高。
-
gzip压缩我们使用了第三方的github.com/klauspost/compress库,比自带的compress库性能有20%以上的提升。由于整个程序中压缩处理占了大部分的cpu时间,这个优化对整体性能的提升作用相当明显。
-
Compressor和Writer中使用了大量的buffer,我们使用了free list进行内存管理,使内存块可以一直重复利用,避免GC。具体实现可以参考官方文档effective go一章中的介绍。(点击 阅读原文 可查看该文/链接:http://docs.studygolang.com/doc/effective_go.html#leaky_buffer)
最终效果
经过一些参数调整,最终对HDFS的写入频率平日可以控制在50QPS左右,HDFS集群的性能完全可以满足。而文件更新延迟可以控制在15分钟以下(只对日志量少的应用,日志量大的当buffer满了就会flush,延迟在分钟级。最大延迟可以配置得更低,但似乎没有需要)。
在单台24核CPU,128GB内存的服务器上,AccessLogWriter最高可以达到每秒处理150万条日志的速率。这时CPU接近跑满,主要被压缩计算占用,是比较理想的结果。内存占用通常在64GB以下,预留了空间给万一出现处理延迟需要追赶的情况下需要使用更多的内存。
在今年616大促时,仅使用6台AccessLogWriter服务器和10台HDFS服务器,就支撑了全网各种Access Log的写入,最大延迟不超过半小时。
篇外话
Access Log下载和查询是Dragonfly日志系统的一个新功能。唯品会内部数坊平台提供了全网的Nginx Access Log数据,但数据延迟时间比较长、查询速度稍慢。Dragonfly的Access Log服务可以提供准实时数据和快速查询,但目前还没有离线处理和报表生成能力。所以对于Nginx Access Log数据,数坊和Dragonfly可以很好地互补。
除此之外,内部Osp Access Log,Osp-Proxy Access Log和Janus Access Log也可以在Dragonfly上查到。
推荐阅读
Flink在唯品会的实践
基于Kafka1.0消息可靠性设计之消费重试策略
灾难连锁之RedisCluster下线节点导致集群崩溃事件整理反思
“唯技术”一档专为唯品技术人发声的公众号
欢迎投稿!!
只要是 技术相关的文章 尽管砸过来!
以上所述就是小编给大家介绍的《唯品会高吞吐量 Access Log 存储的实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- kafka高吞吐量之消息压缩
- 如何找到 Kafka 集群的吞吐量极限?
- 高吞吐量的Java事件总线:MBassador
- 再次提高 Kafka 吞吐量,原来还有这么多细节?
- 软件交付效能度量:从吞吐量和稳定性开始
- 惊:FastThreadLocal吞吐量居然是ThreadLocal的3倍!!!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java遗传算法编程
Lee Jacobson、Burak Kanber / 王海鹏 / 人民邮电出版社 / 2016-12-6 / 49元
本书简单、直接地介绍了遗传算法,并且针对所讨论的示例问题,给出了Java代码的算法实现。全书共分灾6章。第1章简单介绍了人工智能和生物进化的知识背景,这也是遗传算法的历史知识背景。第2章给出了一个基本遗传算法的实现;第4章和第5章,分别针对机器人控制器、旅行商问题、排课问题展开分析和讨论,并给出了算法实现。在这些章的末尾,还给出了一些练习供读者深入学习和实践。第6章专门讨论了各种算法的优化问题。 ......一起来看看 《Java遗传算法编程》 这本书的介绍吧!