Distributor
1)Wirte Path 的第一站,进行初始化检查,比如 Tenant ID 等等,以减少 Ingester 压力,然后将流量发往多个 Ingester;Distributor 是无状态,前端负载均衡可以流量均发
2)Validation:时间戳检查、Label 检查、日志长度检查等等;
3)Preprocessing:预先处理,比如对 Label 排序以使得 Hash 一致;
4)Rate limiting:能够针对租户进行频率限制;增加 Distributor 节点,这能够限制某租户在每个 Distributor 的写入频率(加入租户和写入频率是固定的); 注意 distributor 使用自己的 ring 来与对等体注册,并获取节点数量,但这与 Ingester 的 ring 是两回事;
5)Forwarding:负责将数据进行转发到 Ingester(负责最后写入);
6)Replication factor:Distributor 会将日志写入多个 Ingester 中,并要保证 floor(replication_factor / 2) + 1 个写入成功;并且还提供 WAL 机制以防止数据丢失;
7)Hashing:Distributor 与 副本因数结合,以确定 Stream 应该写入哪个 Ingester 中;Stream 是通过 LabelSet 及 Tenant ID 进行 Hash 以找到对应的 Ingester 来存储日志;Ingester 将自己注册到 Hash Ring 中,并附带一堆 Token(Random Unsigned Int 32);进行 Hash 查找时,先找到大于 Stream 的最小 Hash,然后是下一个 Ingrester 的 Token,以此类推(顺时针);Ingester 拥有的 Token 仅负责部分范围 Hash;
8)Quorum consistency:Distributor 使用相同的 Hash ring,所以写入请求发往任意一个 Distributor 都是可以的;Loki 使用 Dynamo 风格的仲裁一致性,以保证读写一致,即 Distributor 将等待 (1/2) +1 个 Ingester 返回后再返回给客户端;
Ingester
1)负责将数据写入后端存储;负责将内存查询的日志返回;
2)lifecycler 来管理 Ingester 的生命周期(但是 WAL 取代此功能):PENDING(当它正在等待另一个 ingester 的切换时), JOINING(当它当前将其令牌插入环并初始化自身时。此时能够接收请求), ACTIVE(当它完全初始化时。它可能会收到对其拥有的令牌的写入和读取请求), LEAVING(当它关闭时。它可能会收到对它仍然在内存中的数据的读取请求。), UNHEALTHY(当它未能向 Consul 发送心跳时。UNHEALTHY 由 Distributor 在定期检查环时设置)
3)Ingester 收到的日志是先写入内存,然后周期刷新到后端存储:达到容量限制、长时间未更新、触发 Flush 操作,都导致 Chunk 被压缩被标记为 ReadOnly 状态,一个可写的块取而代之;此时如果 Ingester 出现问题,将丢失这些数据(所以 Ingester 支持副本以缓解此问题)
4)数据写入后端时,将通过 LabelSet Tenant Cotent 进行 Hash,所以不会有重复数据;如果写入失败,将在后备存储中创建多个不同的块对象(请参阅 Querier 了解如何对数据进行重复数据删除。)
5)Timestamp Ordering:日志是由顺序要求的,Ingester 将检查日志顺序,破坏时间顺序的日志将被丢弃并产生错误;也可以配置无序日志;当要求日志由顺序时,完全相同的日志,后者将被丢弃,但如果时间相同但内容不同,则将保留;
6)交接(Handoff):Ingester 在离开 Hash ring 或关闭时,将查看是否由新进入的 Ingester 实例,如果有则会尝试转交 Token 及 Chunk 数据;PENDING 状态即时在等待交接发生,如果超时,则将进行常规的加入过程,并插入新 TOKEN;这个交接过程是为了避免在关闭时 Flush 所有的 Chunk(刷新是个耗时的过程);
7)文件系统支持:经过 Ingester 支持通过 BoltDB 写入数据库,但仅工作于单进程模式,Querier 需要同一个后端存储,并且 BoltDB 仅允许一个进程持有锁;
Query frontend
1)可选组件用于提升查询速度,后面依旧由 Querier 执行查询;Frontend 进行查询调整并持有查询到队列中,Querier 从队列中获取任务执行查询,并将结果返回给 Frontend(所以 Querier 需要知道 Frondend 的地址);无状态;运行少量(2 个足够)来容错;
2)Queueing:确保大型查询失败时能够重试,以允许管理员提供不足内存,或云新更小的查询;防止大型查询分布在同个 Querier 上(通过 FIFO 队列);公平调度租户查询,防止 DDoS 攻击;
3)Splitting:将大型查询分解为多个小型查询,以减小压力;
4)Caching Metric Queries:能够对查询结果尽心更缓存,以用于后续的查询;如果查询结果不完整,将计算缺少的数据并查询;查询前端可以选择将查询与其步骤参数对齐,以提高查询结果的可缓存性;查询结果能够缓存在多种后端(Redis, Memecache, In-memory Cache)
5)Log Queries:还在开发阶段;
Querier
1)处理 LogQL 查询,以从 Ingester 或 Memeory 中获取日志;
2)先从 Ingester 中获取,失败后将从 Memory 中获取;
Index Gateway
在 BoltDB Shipper 中,Index Gateway 负载访问对象存储,并通过 gRCP 为 Querier 和 Ruler 提供索引查询,以减小对象存储的压力及成本。