组件详解

概述

参考:

Grafana Loki 是一套可以组合成一个功能齐全的日志堆栈组件,与其他日志记录系统不同,Loki 是基于仅索引有关日志元数据的想法而构建的:标签(就像 Prometheus 标签一样)。日志数据本身被压缩然后并存储在对象存储(例如 S3 或 GCS)的块中,甚至存储在本地文件系统上,轻量级的索引和高度压缩的块简化了操作,并显着降低了 Loki 的成本,Loki 更适合中小团队。

Grafana Loki 主要由 3 部分组成:

  • loki: 日志记录引擎,负责存储日志和处理查询
  • promtail: 代理,负责收集日志并将其发送给 loki
  • grafana: UI 界面

Distributor(分配器)

Distributor 服务负责处理客户端写入的日志,它本质上是日志数据写入路径中的第一站,一旦 Distributor 收到日志数据,会将其拆分为多个批次,然后并行发送给多个 Ingester。

Distributor 通过 gRPC 与 Ingester 通信,它们都是无状态的,可以根据需要扩大或缩小规模。

Validation(验证)

Preprocessing(预处理)

Rate limiting(速率限制)

Forwarding(转发)

Hashing(哈希)

Distributors 将一致性哈希和可配置的复制因子结合使用,以确定 Ingester 服务的哪些实例应该接收指定的流。

流是一组与租户和唯一标签集关联的日志,使用租户 ID 和标签集对流进行 hash 处理,然后使用哈希查询要发送流的 Ingesters

存储在 Consul 中的哈希环被用来实现一致性哈希。。。所有的 ingester 都会使用自己拥有的一组 Token 注册到哈希环中,每个 Token 是一个随机的无符号 32 位数字,与一组 Token 一起,ingester 将其状态注册到哈希环中,状态 JOININGACTIVE 都可以接收写请求,而 ACTIVELEAVINGingesters 可以接收读请求。在进行哈希查询时,distributors 只使用处于请求的适当状态的 ingester 的 Token。

为了进行哈希查找,distributors 找到最小合适的 Token,其值大于日志流的哈希值,当复制因子大于 1 时,属于不同 ingesters 的下一个后续 Token(在环中顺时针方向)也将被包括在结果中。

这种哈希配置的效果是,一个 ingester 拥有的每个 Token 都负责一个范围的哈希值,如果有三个值为 0、25 和 50 的 Token,那么 3 的哈希值将被给予拥有 25 这个 Token 的 ingester,拥有 25 这个 Token 的 ingester负责1-25的哈希值范围。

Quorum consistency(仲裁一致性)

由于所有的 Distributors 共享对同一哈希环的访问权,所以写请求可以被发送到任何 Distributor

为了确保查询结果的一致性,Loki 在读和写上使用 Dynamo 式的仲裁一致性方式,这意味着 Distributor 将等待至少一半加一个 Ingesters 的响应,然后再对发送的客户端进行响应。

Ingester(摄取器)

Ingester 服务负责将日志数据写入长期存储后端(DynamoDB、S3、Cassandra 等)。并且还会响应查询器的查询请求以返回自身内存中的数据。

来自每个唯一标签集的日志在内存中被建立成 chunks(块),然后可以根据配置的时间间隔刷新到支持的后端存储。在下列情况下,块被压缩并标记为只读:

  • 当前块容量已满(该值可配置)
  • 过了太长时间没有更新当前块的内容
  • 刷新了

每当一个数据块被压缩并标记为只读时,一个可写的数据块就会取代它。如果一个 ingester 进程崩溃或突然退出,所有尚未刷新的数据都会丢失。Loki 通常配置为多个副本(通常是 3 个)来降低这种风险。

当向持久存储刷新时,该块将根据其租户、标签和内容进行哈希处理,这意味着具有相同数据副本的多个 ingesters 实例不会将相同的数据两次写入备份存储中,但如果对其中一个副本的写入失败,则会在备份存储中创建多个不同的块对象。有关如何对数据进行重复数据删除,请参阅 Querier。

此外 Ingester 会验证摄取的日志行是按照时间戳递增的顺序接收的(即每条日志的时间戳都比前面的日志晚一些),当 Ingester 收到不符合这个顺序的日志时,该日志行会被拒绝并返回一个错误。

  • 如果传入的行与之前收到的行完全匹配(与之前的时间戳和日志文本都匹配),传入的行将被视为完全重复并被忽略。
  • 如果传入的行与前一行的时间戳相同,但内容不同,则接受该日志行。这意味着同一时间戳有两个不同的日志行是可能的。

但是,从 XX 版本开始,Ingester 添加了排序功能,通过配置接收乱序写入。

WAL

上面我们也提到了 Ingesters 将数据临时存储在内存中,如果发生了崩溃,可能会导致数据丢失,而 WAL 就可以帮助我们来提高这方面的可靠性。

在计算机领域,WAL(Write-ahead logging,预写式日志)是数据库系统提供原子性和持久化的一系列技术。

在使用 WAL 的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中。通常包含 redo 和 undo 两部分信息。为什么需要使用 WAL,然后包含 redo 和 undo 信息呢?举个例子,如果一个系统直接将变更应用到系统状态中,那么在机器断电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了 WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。

redo log 称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log,这样当发生断电之类的情况时系统可以在重启后继续操作。undo log 称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态。

Loki 中的 WAL 记录了传入的数据,并将其存储在本地文件系统中,以保证在进程崩溃的情况下持久保存已确认的数据。重新启动后,Loki 将重放日志中的所有数据,然后将自身注册,准备进行后续写操作。这使得 Loki 能够保持在内存中缓冲数据的性能和成本优势,以及持久性优势(一旦写被确认,它就不会丢失数据)。

Query Frontend(查询前端)

查询前端是一个可选的服务,提供 Querier 的 API 端点,可以用来加速读取路径。当查询前端就位时,应将传入的查询请求定向到查询前端,而不是 querier, 为了执行实际的查询,群集中仍需要 querier 服务。 查询前端在内部执行一些查询调整,并在内部队列中保存查询。querier 作为 workers 从队列中提取作业,执行它们,并将它们返回到查询前端进行汇总。querier 需要配置查询前端地址(通过-querier.frontend-address CLI 标志),以便允许它们连接到查询前端。 查询前端是无状态的,然而,由于内部队列的工作方式,建议运行几个查询前台的副本,以获得公平调度的好处,在大多数情况下,两个副本应该足够了。

Queueing(队列)

查询前端的排队机制用于:

  • 确保可能导致 querier 出现内存不足(OOM)错误的查询在失败时被重试。这允许管理员可以为查询提供不足的内存,或者并行运行更多的小型查询,这有助于降低总成本。
  • 通过使用先进先出队列(FIFO)将多个大型请求分配到所有 querier 上,以防止在单个 querier 中传送多个大型请求。
  • 通过在租户之间公平调度查询。

Splitting(分割)

查询前端将较大的查询分割成多个较小的查询,在下游 querier 上并行执行这些查询,并将结果再次拼接起来。这可以防止大型查询在单个查询器中造成内存不足的问题,并有助于更快地执行这些查询。

Caching(缓存)

查询前端支持缓存指标查询结果,并在后续查询中重复使用。如果缓存的结果不完整,查询前端会计算所需的子查询,并在下游 querier 上并行执行这些子查询。查询前端可以选择将查询与其 step 参数对齐,以提高查询结果的可缓存性。结果缓存与任何 loki 缓存后端(当前为 memcached、redis 和内存缓存)兼容。

Querier(查询器)

Querier 查询器服务使用 LogQL 查询语言处理查询,从 Ingesters 和长期存储中获取日志。

查询器查询所有 ingesters 的内存数据,然后再到后端存储运行相同的查询。由于复制因子,查询器有可能会收到重复的数据。为了解决这个问题,查询器在内部对具有相同纳秒时间戳、标签集和日志信息的数据进行重复数据删除。

Ruler(规则管理器)

Table Manger(表管理器)

Table Manager(表管理器)


最后修改 September 6, 2024: service discovery (1c52294f)