// При обновлении заказа, мы должны отправить данные по заказу в несколько сервисов (сторонних) // количество сервисов растёт (может быть тысячи+) // мы написали код, сначала все было хорошо, но со временем наш сервис начал потреблять много ресурсов 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) }() } }
Answer from AI
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, сетевые соединения).
Рекомендации:
- Исправить замыкание переменной
i:
for i := 0; i < len(hosts); i++ {
host := hosts[i] // локальная копия
go func(h string) {
response, err := s.httpClient.Send(ctx, h, order)
// ...
}(host)
}
- Ограничить количество одновременно работающих горутин:
Использовать семафор или пул воркеров, чтобы не создавать слишком много горутин одновременно.
Пример с семафором:
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{}{}
}
- Использовать worker pool:
Создать фиксированное количество воркеров, которые будут читать из канала с хостами и отправлять запросы.
- Обрабатывать ошибки и таймауты:
Для устойчивости важно контролировать таймауты и корректно обрабатывать ошибки.
Таким образом, чтобы избежать роста потребления ресурсов, нужно контролировать параллелизм и правильно работать с переменными в замыканиях.