Files
junhong_cmp_fiber/docs/polling-system/performance-tuning.md
huang 931e140e8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
实现功能:
- 实名状态检查轮询(可配置间隔)
- 卡流量检查轮询(支持跨月流量追踪)
- 套餐检查与超额自动停机
- 分布式并发控制(Redis 信号量)
- 手动触发轮询(单卡/批量/条件筛选)
- 数据清理配置与执行
- 告警规则与历史记录
- 实时监控统计(队列/性能/并发)

性能优化:
- Redis 缓存卡信息,减少 DB 查询
- Pipeline 批量写入 Redis
- 异步流量记录写入
- 渐进式初始化(10万卡/批)

压测工具(scripts/benchmark/):
- Mock Gateway 模拟上游服务
- 测试卡生成器
- 配置初始化脚本
- 实时监控脚本

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 17:32:44 +08:00

4.2 KiB
Raw Permalink Blame History

轮询系统性能调优指南

千万卡规模优化方案

1. 调度器优化

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

优化方案

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

调整调度间隔:

func DefaultSchedulerConfig() *SchedulerConfig {
    return &SchedulerConfig{
        ScheduleInterval: 5 * time.Second,  // 从 10 秒改为 5 秒
        // ...
    }
}

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

2. 并发控制优化

修改 scripts/init_polling_config.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 实例分担负载:

# docker-compose.yml 示例
services:
  worker-1:
    image: junhong-cmp-worker
    environment:
      - WORKER_ID=1
  worker-2:
    image: junhong-cmp-worker
    environment:
      - WORKER_ID=2
  worker-3:
    image: junhong-cmp-worker
    environment:
      - WORKER_ID=3

注意:只需一个实例运行调度器,其他实例只处理任务。

4. 检查间隔优化

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

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

这样可以大幅减少检查次数:

  • 原方案1000 万次/小时
  • 优化后:约 100 万次/小时

5. 初始化优化

使用 Pipeline 批量写入 Redis

// 优化 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. 数据库索引优化

确保以下索引存在:

-- 用于渐进式初始化的游标分页
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 配置优化

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

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

8. 监控告警阈值

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

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. 使用 wrkvegeta 对 API 进行压测
  2. 使用脚本批量创建测试卡验证初始化性能
  3. 监控 Redis 内存和 CPU 使用率
  4. 监控数据库连接池和查询延迟
# API 压测示例
wrk -t12 -c400 -d30s http://localhost:3000/api/admin/polling-stats