作者

吕亚霖,2019年退出作业帮,作业帮架构研发负责人,在作业帮期间主导了云原生架构演进、推动施行容器化革新、服务治理、GO微服务框架、DevOps的落地实际。

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

摘要

日志是服务察看的次要形式,咱们依赖日志去感知服务的运行状态、历史情况;当产生谬误时,咱们又依赖日志去理解现场,定位问题。日志对研发工程师来说异样要害,同时随着微服务的风行,服务部署越来越分散化,所以咱们须要一套日志服务来采集、传输、检索日志

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

需要场景

在咱们的场景下,顶峰日志写入压力大(每秒千万级日志条数);实时要求高:日志解决从采集到能够被检索的工夫失常 1s 以内(顶峰期间 3s);老本压力微小,要求保留半年的日志且能够回溯查问(百 PB 规模)。

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...
对象存储https://buy.cloud.tencent.com...
归档存储https://buy.cloud.tencent.com...

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

如果检索波及到近程存储上的日志块, 检索服务会将波及到的日志块下载到本地存储, 而后在本地实现解压和检索。因为日志分块的设计,日志块的下载同检索一样,咱们能够在多台机器上并行操作; 下载回本地的数据复制反对在本地缓存后肯定的工夫后再删除, 这样有效期内对同一日志块的检索需要就能够在本地实现而不须要再反复拉取一遍(日志检索场景里屡次检索同样的日志数据还是很常见).

对于归档存储, 在发动检索申请前, 须要对归档存储中的日志块发动取回操作, 取回操作个别耗时在几分钟左右, 实现取回操作后日志块被取回到近程存储上,再之后的数据流转就跟之前统一了。即开发人员如果想要检索冷数据, 须要提前对日志块做归档取回的申请,期待取回实现后就能够依照热数据速度来进行日志检索了。

检索服务架构

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

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

  • 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 日志数据索引收缩)。能有两个量级的差距。