← Назад к вопросам

Как связать несколько запросов?

1.7 Middle🔥 252 комментариев
#Основы Go

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Связь нескольких запросов в Go

В Go связь нескольких запросов обычно относится к организации последовательных или параллельных вызовов API, запросов к базам данных или других сетевых операций. Рассмотрим основные подходы.

Последовательное выполнение запросов

Последовательное выполнение — простейший способ, когда каждый следующий запрос зависит от результата предыдущего.

func sequentialRequests() error {
    // Первый запрос
    user, err := getUser(123)
    if err != nil {
        return fmt.Errorf("ошибка получения пользователя: %w", err)
    }

    // Второй запрос зависит от первого
    orders, err := getOrders(user.ID)
    if err != nil {
        return fmt.Errorf("ошибка получения заказов: %w", err)
    }

    // Третий запрос
    for _, order := range orders {
        err := processOrder(order)
        if err != nil {
            return fmt.Errorf("ошибка обработки заказа %d: %w", order.ID, err)
        }
    }
    return nil
}

Этот подход прост, но имеет низкую производительность при независимых запросах.

Параллельное выполнение с goroutines

Для независимых запросов используют goroutines и каналы (channels) для параллелизации.

func parallelRequests() error {
    var wg sync.WaitGroup
    errors := make(chan error, 3)

    // Запускаем три независимых запроса параллельно
    wg.Add(1)
    go func() {
        defer wg.Done()
        if err := fetchDataFromAPI1(); err != nil {
            errors <- fmt.Errorf("API1: %w", err)
        }
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        if err := fetchDataFromAPI2(); err != nil {
            errors <- fmt.Errorf("API2: %w", err)
        }
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        if err := fetchDataFromAPI3(); err != nil {
            errors <- fmt.Errorf("API3: %w", err)
        }
    }()

    wg.Wait()
    close(errors)

    // Обработка ошибок
    var combinedErr error
    for err := range errors {
        if combinedErr == nil {
            combinedErr = err
        } else {
            combinedErr = fmt.Errorf("%v; %v", combinedErr, err)
        }
    }
    return combinedErr
}

Контекст (Context) для управления запросами

Context — ключевой механизм для передачи метаданных, управления временем жизни и отмены запросов.

func linkedRequestsWithContext(ctx context.Context) error {
    // Первый запрос с контекстом
    ctx1, cancel1 := context.WithTimeout(ctx, 5*time.Second)
    defer cancel1()
    
    result1, err := expensiveOperation(ctx1)
    if err != nil {
        return err
    }

    // Второй запрос использует тот же родительский контекст
    ctx2, cancel2 := context.WithTimeout(ctx, 3*time.Second)
    defer cancel2()
    
    // Передача данных между запросами
    result2, err := anotherOperation(ctx2, result1.Data)
    if err != nil {
        return err
    }

    return nil
}

Паттерны для сложных связей

1. Шина данных (Data Pipeline)

Создание каналов для передачи данных между этапами обработки.

func pipelineProcessing() error {
    // Каналы для передачи данных
    usersChan := make(chan User, 10)
    ordersChan := make(chan Order, 10)
    resultsChan := make(chan Result, 10)

    // Стадия 1: получение пользователей
    go func() {
        users, _ := getAllUsers()
        for _, user := range users {
            usersChan <- user
        }
        close(usersChan)
    }()

    // Стадия 2: получение заказов для каждого пользователя
    go func() {
        for user := range usersChan {
            orders, _ := getUserOrders(user.ID)
            for _, order := range orders {
                ordersChan <- order
            }
        }
        close(ordersChan)
    }()

    // Стадия 3: обработка заказов
    go func() {
        for order := range ordersChan {
            result := processOrder(order)
            resultsChan <- result
        }
        close(resultsChan)
    }()

    // Сбор результатов
    for result := range resultsChan {
        fmt.Printf("Обработан заказ: %d\n", result.OrderID)
    }
    return nil
}

2. Обработка с зависимостями через WaitGroup и каналы

func dependentParallelRequests() {
    var wg sync.WaitGroup
    stage1Results := make(chan Result, 5)

    // Первая стадия
    wg.Add(3)
    for i := 0; i < 3; i++ {
        go func(id int) {
            defer wg.Done()
            res := stage1Operation(id)
            stage1Results <- res
        }(i)
    }

    // Запускаем goroutine для закрытия канала после завершения
    go func() {
        wg.Wait()
        close(stage1Results)
    }()

    // Вторая стадия обрабатывает результаты первой
    var wg2 sync.WaitGroup
    for res := range stage1Results {
        wg2.Add(1)
        go func(r Result) {
            defer wg2.Done()
            stage2Operation(r)
        }(res)
    }
    wg2.Wait()
}

3. Использование errgroup для упрощения

Пакет golang.org/x/sync/errgroup предоставляет удобный способ группировки goroutines с обработкой ошибок.

import "golang.org/x/sync/errgroup"

func errgroupExample(ctx context.Context) error {
    g, ctx := errgroup.WithContext(ctx)

    // Запускаем несколько параллельных операций
    g.Go(func() error {
        return fetchResource(ctx, "resource1")
    })
    
    g.Go(func() error {
        return fetchResource(ctx, "resource2")
    })
    
    g.Go(func() error {
        return fetchResource(ctx, "resource3")
    })

    // Ожидаем завершения всех, возвращаем первую ошибку
    return g.Wait()
}

Практические рекомендации

  • Для независимых запросов всегда используйте параллельное выполнение через goroutines
  • Для зависимых запросов создавайте pipeline через каналы или последовательное выполнение
  • Всегда передавайте Context для управления отменой и таймаутами
  • Обрабатывайте ошибки на каждом уровне, используя многоканальные подходы или errgroup
  • При больших объемах контролируйте количество одновременных запросов через семафоры или пулы воркеров
// Семафор для ограничения параллельных запросов
func limitedParallelRequests(limit int) error {
    sem := make(chan struct{}, limit)
    var wg sync.WaitGroup
    errors := make(chan error, 10)

    for i :=- 0; i < 10; i++ {
        wg.Add(1)
        go func(taskID int) {
            sem <- struct{}{}        // Занимаем семафор
            defer func() {
                <-sem                // Освобождаем семафор
                wg.Done()
            }()
            
            if err := performRequest(taskID); err != nil {
                errors <- err
            }
        }(i)
    }

    wg.Wait()
    close(errors)
    
    // Агрегирование ошибок
    return aggregateErrors(errors)
}

Выбор метода связывания запросов зависит от конкретной задачи: зависимость данных, требования к производительности, необходимость отмены операций. Сочетание goroutines, каналов, context и WaitGroup дает полный контроль над потоком выполнения в Go.

Как связать несколько запросов? | PrepBro