热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

京东App秒级百G日志传输存储架构设计与实战

本文作者:平台业务研发部-武伟峰,数据与智能部-李阳背景在日常工作中,我们通常需要存储一些日志,譬如用户请求的出入参、系

本文作者:平台业务研发部-武伟峰,数据与智能部-李阳


背景

在日常工作中,我们通常需要存储一些日志,譬如用户请求的出入参、系统运行时打印的一些info、error之类的日志,从而对系统在运行时出现的问题有排查的依据。

日志存储和检索是个很常见且简单的工作,市面也有很多关于日志搜集、存储、检索的框架可供使用。

譬如我们只有个位数机器时,可以通过登录服务器,查看log4j之类的框架打印到本地文件的日志。当日志多起来后,可以用elk三剑客处理日志。

当日志量进一步增多,我们可以上消息队列,譬如kafka之类来承接,然后消费入库。或者写本地文件,再采用filebeat之类上报再入库。

以上都是较为常见的日志传输和存储的方案,成本可控的情况下,可适用于绝大多数场景。

我们可以简单总结一下日志框架的功能,大概是暂存、传输、入库保存、快速检索。


量级上升,成本高昂

技术方案的设计和取舍,往往强受限于成本。当成本高企到难以承受时,将必须导致技术方案的升级换代。那么问题来了,我就是存个日志而已,怎么就成本难以承受了呢?

我们以一个常见的日志传输及存储方案来举例,入下图,暂存就是采用客户端写本地文件存日志,传输即是采用MQ,消费入库常见的如ES。下图方案,为了减少部分存储成本,将日志详情存储于压缩更好的Hbase,仅将查询时需要的一些索引字段放在了ES。

以上作为一个常用的方案,为什么会成本高昂呢。

我们来简单计算一下,京东App某个模块(是一个模块,非整个App累计),单次用户请求,用户的入参+返回值+流程中打印的日志占用的大小在40k-2M之间,中位数在60k左右。该模块日常每秒约2-5万次访问,高峰时会翻10倍,极高峰可达百万。

以3万每秒来算,产生的日志大小为1.8G,也就是说即便是低负载时,这个日志框架要吞下1.8G的传输与存储。但这是远远不够的,因为我们即便放弃极高峰,仅仅支撑偶现的高峰,也需要该系统能支撑秒级15G以上的吞吐。但是这仅仅才是一个模块而已,算上前中台这样的模块还有很多。

那么我们就可以来算一算了,一秒1.5G,一个小时就是5.4TB。小高峰是肯定要支撑的,也就是秒级30万是要保的,那么我们的系统就要能支撑秒级15G单模块,算上各模块,200G秒级是跑不了了。

这只是各个机器所打印在各自本地的原始日志文件占用的大小,然后要发到MQ集群,大家都知道,MQ也是写磁盘的,这200G一点不少的在MQ机器上做了保存,并且MQ还有备份机制,就以最简陋的单备份来说,MQ每秒要承接400G的磁盘,并且离删除后释放磁盘还有挺长一段时间,哪怕只存1个小时,也是一个巨大的数字。

我们知道,一般服务器在磁盘还不错的情况下,单机秒级写入量200多M算比较不错的情况,通过上面的了解,我们仅仅做到日志的暂存和传输就需要2千台以上的服务器资源。

然后就到了worker消费集群,该集群只是纯粹的内存数据交换,不占磁盘,worker消费后写入数据库。大家基本可以想象到,数据库的占用是如何。OK,我们终于把数据存了进去,查询问题就成了另外一个必须面对的事情,如何快速从无数亿中找到你要查询的那个用户的链路日志。

到了此时,成本就成了非常要命的事情,尤其方案的设计,会导致原本就很庞大的数据,在链路上再次放大多倍,那么巨大的硬件成本如何解决。


缩短流程,缩减流量

通过上面的分析,我们已经发现,即便是市面上最通用的日志方案,在如此巨大的流量面前,也难以持续下去,高昂的硬件成本,将迫使我们去寻找更合适的技术方案。

世界上有一个著名的法则叫"奥卡姆剃刀定律",讲的是程序员该如何选择合适的剃刀,来让自己的秀发光滑柔顺有光泽。

其实不是的,该定律主要就是八个字"如无必要,勿增实体"。当一个流程难以支撑当前的业务时,我们就该审视一下,哪些步骤是不必要的。

从这个通用流程中,其实我们很容易就能发现,我们经历了很多读写,每次读写都伴随着磁盘的读写(包括MQ也是写磁盘的),和频繁的序列化反序列化,以及翻倍的网络IO。

那么让我们挥舞起奥卡姆的剃刀,做一些删减,把非必要的部分给删掉,就变成了下图的流程:

我们发现,其实写本地磁盘、和MQ都是没有必要的,我们完全可以将日志数据写到本地内存,然后搞个线程,定时通过UDP将日志直接发送到worker端即可。

worker接收到之后,解析一下,写入自己的内存队列,再起数个异步线程,批量将队列的数据写入ClickHouse数据库即可。

大家可能看到了,下图的流程中,那个圆圈明显比上图的圆圈要小,这是为什么呢?因为我做了压缩。

前文讲过,我们单条报文40k-2M,这是一个非常大的报文,这里面都是一些用户请求的入参Json和出参Json以及一些中途日志,我们完全没有必要将原文原封不动往外传输。

通过采用主流的snappy、zstd等压缩工具类,可以直接将字符串压缩成byte[]再往外传输,这个被压缩后的字符串,直至入库都是byte[],全程不对大报文解压。

那么这个压缩能压多少呢,80%-90%,一个60k的报文,往外送时就剩6-8k了,可想而知,仅仅压缩一下原始数据,就在整个流程中,节省了巨大的带宽,同时也大幅提升了worker的吞吐量。

这里有个小细节,udp单个最大报文是64kb,如果我们压缩后,还是超过了64kb的话,udp是送不出去的,这里可以选择发个http请求送到worker即可。

通过上图,我们可以看到,当流程中的某些环节并不是必需的时,我们应该果断砍掉,不要轻易照搬网上的方案,而应该选择更适合自己的方案。下面我们详细讲一下系统是如何设计、运转的。


更强悍的日志搜集系统

我们来审视一下这个链路极短的日志搜集系统。

配置中心:用来存储worker的IP地址,供客户端获取自己模块所分配的worker集群的ip。

client:客户端启动后,从配置中心拉取分配给自己这个模块的worker集群的IP,并轮询将搜集的日志压缩后发送过去,通过UDP的方式。

worker:每个模块会分配数量不等的worker机器,启动后上报自己的IP地址到配置中心。接受到客户端发来的日志后,解析相应的字段,批量写入clickhouse数据库。

clickhouse:一个强大的数据库,压缩比很高,写入性能极强,按天分片,查询速度佳。非常适合应用于日志系统这种写入极大,查询较少的系统。

dashboard:可视化界面,从clickhouse查询数据展示给用户,具有多条件多维度查询功能。

大家都能看出来,这其中最关键的地方是worker端,它的承接流量、消费性能、入库性能将决定着整个链路能否良好地运转。

我们主要分别讲解一下client端和worker端的实现。


Client端聚合日志

一次请求中,我们通常要保留的日志信息主要有:

(1)请求的出入参>

  如果是http web应用,要获取出入参比较简单的方式就是通过自定义filter即可。client的sdk里定义了一个filter,接入方通过配置该filter生效即可搜集到出入参。

  如果是其他rpc应用非http请求的,也提供了对应的filter拦截器来获取出入参。

  在获取到出入参后,sdk对其中大报文,主要是出参进行了压缩,并将整个数据定义为一个JAVA对象,做protobuf序列化,通过UDP方式往自己对应的worker集群轮询传输。

(2)链路上自己打印的一些关键信息,如调用其他系统的的出入参,自己打印的一些info、error信息>

  sdk分别提供了log4j、logback、log4j2三个常用日志框架的自定义appender,用户可以通过在自己的日志配置文件(如logback.xml)中,将我自定义的appender定义出来即可,那么后续用户在代码里所有打印的info、error等日志都会执行这个自定义appender。

  同样,这个自定义appender也是将日志暂存内存,然后异步UDP外送到worker。

这里主要有两个地方需要注意,一是当压缩后的报文依旧超出udp最大报文值时,即通过http送出。二是这一次请求,链路中可能会使用多线程、线程池技术,为避免链路tracer的唯一id在线程池丢失,sdk采用了TransmittableThreadLocal来保持链路的ID,这个查一下就懂。

总体来说,client端实现较为简单,省略了写本地磁盘、消费文件发MQ等等步骤,整体只有一次Protobuf序列化操作,对CPU、接入方性能影响极小,采用UDP外送,不需要worker的任何回复,也不用考虑tcp模式下worker消费慢导致自己阻塞的问题。整体非常简洁高效。


Worker端消费日志并入库

worker端是调优的重点,由于要接收海量客户端发来的日志,解析后入库,所以worker需要具备很强的缓冲能力。

我们都能看出来,系统的瓶颈点肯定在入库这个阶段,解析日志,抽取字段都是效率很高的,而且完全可以通过控制线程的数量来控制住,而入库将强受限于clickhouse的写入性能。至于clickhouse是如何做的优化,后面会有clickhouse集群负责人来讲一下做了哪些优化。

为了做好这个缓冲,即便日志接收量大于入库量,我们也要能接下来这些数据,尽量不丢失。首先硬件上,采用大内存机器,8核32G的容器,来尽量多屯一些数据。其次,采用了双缓冲队列,先将所有接收的数据放一个队列,然后多线程消费、解析成可供入库的行数据,再放入一个待入库队列,然后批量入库。

那么我们做的这些操作,能支撑什么样的数据量呢?

通过线上的应用和严苛的压测,这样一台单机docker容器,每秒可以处理原始日志1-5千万行,譬如一条用户请求,中途产生了共计1千多行日志,那么这样的一台worker单机,每秒可以处理2万客户端QPS。对外写clickhouse数据库,每秒可以写200多M比较稳定。

通过对上文的了解,我们知道,这些数据都是被压缩过的,直至库里面的都是压缩过的,只有当最终用户查询时,才会进行解压。所以,这200M,基本相当于原始数据1G多的大小。

也就是说,只要clickhouse写入速度跟的上,这个系统仅需100台就可以极其高效地处理原始秒级百G的日志。对比写MQ的方案,中途所有会出现瓶颈的点如MQ写磁盘速度、消费拉取速度等,都将不复存在。这是一个纯内存交换的链路系统。


强悍的Clickhouse

通过以上的了解,我们可以清楚的看到,worker作为一个纯内存计算的组件,client端通过worker的数量进行hash均匀分发到各个worker,所以worker可以动态扩容而且不存在性能瓶颈,其唯一受限制的就是写入库的速度。

倘若写库速度跟不上,则worker必须要拿有限的内存去屯下发来的大量数据,一旦写满则就会开始丢弃接收到的数据。所以整个系统的瓶颈点,就是写库的速度。

Clickhouse是面向海量数据实时、多维分析、高性能的新一代OLAP数据库管理系统,实现了向量化执行和SIMD指令,对内存中的列式数据,一个batch调用一次SIMD指令,大幅缩短了计算耗时,带来数倍的性能提升。目前已成为驱动京东集团业务增长、创新的“超级引擎”。那么在京东App秒级百G日志传输存储架构中,Clickhouse如何支撑大吞吐量数据的写入,主要在于两点

 1)集群高可用架构

         EasyOLAP部署CH集群是三层结构:域名 + CHProxy + CH节点,域名转发请求到CHProxy,再由CHProxy根据集群节点状态来转发。CHProxy的引入是为了让Query均匀分布在每个节点上,,并对CHProxy做了一定的改进,自动感知集群节点的状态变化。


多条件查询控制台

控制台比较简单,主要就是做一些sql语句查询,做好clickhouse的高效查询,这里简单提一些知识点。

做好数据的分片,如按天分片。用好prewhere查询功能,可以带来性能的提升。做好索引字段的设计,譬如检索常用的时间、pin。

细节难以尽述,要从百亿千亿数据中,做好极速的查询,还需要对clickhouse的一些查询特性有所了解。

下图界面展示的即为一些索引项,点击查看详情,则从数据库捞出压缩过的数据,此时才解压并展示给前端。查看链路,则是该次请求中,整个链路用户打印的log(包括线程池内的)。


总结与对比

我们可以简单的做一些对比,主要在于硬件成本和软件性能的对比。

从上文可知,磁盘的占用原始方案占用了磁盘(1份),MQ(2份),数据库(1份)。而在新的方案中,磁盘的占用仅剩下clickhouse的(0.8份),clickhouse自身又对数据做了压缩,实际占用空间不到入库容量的80%。

那么仅磁盘即可节省75%以上的存储成本。

大家都知道,秒级的吞吐量,是伴随着服务器Cpu的耗费的,并不是说只给个大硬盘,即可一台服务器每秒吞吐1个G的。每台服务器秒级的吞吐量是有上限的,秒级占用磁盘的上升,即对应Cpu数量的上升,要支撑一秒1G的磁盘写入,需要5台或以上的服务器。

那么在磁盘的大幅节约下,线性地节省了大量的中间过程Cpu服务器。实际粗略统计效果,流程中服务器可节约70%以上。

在软件性能上,过程很好理解。对Client端的消耗主要就是序列化、写磁盘、读磁盘、反序列化这几步的消耗,Udp则仅有一步序列化。我们假设MQ集群是有无限的写入能力,可以吞下所有的发过去的日志,那么就是worker端的消费性能对比。

从MQ拉取并消费,这个过程如果MQ没有积压,则有零拷贝在支撑高速的拉取,如果积压了,则可能产生大量的MQ磁盘IO,拉取速度会大幅下降。这个过程效率会明显低于Udp发送到worker的处理效率,而且占用双份的网络带宽。实际表现上,worker表现出的强劲性能,较之前单条拉取MQ集群时,消费性能提升在10倍以上。

本文到此就结束了,主要简略介绍了一个新的日志搜集系统(用户跟踪框架)的设计方案,以及该方案能带来的巨大的成本节省。相关代码日后会开源于"gitee.com"的京东零售账号下,届时有相关需求的同学可以加以关注。

 

 


推荐阅读
  • C#中实现高效UDP数据传输技术
    C#中实现高效UDP数据传输技术 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • Spark与HBase结合处理大规模流量数据结构设计
    本文将详细介绍如何利用Spark和HBase进行大规模流量数据的分析与处理,包括数据结构的设计和优化方法。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 在深入探讨进程间通信技术时,本文重点解析了描述符传递的方法。通过详细分析发送和接收描述符的过程,文章首先介绍了发送描述符的具体步骤,并提供了相关函数原型。此外,还讨论了如何高效地在不同进程之间传输文件描述符,以实现资源的共享和同步。这一技术在多进程应用中具有重要意义,能够显著提升系统的性能和可靠性。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 在ElasticStack日志监控系统中,Logstash编码插件自5.0版本起进行了重大改进。插件被独立拆分为gem包,每个插件可以单独进行更新和维护,无需依赖Logstash的整体升级。这不仅提高了系统的灵活性和可维护性,还简化了插件的管理和部署过程。本文将详细介绍这些编码插件的功能、配置方法,并通过实际生产环境中的应用案例,展示其在日志处理和监控中的高效性和可靠性。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 基于Dubbo与Zipkin的微服务调用链路监控解决方案
    本文提出了一种基于Dubbo与Zipkin的微服务调用链路监控解决方案。通过抽象配置层,支持HTTP和Kafka两种数据上报方式,实现了灵活且高效的调用链路追踪。该方案不仅提升了系统的可维护性和扩展性,还为故障排查提供了强大的支持。 ... [详细]
  • TCP三次握手过程详解与图示解析
    本文详细解析了TCP三次握手的过程,并通过图示清晰展示了各个状态的变化。同时,文章还介绍了四次挥手的图解,解释了在TIME_WAIT状态中,客户端最后一次发送的ACK包的作用和重要性。 ... [详细]
  • HBase在金融大数据迁移中的应用与挑战
    随着最后一台设备的下线,标志着超过10PB的HBase数据迁移项目顺利完成。目前,新的集群已在新机房稳定运行超过两个月,监控数据显示,新集群的查询响应时间显著降低,系统稳定性大幅提升。此外,数据消费的波动也变得更加平滑,整体性能得到了显著优化。 ... [详细]
  • 在对TCP段的PDU进行重组的过程中,通过网络抓包技术发现了一个特定的TCP段重组PDU实例。经过查阅相关博客和技术文章,成功找到了有效的解决方案,为类似问题的处理提供了宝贵的参考。 ... [详细]
author-avatar
isonlyk
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有