Thanos源码专题【左扬精讲】—— 深入 Thanos Query 源码:当Thanos Query访问Thanos Sidecar的gRPC服务失败时的处理流程

 Thanos Query 源码解析:当Thanos Query访问Thanos Sidecar的gRPC服务失败时的处理流程

   Thanos Query作为Thanos监控系统中的查询组件,负责从多个数据源(如 Thanos Sidecar)中聚合和查询指标数据。当Thanos Query访问Thanos Sidecar的gRPC服务失败时,其源码中定义了一套完善的处理机制来确保系统的高可用性和稳定性。以下是该机制的源码细节解析:

一、初始连接尝试与超时控制

  在Thanos Query的源码中,EndpointSet结构体的getActiveEndpoints方法负责管理端点的连接状态。当需要连接到Thanos Sidecar时,该方法会通过grpc.DialContext建立gRPC连接,并设置超时时间(默认为5秒):

func (e *EndpointSet) getActiveEndpoints(ctx context.Context, endpoints map[string]*endpointRef) map[string]*endpointRef {
    // ...
    conn, err := grpc.DialContext(ctx, addr, e.dialOpts...) // 关键连接建立
    if err != nil {
        e.updateEndpointStatus(&endpointRef{addr: addr}, err) // 失败时更新状态
        level.Warn(e.logger).Log("msg", "update of node failed", "err", errors.Wrap(err, "dialing connection"), "address", addr)
        return
    }
    // ...
}
    • 超时机制:通过context.WithTimeout控制连接超时,避免长时间阻塞。
    • 失败触发:若连接失败(如网络问题、Sidecar 未运行),则进入错误处理流程。

二、错误记录与状态更新

当连接失败时,updateEndpointStatus方法会被调用以更新端点的状态:

func (e *EndpointSet) updateEndpointStatus(er *endpointRef, err error) {
    e.endpointsStatusesMtx.Lock()
    defer e.endpointsStatusesMtx.Unlock()
 
    status := EndpointStatus{Name: er.addr}
    prev, ok := e.endpointStatuses[er.addr]
    if ok {
        status = *prev
    }
 
    if err != nil {
        status.LastError = &stringError{originalErr: err} // 记录错误
        status.LastCheck = time.Now()
    } else {
        status.LastError = nil
        status.LastCheck = time.Now()
        mint, maxt := er.TimeRange()
        status.LabelSets = er.LabelSets()
        status.ComponentType = er.ComponentType()
        status.MinTime = mint
        status.MaxTime = maxt
    }
    e.endpointStatuses[er.addr] = &status // 更新状态
}
    • 日志记录:通过level.Warn记录错误详情。
    • 状态标记:将端点标记为不健康,并记录最后一次错误的时间和详情。 

三、端点处理策略

根据端点的配置(是否为严格静态模式),Thanos Query会采取不同的处理策略:

if !seenAlready && !spec.IsStrictStatic() {
    // 非严格静态模式:直接关闭连接并移除
    er.Close()
    return
}
 
// 严格静态模式:保留端点并假设完整时间范围
if !seenAlready {
    metadata = &endpointMetadata{
        &infopb.InfoResponse{
            Store: &infopb.StoreInfo{
                MinTime: math.MinInt64,
                MaxTime: math.MaxInt64,
            },
        },
    }
    er.Update(metadata) // 更新元数据(假设覆盖所有时间)
}  
    • 非严格静态模式:直接关闭连接,并在后续的更新周期中移除该端点。
    • 严格静态模式:保留端点,并假设其覆盖完整的时间范围(MinTime=math.MinInt64MaxTime=math.MaxInt64),以避免因临时故障导致数据缺失。

四、健康检查与自动恢复

Thanos Query会定期(通过Update方法)执行健康检查,以重新尝试连接失败的端点:

func (e *EndpointSet) Update(ctx context.Context) {
    e.updateMtx.Lock()
    defer e.updateMtx.Unlock()
 
    // 获取当前端点列表
    e.endpointsMtx.RLock()
    endpoints := make(map[string]*endpointRef, len(e.endpoints))
    for addr, er := range e.endpoints {
        endpoints[addr] = er
    }
    e.endpointsMtx.RUnlock()
 
    // 重新检查所有端点
    activeEndpoints := e.getActiveEndpoints(ctx, endpoints)
 
    // 更新端点状态
    for addr, er := range endpoints {
        if _, ok := activeEndpoints[addr]; !ok {
            er.Close() // 关闭不活跃的端点
            delete(endpoints, addr)
            e.updateEndpointStatus(er, errors.New(unhealthyEndpointMessage))
        }
    }
 
    // 添加新活跃的端点
    for addr, er := range activeEndpoints {
        if _, ok := endpoints[addr]; !ok {
            endpoints[addr] = er
            e.updateEndpointStatus(er, nil)
        }
    }
}
    • 定期检查:通过 Update 方法周期性调用 getActiveEndpoints,重新尝试连接所有端点。
    • 自动恢复:若 Thanos Sidecar 恢复运行,下次健康检查时会重新建立连接并更新状态。
    • 超时清理:通过 cleanUpEndpointStatuses 方法清理长时间不健康的端点(默认5分钟)。

五、监控指标更新

无论连接成功或失败,Thanos Query 都会更新监控指标 thanos_store_nodes_grpc_connections,以反映当前活动的 StoreAPI 连接数:

func (c *endpointSetNodeCollector) Update(nodes map[component.Component]map[string]int) {
    c.mtx.Lock()
    defer c.mtx.Unlock()
    c.storeNodes = nodes // 更新内部状态
}
 
func (c *endpointSetNodeCollector) Collect(ch chan<- prometheus.Metric) {
    c.mtx.Lock()
    defer c.mtx.Unlock()
 
    for storeType, occurrencesPerExtLset := range c.storeNodes {
        for externalLabels, occurrences := range occurrencesPerExtLset {
            ch <- prometheus.MustNewConstMetric(
                c.connectionsDesc,
                prometheus.GaugeValue,
                float64(occurrences),
                externalLabels,
                storeType.String(),
            )
        }
    }
}

六、故障转移与查询路由

Thanos Query 通过 GetStoreClients 等方法获取健康的端点,并确保查询请求仅路由到这些健康的端点:

func (e *EndpointSet) GetStoreClients() []store.Client {
    e.endpointsMtx.RLock()
    defer e.endpointsMtx.RUnlock()
 
    stores := make([]store.Client, 0, len(e.endpoints))
    for _, er := range e.endpoints {
        if er.HasStoreAPI() { // 仅返回健康的端点
            stores = append(stores, er)
        }
    }
    return stores
} 
    • 自动故障转移:查询请求仅路由到健康的端点,避免故障节点影响查询。
    • 查询去重:由于 Thanos Sidecar 可能返回重复标签的数据,Thanos Query 会对结果进行去重处理。

七、 gRPC 重试机制(间接影响)

  虽然 gRPC 客户端可能配置了重试机制,但 Thanos Query 的错误处理逻辑会覆盖这一行为,确保严格按上述流程处理。若 gRPC 客户端重试成功,Thanos Query 仍会通过元数据检查确认端点的健康状态。 

posted @ 2025-05-19 17:15  左扬  阅读(45)  评论(0)    收藏  举报