Sobes.tech
Principal
22

// При обновлении заказа, мы должны отправить данные по заказу в несколько сервисов (сторонних) // количество сервисов растёт (может быть тысячи+) // мы написали код, сначала все было хорошо, но со временем наш сервис начал потреблять много ресурсов func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) { for i := 0; i < len(hosts); i++ { go func() { // Представим, что это долгий сетевой вызов response, err := s.httpClient.Send(ctx, hosts[i], order) if err != nil { s.logger.Error(ctx, "failed to send", err) return } s.logger.Info(ctx, "success", response) }() } }

Компании, где спрашивали
101ХР101ХР

Ответ от нейросети

sobes.tech AI

В представленном коде есть проблема с использованием переменной цикла i внутри горутины:

for i := 0; i < len(hosts); i++ {
    go func() {
        response, err := s.httpClient.Send(ctx, hosts[i], order)
        // ...
    }()
}

Здесь i используется внутри анонимной функции, но к моменту запуска горутины значение i может измениться, что приведёт к ошибкам (например, выход за пределы массива).

Кроме того, при большом количестве сервисов (тысячи и более) запуск тысячи горутин одновременно может привести к чрезмерному потреблению ресурсов (память, CPU, сетевые соединения).

Рекомендации:

  1. Исправить замыкание переменной i:
for i := 0; i < len(hosts); i++ {
    host := hosts[i] // локальная копия
    go func(h string) {
        response, err := s.httpClient.Send(ctx, h, order)
        // ...
    }(host)
}
  1. Ограничить количество одновременно работающих горутин:

Использовать семафор или пул воркеров, чтобы не создавать слишком много горутин одновременно.

Пример с семафором:

sem := make(chan struct{}, 100) // максимум 100 одновременных горутин

for _, host := range hosts {
    sem <- struct{}{} // захват семафора
    go func(h string) {
        defer func() { <-sem }() // освобождение семафора
        response, err := s.httpClient.Send(ctx, h, order)
        if err != nil {
            s.logger.Error(ctx, "failed to send", err)
            return
        }
        s.logger.Info(ctx, "success", response)
    }(host)
}

// дождаться завершения всех горутин
for i := 0; i < cap(sem); i++ {
    sem <- struct{}{}
}
  1. Использовать worker pool:

Создать фиксированное количество воркеров, которые будут читать из канала с хостами и отправлять запросы.

  1. Обрабатывать ошибки и таймауты:

Для устойчивости важно контролировать таймауты и корректно обрабатывать ошибки.

Таким образом, чтобы избежать роста потребления ресурсов, нужно контролировать параллелизм и правильно работать с переменными в замыканиях.