TKE 用户故事 - 做业帮 PB 级低成本日志检索服务

2022年01月14日 阅读数:3
这篇文章主要向大家介绍TKE 用户故事 - 做业帮 PB 级低成本日志检索服务,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

做者

吕亚霖,2019年加入做业帮,做业帮架构研发负责人,在做业帮期间主导了云原生架构演进、推进实施容器化改造、服务治理、GO微服务框架、DevOps的落地实践。前端

莫仁鹏,2020年加入做业帮,做业帮高级架构师,在做业帮期间,推进了做业帮云原生架构演进,负责做业帮服务治理体系的设计和落地、服务感知体系建设以及自研mesh、MQproxy研发工做。mysql

摘要

日志是服务观察的主要方式,咱们依赖日志去感知服务的运行状态、历史情况;当发生错误时,咱们又依赖日志去了解现场,定位问题。日志对研发工程师来讲异常关键,同时随着微服务的流行,服务部署愈来愈分散化,因此咱们须要一套日志服务来采集、传输、检索日志redis

基于这个状况,诞生了以 ELK 为表明的开源的日志服务。算法

需求场景

在咱们的场景下,高峰日志写入压力大(每秒千万级日志条数);实时要求高:日志处理从采集到能够被检索的时间正常 1s 之内(高峰时期 3s);成本压力巨大,要求保存半年的日志且能够回溯查询(百 PB 规模)。sql

ElasticSearch 的不足

ELK 方案里最为核心的就是 ElasticSearch, 它负责存储和索引日志, 对外提供查询能力。Elasticsearch 是一个搜索引擎, 底层依赖了 Lucene 的倒排索引技术来实现检索, 而且经过 **shard **的设计拆分数据分片, 从而突破单机在存储空间和处理性能上的限制缓存

写入性能

​ ElasticSearch 写入数据须要对日志索引字段的倒排索引作更新,从而可以检索到最新的日志。为了提高写入性能,能够作聚合提交、延迟索引、减小 refersh 等等,可是始终要创建索引, 在日志流量巨大的状况下(每秒 20GB 数据、千万级日志条数), 瓶颈明显。离理想差距过大,咱们指望写入近乎准实时。服务器

运行成本

​ ElasticSearch 须要按期维护索引、数据分片以及检索缓存, 这会占用大量的 CPU 和内存,日志数据是存储在机器磁盘上,在须要存储大量日志且保存很长时间时, 机器磁盘使用量巨大,同时索引后会带来数据膨胀,进一步带来成本提高。架构

对非格式化的日志支持很差

​ ELK须要解析日志以便为日志项创建索引, 非格式化的日志须要增长额外的处理逻辑来适配。存在不少业务日志并不规范,且有收敛难度。并发

总结:日志检索场景是一个写多读少的场景, 在这样的场景下去维护一个庞大且复杂的索引, 在咱们看来实际上是一个性价比很低的事情。若是采用 ElasticSearch 方案,经测算咱们须要几万核规模集群,仍然保证不了写入数据和检索效率,且资源浪费严重。负载均衡

日志检索设计

面对这种状况, 咱们不妨从一个不一样的角度去看待日志检索的场景, 用一个更适合的设计来解决日志检索的需求, 新的设计具体有如下三个点:

日志分块

一样的咱们须要对日志进行采集,但在处理日志时咱们不对日志原文进行解析和索引,而是经过日志时间、日志所属实例、日志类型、日志级别等日志元数据对日志进行分块。这样检索系统能够不对日志格式作任何要求,而且由于没有解析和创建索引(这块开销很大)的步骤, 写入速度也可以达到极致(只取决于磁盘的 IO 速度)。

简单来讲, 咱们能够将一个实例产生的同一类日志按时间顺序写入到一个文件中, 并按时间维度对文件拆分. 不一样的日志块会分散在多台机器上(咱们通常会按照实例和类型等维度对日志块的存储机器进行分片), 这样咱们就能够在多台机器上对这些日志块并发地进行处理, 这种方式是支持横向扩展的. 若是一台机器的处理性能不够, 横向再扩展就行。

那如何对入日志块内的数据进行检索呢?这个很简单, 由于保存的是日志原文,能够直接使用 grep 相关的命令直接对日志块进行检索处理。对开发人员来讲, grep 是最为熟悉的命令, 而且使用上也很灵活, 能够知足开发对日志检索的各类需求。由于咱们是直接对日志块作追加写入,不须要等待索引创建生效,在日志刷入到日志块上时就能够被马上检索到, 保证了检索结果的实时性

元数据索引

接下来咱们看看要如何对这么一大批的日志块进行检索。

首先咱们当日志块创建时, 咱们会基于日志块的元数据信息搭建索引, 像服务名称、日志时间, 日志所属实例, 日志类型等信息, 并将日志块的存储位置作为 value 一块儿存储。经过索引日志块的元数据,当咱们须要对某个服务在某段时间内的某类日志发起检索时,就能够快速地找到须要检索的日志块位置,并发处理。

索引的结构能够按需构建, 你能够将你关心的元数据信息放入到索引中, 从而方便快速圈定须要的日志块。由于咱们只对日志块的元数据作了索引, 相比于对所有日志创建索引, 这个成本能够说降到了极低, 锁定日志块的速度也足够理想。

日志生命周期与数据沉降

日志数据以时间维度的方向能够理解为一种时序数据, 离当前时间越近的日志会越有价值, 被查询的可能性也会越高, 呈现一种冷热分离的状况。并且冷数据也并不是是毫无价值,开发人员要求回溯几个月前的日志数据也是存在的场景, 即咱们的日志须要在其生命周期里都可以对外提供查询能力。

对于这种状况,若是将生命周期内的全部日志块都保存在本地磁盘上, 无疑是对咱们的机器容量提了很大的需求。对于这种日志存储上的需求,咱们能够采用压缩和沉降的手段来解决。

简单来讲,咱们将日志块存储分为本地存储(磁盘)、远程存储(对象存储)、归档存储三个级别; 本地存储负责提供实时和短时间的日志查询(一天或几个小时), 远程存储负责必定时期内的日志查询需求(一周或者几周), 归档存储负责日志整个生命周期里的查询需求。

如今咱们看看日志块在其生命周期里是如何在多级存储间流转的, 首先日志块会在本地磁盘建立并写入对应的日志数据, 完成后会在本地磁盘保留必定时间(保留的时间取决于磁盘存储压力), 在保存必定时间后, 它首先会被压缩而后被上传至远程存储(通常是对象存储中的标准存储类型), 再通过一段时间后日志块会被迁移到归档存储中保存(通常是对象存储中的归档存储类型).

这样的存储设计有什么好处呢? 以下面的多级存储示意图所示, 越往下存储的数据量越大, 存储介质的成本也越低, 每层大概为上一层的 1/3 左右, 而且数据是在压缩后存储的, 日志的数据压缩率通常能够达到10:1, 由此看归档存储日志的成本能在本地存储的1%的左右, 若是使用了 SSD 硬盘做为本地存储, 这个差距还会更大。

价格参考:

存储介质 参考连接
本地盘 https://buy.cloud.tencent.com/price/cvm?regionId=8&zoneId=800002
对象存储 https://buy.cloud.tencent.com/price/cos
归档存储 https://buy.cloud.tencent.com/price/cos

那在多级存储间又是如何检索的呢? 这个很简单, 对于本地存储上的检索, 直接在本地磁盘上进行便可。

若是检索涉及到远程存储上的日志块, 检索服务会将涉及到的日志块下载到本地存储, 而后在本地完成解压和检索。由于日志分块的设计,日志块的下载同检索同样,咱们能够在多台机器上并行操做; 下载回本地的数据复制支持在本地缓存后必定的时间后再删除, 这样有效期内对同一日志块的检索需求就能够在本地完成而不须要再重复拉取一遍(日志检索场景里屡次检索一样的日志数据仍是很常见).

对于归档存储, 在发起检索请求前, 须要对归档存储中的日志块发起取回操做, 取回操做通常耗时在几分钟左右, 完成取回操做后日志块被取回到远程存储上,再以后的数据流转就跟以前一致了。即开发人员若是想要检索冷数据, 须要提早对日志块作归档取回的申请,等待取回完成后就能够按照热数据速度来进行日志检索了。

检索服务架构

在了解上面的设计思路后, 咱们看看基于这套设计的日志检索服务是怎么落地的.

日志检索服务分为如下几个模块:

  • GD-Search

​ 查询调度器, 负责接受查询请求, 对查询命令作解析和优化, 并从 Chunk Index 中获取查询范围内日志块的地址, 最终生成分布式的查询计划

GD-Search 自己是无状态的, 能够部署多个实例,经过负载均衡对外提供统一的接入地址。

  • Local-Search

​ 本地存储查询器, 负责处理 GD-Search 分配过来的本地日志块的查询请求。

  • Remote-Search

​ 远程存储查询器, 负责处理 GD-Search 分配过来的远程日志块的查询请求。

Remote-Search 会将须要的日志块从远程存储拉取到本地并解压, 以后同 Local-Search 同样在本地存储上进行查询。同时 Remote-Search 会将日志块的本地存储地址更新到 Chunk Index 中,以便将后续一样日志块的查询请求路由到本地存储上。

  • Log-Manager

​ 本地存储管理器,负责维护本地存储上日志块的生命周期。

Log-Manager 会按期扫描本地存储上的日志块, 若是日志块超过本地保存期限或者磁盘使用率到达瓶颈,则会按照策略将部分日志块淘汰(压缩后上传到远程存储, 压缩算法采用了 ZSTD), 并更新日志块在 Chunk Index 中的存储信息。

  • Log-Ingester

​ 日志摄取器模块, 负责从日志 kafka 订阅日志数据, 而后将日志数据按时间维度和元数据维度拆分, 写入到对应的日志块中。在生成新的日志块同时, Log-Ingester 会将日志块的元数据写入 Chunk Index 中, 从而保证最新的日志块可以被实时检索到。

  • Chunk Index

​ 日志块元数据存储, 负责保存日志块的元数据和存储信息。当前咱们选择了 Redis 做为存储介质, 在元数据索引并不复杂的状况下, redis 已经可以知足咱们索引日志块的需求, 而且基于内存的查询速度也可以知足咱们快速锁定日志块的需求。

检索策略

在检索策略设计上, 咱们认为检索的返回速度是追求更快, 同时避免巨大的查询请求进入系统。

咱们认为日志检索通常有如下三种场景:

  1. 查看最新的服务日志。

  2. 查看某个请求的日志, 依据 logid 来查询。

  3. 查看某类日志, 像访问 mysql 的错误日志, 请求下游服务的日志等等。

在大部分场景下, 用户是不须要全部匹配到的日志, 拿一部分日志足以处理问题。因此在查询时使用者能够设置 limit 数量, 整个检索服务在查询结果知足 limit设置的日志数量时, 终止当前的查询请求并将结果返回给前端。

另外 GD-Search 组件在发起日志块检索时, 也会提早判断检索的日志块大小总和, 对于超限的大范围检索请求会作拒绝。(用户能够调整检索的时间范围多试几回或者调整检索语句使其更有选择性)

性能一览

使用 1KB 每条的日志进行测试, 总的日志块数量在10000左右, 本地存储使用 NVME SSD 硬盘, 远程存储使用 S3 协议标准存储.

• 写入

​ 单核可支持 2W条/S的写入速度, 1W 条/S的写入速度约占用 1~2G 左右的内存,可分布式扩展,无上限

• 查询(全文检索)

​ 基于本地存储的 1TB 日志数据查询速度可在 3S 之内完成

​ 基于远程存储的 1TB 日志数据查询耗时在 10S 间。

成本优点

在每秒千万级写入,百 PB 存储上,咱们使用十几台物理服务器就能够保证日志写入和查询。热点数据在本地 nvme 磁盘上,次热数据在对象存里,大量日志数据存储在归档存储服务上。

计算对比

​ 由于不须要创建索引,咱们只须要千核级别就能够保证写入,同时日志索引是个写多读少的服务,千核能够保证百级别 QPS 查询。

​ ES 在这个量级上须要投入几万核规模。来应对写入性能和查询瓶颈,可是仍不能保证写入和查询效率。

存储对比

​ 核心是在保证业务需求下,使用更便宜的存储介质(归档存储 VS 本地磁盘)和更少的存储数据(压缩率 1/10vs 日志数据索引膨胀)。能有两个量级的差距。

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!