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

news2024/11/24 5:18:08

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

 

背景

在日常工作中,我们通常需要存储一些日志,譬如用户请求的出入参、系统运行时打印的一些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"的京东零售账号下,届时有相关需求的同学可以加以关注。

 

 


---------------------
作者:天涯泪小武
来源:CSDN
原文:https://blog.csdn.net/tianyaleixiaowu/article/details/121401664
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2142933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

作为研发部门的负责人,如何助力产品在市场竞争中胜出?浅谈 CTQ

在激烈的市场竞争中,产品研发团队如何帮助企业的产品脱颖而出?成功的产品往往不仅依赖于强大的功能和技术创新,还需要通过高效的研发效能,包括效率、质量和创新,来提升产品的市场竞争力。在本文中,我们将探…

文档内容识别系统源码分享

文档内容识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

一款源码阅读的插件

文章目录 进度汇报功能预览添加高亮标记高亮风格设置笔记颜色设置数据概览高亮数据详情 结尾 进度汇报 之前提到最近有在开发一个源码阅读的IDEA插件,第一版已经开发完上传插件市场了,等官方审批通过就可以尝鲜了。插件名称:Mark source cod…

基于STM32F407ZGT6——看门狗

独立看门狗 独立看门狗的时钟由独立的RC 振荡器LSI 提供,即使主时钟发生故障它仍然有效,非常独立。 LSI 的频率一般在30~60KHZ 之间,根据温度和工作场合会有一定的漂移, 所以独立看门狗的定时时间并不一定非常精确,只适…

格式化u盘选择FAT还是NTFS U盘和硬盘格式化两者选谁

Mac用户在将U盘或硬盘进行格式化时,选择FAT还是NTFS往往是一个让人纠结的问题。很多用户不知道这两个格式之间有什么区别,更不知道在格式化时如何做出选择。本文将为大家介绍Mac选择FAT还是NTFS,并为大家推荐U盘和硬盘格式化两者选谁。 一、…

36.贪心算法3

1.坏了的计算器(medium) . - 力扣(LeetCode) 题目解析 算法原理 代码 class Solution {public int brokenCalc(int startValue, int target) {// 正难则反 贪⼼int ret 0;while (target > startValue) {if (target % 2 0…

第159天:安全开发-Python-协议库爆破FTPSSHRedisSMTPMYSQL等

案例一: Python-文件传输爆破-ftplib 库操作 ftp 协议 开一个ftp 利用ftp正确登录与失败登录都会有不同的回显 使用ftplib库进行测试 from ftplib import FTP # FTP服务器地址 ftp_server 192.168.172.132 # FTP服务器端口(默认为21) ftp_po…

Base 社区见面会 | 新加坡站

活动信息 备受期待的 Base 社区见面会将于 Token2049 期间在新加坡举行,为 Base 爱好者和生态系统建设者提供一个独特的交流机会。本次活动由 DAOBase 组织,Base 和 Coinbase 提供支持,并得到了以下合作伙伴的大力支持: The Sand…

Python 课程15-PyTorch

前言 PyTorch 是一个开源的深度学习框架,由 Facebook 开发,广泛应用于学术研究和工业领域。与 TensorFlow 类似,PyTorch 提供了强大的工具用于构建和训练深度学习模型。PyTorch 的动态计算图和灵活的 API 使得它特别适合研究和实验。它还支持…

GetMaterialApp组件的用法

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性 3. 示例代码4. 内容总结 我们在上一章回中介绍了"Get包简介"相关的内容,本章回中将介绍GetMaterialApp组件.闲话休提,让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经…

King3399 SDK编译简明教程

该文章仅供参考,编写人不对任何实验设备、人员及测量结果负责!!! 0 引言 文章主要介绍King3399(瑞芯微rk3399开发板,荣品)官方SDK编译过程,涉及环境配置、补丁以及编译过程中注意事…

Using OpenAI API from Firebase Cloud Functions in flutter app

题意:“在 Flutter 应用中通过 Firebase Cloud Functions 使用 OpenAI API。” 问题背景: I cant figure out how to make this work. “我不知道该如何让这正常运行。” This is my cloud function in Javascript. Im trying a simple code to see if…

鸿蒙媒体开发系列04——音频播放

如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。 1、如何选择音频播放开发方式 在HarmonyOS系统中,多种API都提供了音频播…

C++_map_set详解

关联容器的基本介绍 关联容器支持高效的关键字查找和访问。map和set是最主要关联容器。关联容器也是用来存储数据的&#xff0c;与序列式容器不同的是&#xff0c;其里面存储的是<key, value>结构的键值对&#xff0c;在数据检索时比序列式容器效率更高。C标准库中提供了…

如何关闭前端Chrome的debugger反调试

1、禁用浏览器断点 2. 把控制台独立一个窗口

如何优雅地处理返回值

我们已经知道了如何优雅的校验传入的参数了&#xff0c;那么后端服务器如何实现把数据返回给前端呢&#xff1f; 返回格式 后端返回给前端我们一般用 JSON 体方式&#xff0c;定义如下&#xff1a; {#返回状态码code:string, #返回信息描述message:string,#返回值data…

《Google软件测试之道》笔记

介绍 GTAC&#xff1a;Google Test Automation Conference&#xff0c;Google测试自动化大会。 本书出版之前还有一本《微软测试之道》&#xff0c;值得阅读。 质量不是被测试出来的&#xff0c;但未经测试也不可能开发出有质量的软件。质量是开发过程的问题&#xff0c;而不…

09年408考研真题解析-计算机网络

[题34]在无噪声情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术,则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A.12 kbps B.24 kbps C.48 kbps D.96 kbps 解析&#xff…

优惠充值话费api对接如何选择对接平台?

优惠充值话费接口通常由电信运营商、第三方支付平台或专业的充值服务提供商提供。这些平台通过API接口允许开发者将话费充值功能集成到应用程序或网站中。 选择哪个平台比较好&#xff0c;取决于以下几个因素&#xff1a; 覆盖范围&#xff1a;选择能够覆盖你需要服务的地区和…

49.面向对象综合训练-朋友

1.创建JavaBean类 public class Friend {//题目要求&#xff1a;定义数组存储4个朋友对象//属性&#xff1a;姓名&#xff0c;年龄&#xff0c;性别&#xff0c;爱好//计算出四位朋友的平均年龄//统计出比平均年龄低的朋友有几个&#xff0c;并把信息都打印出来private String…