Loki在分布式部署的模式下,保存Ingester服务的状态主要有3个渠道,分别是etcd、consul和基于gossip协议的memberlist。不管Loki用的是什么方式,它们最终都是将哈希环以KV的方式保存。再聊Loki之前,先来了解下一致性哈希的基本概念。
一致性哈希是在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法。相较于普通的哈希算法,一致性哈希除了继承数据的离散性、扩展性和容错性之外,还引入了虚拟节点的概念,极大的优化了数据在存储过程的中出现的倾斜问题。另外,遇到需要在不中断业务的情况下扩容节点或处理故障节点时造成的数据哈希变动场景,通常还需要引入数据副本和请求中继的方式来支持服务的热变动。
Loki的分布式架构与Cortex一脉相承,其中对于各组件服务状态和数据的哈希路由均采用了相同的设计。在ring.go
中可以看到在Cortex中定义4种类型的哈希环IngesterRing
、RulerRing
、DestributorRing
和CompactorRing
。其中最重要的一个环便是IngesterRing。
先来看看Loki官方的架构说明里面是怎么描述的吧。
image.png
从这里面我们可以了解到几个关键信息:
- 对日志流进行hash是根据
tenant ID
和Labels
这两个字段计算的 - 每个Ingester服务都会拥有一组32位数字组成的Token注册到哈希环中
- Ingester状态只有JOINING和ACTIVE状态时,才可以接收日志写请求
- Ingester状态只有ACTIVE和LEAVING状态时,才可以接收日志读请求
- 查询hash环上寻址会符合
token > 日志流hash key
中最小的一个token,并找到预期对应的ingester实例 - 如果复制因子大于1,则在已有哈希环上token的位置再顺时针寻找下一个不属于当前ingster的实例
根据上述描述,我们大致得到一个类似下面的示意图:
image.png
可以看到,当复制因子等于2时,Distributor就会将日志流发给两个ingester服务处理。其中一个ingester故障或者下线时,查询日志时仍然可以从有副本下一个Ingester实例中获取到数据
image.png
在真实环境里,我们来看看Loki中关于IngesterRing的核心配置如下:
ingester_config:
lifecycler:
num_tokens: <int> | default = 128]
ring:
kvstore: <string> | default = "consul"]
replication_factor: <int> | default = 3]
这里最重要的便是虚拟节点(num_tokens)
和复制因子(replication_factor)
,这两个配置将直接影响日志流经过一致性哈希后的路由。虚拟节点的数量直接影响了一致性哈希的数据离散性。可以简单的概括为num_tokens越多,日志流经过一致性哈希后会分散得更均衡到ingester处理,不过这是建立在牺牲内存消耗和查找时间
前提下的结论。
关于一致性哈希均衡性的结论可参考
https://medium.com/@dgryski/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8#890d
此外,复制因子的数量决定了Loki分布式集群对于节点故障的容忍性
,以replication_factor=2为例,当日志流经过一致性哈希后会映射至两个ingester实例,那么当集群出现故障时,在挂掉一个ingester实例时,Loki日志流的写入和读取可以从相邻的下一个ingester实例中获取到。如果恰好两个相邻的ingester实例挂掉的话那此时日志流的读写映射到此区域的皆会受到影响。我们知道,ingester在将chunks刷新到存储前,这部分数据都是存在内存的,所以复制因子配置过大,会造成内容冗余,配置过小则又会造成故障时丢失数据的风险。在生产环境中使用时,需根据loki的集群规模找到最佳值。
我们可以在浏览器中访问http://<loki地址>/ring
来查看和管理一致性哈希环。
可以看到里面主要存了Ingester的ID、状态、地址、注册时间、更新时间、Token数(虚拟节点)、数据分布情况和每个Ingester的详细Tokens。对所有的tokens做个排序便形成了一致性哈希环。当有新的ingester加入进来的时候,带来的新tokens会填入一致性哈希环,期间涉及到实例间数据迁移的部分仅会影响新token相邻节点之间展开。
另外,我们还可以发现ingester注册进环中的服务名取的是hostname,也就是说如果你的Loki集群主机名有变化的话,ingester会以新的实例名称注册进哈希环。老的失效的ingester仍然会保留在环中影响数据分布。这时我们需要在管理界面中将失效的Ingester实例删除掉,也就是图中的Forget按钮。
如果你的Loki集群是裸金属部署,那你需要保证服务器的HostName
如果你的Loki集群是通过kubernetes部署,那Ingester一定要用StatesfulSet类型
最后,除一致性哈希外,Loki中关于仲裁一致性也会影响集群的可用性。当Distributor的一次请求要经过集群内超过半数的ingester成功相应后才会将本次请求返回给客户端。关于这部分,Loki采用的Dynamo-style方法,这个曾经在Amazon的Dynamo系统中采用过,目前Riak, Cassandra, 和Voldemort这些无主节点备份模型也有参考。这里算是另一个话题了,留作以后再写吧。
关注公众号「云原生小白」,获取更多精彩内容
网友评论