Files
junhong_cmp_fiber/internal/task/polling_priority_index.go
邹进 1214181e78
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 16m2s
feat: 轮询观测入库异步化,调网关与入库分离,采集记录经消息攒批写入
- 轮询步骤异步模式下只调网关并发布 card.ingest 入库消息,入库消费者固定并发 30、按采集 ID 去重、可重试错误不限次数退避重投
- 采集记录经 card.collection 攒批写入,失败整批重投,新增 (observation_id, observed_at) 唯一索引
- 网络、实名观测增加乱序保护;轮询读查询走缓存,套餐状态查询各自计时且失败不缓存
- 入库积压告警与反压;系统配置 polling.ingest.mode 默认 sync 可回退;测试环境 nsqd 消息全部落盘

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:33:29 +09:00

74 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package task
import (
"context"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// priorityIndexAbsent 判断活动优先项索引能否确认该卡该任务类型没有活动优先项。
// 索引未建立、读取失败或成员存在时返回 false,调用方照常查库。
func (b *PollingBase) priorityIndexAbsent(ctx context.Context, cardID uint, taskType string) bool {
if b == nil || b.redis == nil {
return false
}
pipe := b.redis.Pipeline()
ready := pipe.Exists(ctx, constants.RedisPollingPriorityReadyKey)
member := pipe.SIsMember(ctx, constants.RedisPollingPriorityActiveKey, constants.PollingPriorityIndexMember(cardID, taskType))
if _, err := pipe.Exec(ctx); err != nil {
return false
}
return ready.Val() > 0 && !member.Val()
}
// PriorityIndexRebuilder 定期按库内活动优先项重建索引(并入最近入队的项),使已终态的项退出索引。
type PriorityIndexRebuilder struct {
store *postgres.PollingPriorityItemStore
redis *redis.Client
logger *zap.Logger
}
// NewPriorityIndexRebuilder 创建活动优先项索引重建器。
func NewPriorityIndexRebuilder(store *postgres.PollingPriorityItemStore, redisClient *redis.Client, logger *zap.Logger) *PriorityIndexRebuilder {
if logger == nil {
logger = zap.NewNop()
}
return &PriorityIndexRebuilder{store: store, redis: redisClient, logger: logger}
}
// Rebuild 重建一次索引;失败时清除就绪标记,轮询步骤回退为查库。
func (r *PriorityIndexRebuilder) Rebuild(ctx context.Context) {
if r == nil || r.store == nil || r.redis == nil {
return
}
keys, err := r.store.ListActiveKeys(ctx)
if err != nil {
r.logger.Warn("重建活动优先项索引失败,轮询步骤回退查库", zap.Error(err))
r.redis.Del(ctx, constants.RedisPollingPriorityReadyKey)
return
}
members := make([]any, 0, len(keys))
for _, key := range keys {
members = append(members, constants.PollingPriorityIndexMember(key.CardID, key.TaskType))
}
tmp := constants.RedisPollingPriorityRebuildKey
pipe := r.redis.TxPipeline()
pipe.Del(ctx, tmp)
if len(members) > 0 {
pipe.SAdd(ctx, tmp, members...)
}
pipe.SUnionStore(ctx, tmp, tmp, constants.RedisPollingPriorityRecentKey)
pipe.Del(ctx, constants.RedisPollingPriorityActiveKey)
pipe.SUnionStore(ctx, constants.RedisPollingPriorityActiveKey, tmp)
pipe.Del(ctx, tmp)
pipe.Set(ctx, constants.RedisPollingPriorityReadyKey, "1", constants.PollingPriorityIndexReadyTTL)
if _, err := pipe.Exec(ctx); err != nil {
r.logger.Warn("写入活动优先项索引失败,轮询步骤回退查库", zap.Error(err))
r.redis.Del(ctx, constants.RedisPollingPriorityReadyKey)
}
}