Files
junhong_cmp_fiber/internal/polling/carrierqueue/sr_queue.go
邹进 a4e3fe4d8f
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m24s
fix: 停复机指令队列审查问题
- 开关开启但分发器心跳缺失时回退为当场执行,避免指令无人消费
- 待确认指令先查上游状态再判断;执行期间被覆盖时把待确认带到新指令
- 同方向失败退避中重复写入不提前执行时间
- 领取容量把执行上限与本秒剩余 QPS 分开计算
- 队列复机共用单卡复机锁;执行上下文取消后按结果未知确认
- 消费者并行停止;读取最近调用时间失败时不覆盖;成功但终结日志失败按成功处理

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:43:58 +09:00

73 lines
2.2 KiB
Go

package carrierqueue
import (
"context"
"strconv"
"sync"
"time"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// heartbeatFreshness 是分发器心跳有效期:超过该时长没有心跳视为指令无人消费。
const heartbeatFreshness = 30 * time.Second
// heartbeatCacheTTL 是进程内缓存心跳判断的时长,避免每次请求评估都读 Redis。
const heartbeatCacheTTL = 3 * time.Second
// CommandQueue 是停复机指令队列写入端:开关读取受控系统配置,写入走共享缓存 Lua。
type CommandQueue struct {
store *Store
settings *SettingsProvider
mu sync.Mutex
aliveAt time.Time
alive bool
checkedAt time.Time
}
// NewCommandQueue 创建停复机指令队列写入端。
func NewCommandQueue(store *Store, settings *SettingsProvider) *CommandQueue {
return &CommandQueue{store: store, settings: settings}
}
// Enabled 返回是否经指令队列执行:停复机出口开关开启,且分发器在心跳有效期内运行(有消费方)。
// 开关开启但没有分发器时回退为各入口当场执行,避免指令写入后无人消费。
func (q *CommandQueue) Enabled(ctx context.Context) bool {
if q == nil || q.store == nil || !q.settings.Current(ctx).StopResumeQueueEnabled {
return false
}
return q.dispatcherAlive(ctx)
}
func (q *CommandQueue) dispatcherAlive(ctx context.Context) bool {
q.mu.Lock()
defer q.mu.Unlock()
if time.Since(q.checkedAt) < heartbeatCacheTTL {
return q.alive
}
q.checkedAt = time.Now()
beats, err := q.store.redis.HGetAll(ctx, constants.RedisCarrierSchedulerHeartbeatKey).Result()
if err != nil {
return q.alive
}
q.alive = false
for _, raw := range beats {
if ms, convErr := strconv.ParseInt(raw, 10, 64); convErr == nil && time.Since(time.UnixMilli(ms)) < heartbeatFreshness {
q.alive = true
break
}
}
return q.alive
}
// WriteCommand 写入或覆盖卡的停复机指令。
func (q *CommandQueue) WriteCommand(ctx context.Context, cmd StopResumeCommand) (int64, error) {
return q.store.WriteCommand(ctx, cmd)
}
// RequestDeviceEval 请求一次设备维度判断。
func (q *CommandQueue) RequestDeviceEval(ctx context.Context, deviceID uint, scenes []string) error {
return q.store.RequestDeviceEval(ctx, deviceID, scenes)
}