# OTel 服务连接恢复指南

## 问题背景

在之前的实现中，当 OTEL 收集器服务下线后再上线，应用无法自动恢复连接，导致数据无法上传。错误表现为：

```
failed to upload metrics: reader collect and export timeout: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 38.38.251.113:31547: connect: connection refused"
```

## 解决方案概述

本次修复通过以下方式实现了完整的连接恢复机制：

### 1. 导出器级别的重试策略（manager.go）

**配置导出器的内置重试机制**：

- **初始重试间隔** (RetryInitInterval): 1 秒
- **最大重试间隔** (RetryMaxInterval): 30 秒
- **最大重试总时间** (RetryMaxElapsed): 5 分钟
- **指数退避** (Exponential Backoff): 自动应用，每次重试间隔 * 1.5

```go
opts = append(opts,
    otlptracegrpc.WithRetry(otlptracegrpc.RetryConfig{
        Enabled:         true,
        InitialInterval: m.config.RetryInitInterval,  // 1秒
        MaxInterval:     m.config.RetryMaxInterval,   // 30秒
        MaxElapsedTime:  m.config.RetryMaxElapsed,    // 5分钟
    }),
)
```

### 2. 连接健康检查机制（manager.go）

**HealthCheck 方法**：

- 创建测试 span 来验证与收集器的连接
- 在重新连接前验证连接状态
- 可用于主动监控和恢复

```go
func (m *Manager) HealthCheck(ctx context.Context) error {
    tracer := m.CreateTracer("healthcheck")
    ctx, span := tracer.Start(ctx, "otel-health-check")
    defer span.End()
    span.AddEvent("health_check_started", ...)
    return nil
}
```

### 3. 自动连接恢复管理器（recovery.go）

**ConnRecoveryManager** - 后台监控服务：

- **检查间隔**: 30 秒（可配置）
- **最大重试次数**: 12 次（约 6 分钟）
- **自动恢复**: 当连接恢复时自动重置重试计数
- **状态报告**: 实时连接状态和重试次数

```go
recoveryManager := otel.GetRecoveryManager()
status := recoveryManager.GetConnectionStatus()
// Output: "Status: connected, Retries: 0/12, Endpoint: 38.38.251.113:31547"
```

### 4. 增强的配置参数（config.go）

新增超时和重试配置：

```go
type Config struct {
    // ... 现有字段 ...
    Timeout           time.Duration  // 连接超时（默认 10 秒）
    RetryEnabled      bool           // 是否启用重试（默认 true）
    RetryInitInterval time.Duration  // 初始重试间隔（默认 1 秒）
    RetryMaxInterval  time.Duration  // 最大重试间隔（默认 30 秒）
    RetryMaxElapsed   time.Duration  // 最大重试总时间（默认 5 分钟）
}
```

## 使用方法

### 基础初始化

```go
package main

import (
    "kami/utility/otel"
)

func main() {
    // 创建默认配置
    config := otel.DefaultConfig()
    config.ServiceName = "my-service"
    config.CollectorURL = "collector-host:4317"
    config.Insecure = true
    
    // 初始化 - 自动启动连接恢复监控
    if err := otel.InitWithConfig(config); err != nil {
        panic(err)
    }
    
    // 应用代码...
    
    // 优雅关闭（关闭恢复管理器）
    defer otel.Shutdown(context.Background())
}
```

### 自定义重试配置

```go
config := otel.DefaultConfig()
config.ServiceName = "my-service"
config.CollectorURL = "collector-host:4317"

// 自定义重试策略
config.Timeout = 15 * time.Second        // 连接超时 15 秒
config.RetryInitInterval = 500 * time.Millisecond  // 初始延迟 500ms
config.RetryMaxInterval = 60 * time.Second         // 最大延迟 60 秒
config.RetryMaxElapsed = 10 * time.Minute          // 最多重试 10 分钟

if err := otel.InitWithConfig(config); err != nil {
    panic(err)
}
```

### 监控连接状态

```go
// 获取恢复管理器
recoveryManager := otel.GetRecoveryManager()

// 检查是否连接
if recoveryManager.IsConnected() {
    // OTel 连接正常
}

// 获取详细状态
status := recoveryManager.GetConnectionStatus()
fmt.Println(status)  // "Status: connected, Retries: 0/12, Endpoint: ..."

// 手动重置重试计数（如果需要）
recoveryManager.ResetRetryCount()
```

### 动态调整配置

```go
recoveryManager := otel.GetRecoveryManager()

// 调整检查间隔（更频繁的检查）
recoveryManager.SetCheckInterval(15 * time.Second)

// 调整最大重试次数
recoveryManager.SetMaxRetries(20)
```

## 工作流程

### 正常场景

1. 应用启动 → OTel 初始化 → 启动恢复管理器
2. 恢复管理器后台每 30 秒检查一次连接
3. 连接正常，数据正常上传

### 服务下线场景

1. OTel 服务离线
2. 导出器的重试机制激活（指数退避，最多 5 分钟）
3. 同时恢复管理器开始监控（每 30 秒检查）
4. 当连接恢复时，重试计数自动重置

### 恢复流程

```
连接失败 → 导出器重试 (1s, 1.5s, 2.25s, ...)
           + 恢复管理器监控 (每30s检查)
           ↓
        5分钟后仍未恢复 → 记录错误日志
           ↓
        定期继续尝试（恢复管理器）
           ↓
        连接恢复 → 自动重置，恢复正常
```

## 配置推荐

### 本地开发

```go
config := otel.DefaultConfig()
config.Insecure = true
config.Timeout = 5 * time.Second
config.RetryMaxElapsed = 1 * time.Minute
```

### 生产环境

```go
config := otel.DefaultConfig()
config.Insecure = false  // 使用 TLS
config.Timeout = 10 * time.Second
config.RetryInitInterval = 1 * time.Second
config.RetryMaxInterval = 30 * time.Second
config.RetryMaxElapsed = 5 * time.Minute
// 配置认证头
config.Headers = map[string]string{
    "Authorization": "Bearer <token>",
}
```

### 高可用部署

```go
config := otel.DefaultConfig()
// 指向负载均衡器或多个收集器的虚拟 IP
config.CollectorURL = "otel-collector-lb:4317"
config.Timeout = 15 * time.Second
config.RetryInitInterval = 500 * time.Millisecond
config.RetryMaxInterval = 60 * time.Second
config.RetryMaxElapsed = 10 * time.Minute
```

## 日志输出

在连接状态变化时，系统会输出相应的日志：

```
# 启动恢复监控
[INFO] OTel连接恢复监控已启动, 检查间隔: 30s, 最大重试次数: 12

# 连接失败
[WARN] OTel连接失败 [尝试 1/12]: 38.38.251.113:31547, 已等待: 15s
[WARN] OTel连接失败 [尝试 2/12]: 38.38.251.113:31547, 已等待: 45s

# 超时
[ERROR] OTel服务连接超时: 超过最大重试次数 (12), 服务地址: 38.38.251.113:31547

# 连接恢复
[INFO] OTel服务已恢复连接: 38.38.251.113:31547

# 关闭
[INFO] OTel连接恢复监控已停止
```

## 最佳实践

### 1. 确保正确的超时配置

```go
// ❌ 不要设置过短的超时
config.Timeout = 1 * time.Second  // 太短，容易导致误判

// ✅ 合理的超时配置
config.Timeout = 10 * time.Second  // 给网络足够的时间
```

### 2. 监控重试计数

```go
ticker := time.NewTicker(1 * time.Minute)
go func() {
    for range ticker.C {
        rm := otel.GetRecoveryManager()
        if !rm.IsConnected() {
            log.Printf("WARNING: OTel disconnected - %s", 
                rm.GetConnectionStatus())
        }
    }
}()
```

### 3. 性能考虑

- 批处理大小: 1024（已优化）
- 批处理超时: 10 秒
- 收集器连接复用: 自动处理（gRPC）
- 压缩: gzip（节省带宽）

### 4. 生产部署检查清单

- [ ] 配置正确的 Collector 地址
- [ ] 启用 TLS（生产环境）
- [ ] 配置认证信息（Headers）
- [ ] 测试网络连接性
- [ ] 配置适当的重试策略
- [ ] 监控 OTel 系统的连接状态
- [ ] 设置告警（如连接中断超过阈值）

## 故障排查

### 问题：连接持续失败

**检查项**：

1. Collector 是否正在运行？ → `netstat -an | grep 4317`
2. 防火墙是否允许连接？ → 测试 ping / telnet
3. 地址和端口是否正确？ → 检查 config.CollectorURL
4. 网络延迟？ → 增加 Timeout 值

### 问题：频繁的"连接失败"日志

**解决方案**：

```go
// 增加检查间隔，减少日志频率
recoveryManager := otel.GetRecoveryManager()
recoveryManager.SetCheckInterval(60 * time.Second)

// 或者增加初始重试延迟
config.RetryInitInterval = 2 * time.Second
```

### 问题：内存持续增长

**检查项**：

1. 批处理大小是否过大？ → 调整 `log.WithExportMaxBatchSize`
2. 是否有采样率问题？ → 检查 `config.SampleRate`
3. 是否正确关闭了系统？ → 确保调用 `otel.Shutdown()`

## 参考文档

- [OpenTelemetry Go SDK 重试机制](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc)
- [gRPC 连接管理](https://grpc.io/docs/guides/performance-best-practices/)
- [OTLP Protocol 规范](https://opentelemetry.io/docs/specs/otel/protocol/exporter/)
