fix(iot): ретраи отправки SQS до shutdown + таймаут на попытку (🔴4)
- sendWithRetry больше не дропает после 3 попыток (~7с) при почти пустом буфере: ретраит до ctx.Done() (shutdown) или успеха, backoff капится на 8с. - Добавлен sendAttemptTimeout=10с на одну попытку — зависшее соединение SQS не блокирует воркер навсегда.
This commit is contained in:
@@ -27,8 +27,13 @@ const (
|
|||||||
dispatcherQueueSize = 10000
|
dispatcherQueueSize = 10000
|
||||||
// dispatcherWorkers — горутин, отправляющих в SQS.
|
// dispatcherWorkers — горутин, отправляющих в SQS.
|
||||||
dispatcherWorkers = 4
|
dispatcherWorkers = 4
|
||||||
// sendMaxAttempts — попыток SendMessage на одно сообщение.
|
|
||||||
sendMaxAttempts = 3
|
// sendAttemptTimeout — таймаут ОДНОЙ попытки SendMessage. Без него зависшее
|
||||||
|
// соединение (SQS висит, но не отвечает) блокировало бы воркера навсегда и
|
||||||
|
// забило бы весь пул (ревью 🔴4).
|
||||||
|
sendAttemptTimeout = 10 * time.Second
|
||||||
|
// sendMaxBackoff — потолок паузы между попытками (экспоненциальный backoff).
|
||||||
|
sendMaxBackoff = 8 * time.Second
|
||||||
|
|
||||||
// maxSQSBodyBytes — потолок размера envelope для SQS.
|
// maxSQSBodyBytes — потолок размера envelope для SQS.
|
||||||
// SQS (и shared-sqs) режут сообщения >256KB — запас на оверхед.
|
// SQS (и shared-sqs) режут сообщения >256KB — запас на оверхед.
|
||||||
@@ -116,30 +121,34 @@ func (d *sqsDispatcher) worker(ctx context.Context, wid int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendWithRetry — SendMessage с backoff 1с/2с/4с.
|
// sendWithRetry — SendMessage с ретраями до остановки (ctx) и таймаутом на
|
||||||
|
// попытку. НЕ дропаем после фиксированного числа попыток (было 3 ≈ 7с): буфер
|
||||||
|
// на dispatcherQueueSize рассчитан на поглощение временной недоступности SQS,
|
||||||
|
// поэтому ретраим до ctx.Done() (shutdown) или успеха — иначе потеря была бы
|
||||||
|
// безвозвратной (ревью 🔴4).
|
||||||
func (d *sqsDispatcher) sendWithRetry(ctx context.Context, body string) error {
|
func (d *sqsDispatcher) sendWithRetry(ctx context.Context, body string) error {
|
||||||
var lastErr error
|
|
||||||
backoff := time.Second
|
backoff := time.Second
|
||||||
for attempt := 1; attempt <= sendMaxAttempts; attempt++ {
|
for {
|
||||||
_, err := d.client.SendMessage(ctx, &sqs.SendMessageInput{
|
attemptCtx, cancel := context.WithTimeout(ctx, sendAttemptTimeout)
|
||||||
|
_, err := d.client.SendMessage(attemptCtx, &sqs.SendMessageInput{
|
||||||
QueueUrl: aws.String(d.queueURL),
|
QueueUrl: aws.String(d.queueURL),
|
||||||
MessageBody: aws.String(body),
|
MessageBody: aws.String(body),
|
||||||
})
|
})
|
||||||
|
cancel()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
lastErr = err
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return lastErr
|
// shutdown: ctx отменён, ретраить бессмысленно.
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if attempt < sendMaxAttempts {
|
select {
|
||||||
select {
|
case <-ctx.Done():
|
||||||
case <-ctx.Done():
|
return err
|
||||||
return lastErr
|
case <-time.After(backoff):
|
||||||
case <-time.After(backoff):
|
}
|
||||||
}
|
if backoff < sendMaxBackoff {
|
||||||
backoff *= 2
|
backoff *= 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lastErr
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user