# 轮询系统性能调优指南

## 千万卡规模优化方案

### 1. 调度器优化

当前配置存在瓶颈：每次只取 1000 张卡，每 10 秒调度一次，每分钟最多处理 6000 张卡。

**优化方案**：

```go
// 修改 scheduler.go 中的 processTimedQueue
cardIDs, err := s.redis.ZRangeByScore(ctx, queueKey, &redis.ZRangeBy{
    Min:   "-inf",
    Max:   formatInt64(now),
    Count: 10000,  // 从 1000 提高到 10000
}).Result()
```

调整调度间隔：
```go
func DefaultSchedulerConfig() *SchedulerConfig {
    return &SchedulerConfig{
        ScheduleInterval: 5 * time.Second,  // 从 10 秒改为 5 秒
        // ...
    }
}
```

优化后：每分钟可处理 12 万张卡

### 2. 并发控制优化

修改 `scripts/init_polling_config.sql`：

```sql
-- 千万卡规模的并发配置
INSERT INTO tb_polling_concurrency_config (task_type, max_concurrency, description) VALUES
('realname', 500, '实名检查并发数'),
('carddata', 1000, '流量检查并发数'),
('package', 500, '套餐检查并发数'),
('stop_start', 100, '停复机操作并发数');
```

### 3. Worker 多实例部署

多实例扩容时，必须拆分 Worker 角色，禁止直接复制多个完整 Worker 进程。推荐保留 `1 leader + N consumer`：

```yaml
# docker-compose.yml 示例
services:
  worker-leader:
    image: junhong-cmp-worker
    environment:
      - JUNHONG_WORKER_ROLE=leader
      - JUNHONG_WORKER_INSTANCE_NAME=worker-leader-1
  worker-consumer-1:
    image: junhong-cmp-worker
    environment:
      - JUNHONG_WORKER_ROLE=consumer
      - JUNHONG_WORKER_INSTANCE_NAME=worker-consumer-1
  worker-consumer-2:
    image: junhong-cmp-worker
    environment:
      - JUNHONG_WORKER_ROLE=consumer
      - JUNHONG_WORKER_INSTANCE_NAME=worker-consumer-2
```

说明：
- `leader` 负责轮询初始化、轮询调度、Asynq 定时任务调度等单例职责
- `consumer` 只负责 Asynq 队列消费，适合横向扩容
- 单实例部署默认使用 `all`，行为等价于历史版本
- 不要直接启动多个 `all` 或多个 `leader`，否则会重复扫库、重复调度、重复入队
- 需要提升吞吐时，优先增加 `consumer` 数量，而不是复制完整 Worker
- 本期仅完成角色拆分，不包含 Redis Leader 锁、自动选主、任务唯一性去重，这些作为第二期增强处理

### 4. 检查间隔优化

根据业务需求调整检查间隔，减少不必要的检查：

| 卡状态 | 当前间隔 | 建议间隔 | 说明 |
|--------|---------|---------|------|
| 未实名 | 60 秒 | 300 秒 | 实名状态不会频繁变化 |
| 已实名 | 3600 秒 | 86400 秒 | 已实名只需每天检查一次 |
| 已激活流量 | 1800 秒 | 3600 秒 | 每小时检查一次足够 |

这样可以大幅减少检查次数：
- 原方案：1000 万次/小时
- 优化后：约 100 万次/小时

### 5. 初始化优化

使用 Pipeline 批量写入 Redis：

```go
// 优化 initCardPolling，使用 Pipeline
func (s *Scheduler) initCardsBatch(ctx context.Context, cards []*model.IotCard) error {
    pipe := s.redis.Pipeline()
    for _, card := range cards {
        config := s.MatchConfig(card)
        if config == nil {
            continue
        }
        // 批量 ZADD
        nextTime := s.calculateNextCheckTime(card, config)
        pipe.ZAdd(ctx, queueKey, redis.Z{Score: float64(nextTime), Member: card.ID})
        // 批量 HSET
        pipe.HSet(ctx, cacheKey, cardData)
    }
    _, err := pipe.Exec(ctx)
    return err
}
```

优化效果：减少 Redis 往返次数，初始化时间从 150 秒降至 30-50 秒

### 6. 数据库索引优化

确保以下索引存在：

```sql
-- 用于渐进式初始化的游标分页
CREATE INDEX IF NOT EXISTS idx_iot_card_id_asc ON tb_iot_card(id ASC) WHERE deleted_at IS NULL;

-- 用于条件筛选
CREATE INDEX IF NOT EXISTS idx_iot_card_polling
ON tb_iot_card(enable_polling, real_name_status, activation_status, card_category);
```

### 7. Redis 配置优化

```conf
# redis.conf
maxmemory 8gb
maxmemory-policy allkeys-lru

# 连接池优化
tcp-keepalive 300
timeout 0
```

### 8. 监控告警阈值

千万卡规模的告警阈值建议：

```sql
INSERT INTO tb_polling_alert_rule (rule_name, metric_type, task_type, threshold, comparison, alert_level) VALUES
('队列积压告警', 'queue_size', 'polling:realname', 100000, 'gt', 'critical'),
('失败率告警', 'failure_rate', 'polling:realname', 10, 'gt', 'warning'),
('延迟告警', 'avg_wait_time', 'polling:carddata', 3600, 'gt', 'warning');
```

---

## 容量规划

| 规模 | Worker 数 | Redis 内存 | 并发总数 | 预估 QPS |
|------|----------|-----------|---------|---------|
| 100 万卡 | 2 | 512MB | 200 | 1000 |
| 500 万卡 | 4 | 2GB | 500 | 3000 |
| 1000 万卡 | 8 | 4GB | 1000 | 5000 |
| 2000 万卡 | 16 | 8GB | 2000 | 10000 |

## 压测建议

1. 使用 `wrk` 或 `vegeta` 对 API 进行压测
2. 使用脚本批量创建测试卡验证初始化性能
3. 监控 Redis 内存和 CPU 使用率
4. 监控数据库连接池和查询延迟

```bash
# API 压测示例
wrk -t12 -c400 -d30s http://localhost:3000/api/admin/polling-stats
```
