优化生产日志体积:访问降噪、压缩清理与退款告警节流
跳过轮询/封面等热路径成功请求,按日 gzip 并保留可配天数,退款 max-retry 汇总告警。
This commit is contained in:
@@ -225,14 +225,25 @@ func (j *Job) warnMaxRetryRefunds(ctx context.Context, now time.Time) {
|
||||
j.logger.Warn("refund retry max count scan failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
// 汇总一条告警,避免每 2 分钟对同一批单刷屏;更新 last_retry_at 实现 24h 节流
|
||||
ids := make([]uint64, 0, len(rows))
|
||||
orderIDs := make([]uint64, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
j.logger.Warn("refund retry reached max count, need manual check",
|
||||
zap.Uint64("payment_id", row.ID),
|
||||
zap.Uint64("order_id", row.OrderID),
|
||||
zap.String("biz_type", row.BizType),
|
||||
zap.String("status", row.Status),
|
||||
zap.Int("retry_count", row.RetryCount),
|
||||
)
|
||||
ids = append(ids, row.ID)
|
||||
orderIDs = append(orderIDs, row.OrderID)
|
||||
}
|
||||
j.logger.Warn("refund retry reached max count, need manual check",
|
||||
zap.Int("count", len(rows)),
|
||||
zap.Uint64s("payment_ids", ids),
|
||||
zap.Uint64s("order_ids", orderIDs),
|
||||
)
|
||||
if err := j.db.WithContext(ctx).Model(&model.PaymentOrder{}).
|
||||
Where("id IN ?", ids).
|
||||
Update("last_retry_at", now).Error; err != nil {
|
||||
j.logger.Warn("refund retry bump last_retry_at failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +259,8 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
|
||||
}
|
||||
missing := 0
|
||||
rebuilt := 0
|
||||
var missingOrderIDs []uint64
|
||||
var rebuildFailOrderIDs []uint64
|
||||
for _, row := range rows {
|
||||
var count int64
|
||||
if err := j.db.WithContext(ctx).Model(&model.PaymentOrder{}).
|
||||
@@ -264,18 +277,11 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
|
||||
// 非仲裁孤儿 biz_type 无法从订单状态可靠区分,维持只告警。
|
||||
if row.SettlementStatus == "arbitrated" {
|
||||
if j.payments == nil {
|
||||
j.logger.Warn("auto rebuild arbitration refund skipped, payment repository unavailable",
|
||||
zap.Uint64("order_id", row.ID),
|
||||
zap.Int64("refund_amount_cent", row.RefundAmountCent),
|
||||
)
|
||||
rebuildFailOrderIDs = append(rebuildFailOrderIDs, row.ID)
|
||||
continue
|
||||
}
|
||||
if _, err := j.payments.StartRefund(ctx, row.ID, row.RefundAmountCent, "arbitration_refund", "仲裁退款补偿补建"); err != nil {
|
||||
j.logger.Warn("auto rebuild arbitration refund failed, need manual check",
|
||||
zap.Uint64("order_id", row.ID),
|
||||
zap.Int64("refund_amount_cent", row.RefundAmountCent),
|
||||
zap.Error(err),
|
||||
)
|
||||
rebuildFailOrderIDs = append(rebuildFailOrderIDs, row.ID)
|
||||
} else {
|
||||
rebuilt++
|
||||
j.logger.Info("auto rebuild arbitration refund submitted",
|
||||
@@ -285,13 +291,36 @@ func (j *Job) warnMissingRefundOrders(ctx context.Context, now time.Time) (int,
|
||||
}
|
||||
continue
|
||||
}
|
||||
missingOrderIDs = append(missingOrderIDs, row.ID)
|
||||
}
|
||||
// 汇总告警 + Redis 24h 去重,避免每轮扫描刷屏
|
||||
if len(missingOrderIDs) > 0 && j.shouldWarn(ctx, "missing_payment", 0) {
|
||||
j.logger.Warn("refund order missing payment record, need manual check",
|
||||
zap.Uint64("order_id", row.ID),
|
||||
zap.String("order_no", row.OrderNo),
|
||||
zap.String("refund_status", row.RefundStatus),
|
||||
zap.String("settlement_status", row.SettlementStatus),
|
||||
zap.Int64("refund_amount_cent", row.RefundAmountCent),
|
||||
zap.Int("count", len(missingOrderIDs)),
|
||||
zap.Uint64s("order_ids", missingOrderIDs),
|
||||
)
|
||||
}
|
||||
if len(rebuildFailOrderIDs) > 0 && j.shouldWarn(ctx, "rebuild_fail", 0) {
|
||||
j.logger.Warn("auto rebuild arbitration refund failed, need manual check",
|
||||
zap.Int("count", len(rebuildFailOrderIDs)),
|
||||
zap.Uint64s("order_ids", rebuildFailOrderIDs),
|
||||
)
|
||||
}
|
||||
return missing, rebuilt, nil
|
||||
}
|
||||
|
||||
// shouldWarn 用 Redis 做 24h 节流;无 Redis 时每个 job 周期最多打一次同类汇总(由调用方聚合)。
|
||||
func (j *Job) shouldWarn(ctx context.Context, kind string, id uint64) bool {
|
||||
if j.redis == nil {
|
||||
return true
|
||||
}
|
||||
key := fmt.Sprintf("hfb:job:refundretry:warn:%s", kind)
|
||||
if id > 0 {
|
||||
key = fmt.Sprintf("%s:%d", key, id)
|
||||
}
|
||||
ok, err := j.redis.SetNX(ctx, key, "1", manualWarnThreshold).Result()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -46,6 +46,50 @@ func TestRetryBackoffCapsAtOneHour(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarnMaxRetryRefundsThrottlesByLastRetryAt(t *testing.T) {
|
||||
db := setupRefundRetryTestDB(t)
|
||||
job := New(db, nil, zap.NewNop(), nil)
|
||||
now := time.Date(2026, 7, 11, 12, 0, 0, 0, time.UTC)
|
||||
old := now.Add(-25 * time.Hour)
|
||||
row := model.PaymentOrder{
|
||||
PaymentNo: "PAY202607110001",
|
||||
OrderID: 9,
|
||||
OrderNo: "ORD202607110001",
|
||||
UserID: 2,
|
||||
Provider: "mock",
|
||||
ThirdOrderID: "REF202607110001",
|
||||
BizType: "admin_refund",
|
||||
Status: "failed",
|
||||
RetryCount: maxRetryCount,
|
||||
LastRetryAt: &old,
|
||||
}
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("创建退款单失败: %v", err)
|
||||
}
|
||||
|
||||
job.warnMaxRetryRefunds(t.Context(), now)
|
||||
|
||||
var latest model.PaymentOrder
|
||||
if err := db.First(&latest, row.ID).Error; err != nil {
|
||||
t.Fatalf("查询退款单失败: %v", err)
|
||||
}
|
||||
if latest.LastRetryAt == nil || !latest.LastRetryAt.Equal(now) {
|
||||
t.Fatalf("LastRetryAt 应更新为 now 以节流,got %v", latest.LastRetryAt)
|
||||
}
|
||||
|
||||
// 立即再扫不应匹配(last_retry_at 未超过 24h)
|
||||
var count int64
|
||||
if err := db.Model(&model.PaymentOrder{}).
|
||||
Where("retry_count >= ? AND (last_retry_at IS NULL OR last_retry_at <= ?)",
|
||||
maxRetryCount, now.Add(-manualWarnThreshold)).
|
||||
Count(&count).Error; err != nil {
|
||||
t.Fatalf("count: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("throttled rows count = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkRetryFailedStopsAtMaxCount(t *testing.T) {
|
||||
db := setupRefundRetryTestDB(t)
|
||||
job := New(db, nil, zap.NewNop(), nil)
|
||||
|
||||
Reference in New Issue
Block a user