Как избежать исчерпания лимита файловых дескрипторов (File Descriptors) в Go?

Ответ

Исчерпание лимита файловых дескрипторов — частая проблема в высоконагруженных сетевых приложениях. Это происходит, когда программа открывает больше файлов или сетевых соединений, чем разрешено операционной системой.

Вот ключевые стратегии для предотвращения этой проблемы в Go:

  1. Своевременное закрытие ресурсов. Это самое важное правило. Используйте defer для гарантированного закрытия файла или сетевого соединения, даже если в функции произойдет паника.

    file, err := os.Open("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close() // Гарантирует закрытие дескриптора при выходе из функции
    
    // ... работа с файлом
  2. Использование пулов соединений. Для работы с базами данных (database/sql) или HTTP-клиентами не создавайте новое соединение на каждый запрос. Настраивайте и переиспользуйте пулы.

    // Пример для HTTP-клиента
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        100, // Макс. кол-во "простаивающих" соединений
            MaxIdleConnsPerHost: 10,  // Макс. кол-во для одного хоста
            IdleConnTimeout:     30 * time.Second,
        },
    }
  3. Настройка системных лимитов. Если приложение действительно требует большого количества соединений, может потребоваться увеличить лимит на уровне ОС. В Unix-системах это делается командой ulimit -n <новое_значение>.

  4. Мониторинг и отладка. Регулярно проверяйте, сколько файловых дескрипторов использует ваш процесс. В Linux это можно сделать с помощью lsof -p <PID> или посмотрев содержимое /proc/<PID>/fd/.

  5. Управление таймаутами. Устанавливайте таймауты для сетевых операций (чтение, запись, подключение), чтобы избежать "зависших" соединений, которые бесконечно удерживают файловый дескриптор.

  6. Отладка утечек. Если вы подозреваете утечку, можно использовать runtime.SetFinalizer для отслеживания объектов, которые не были должным образом закрыты. Однако это сложный инструмент, который следует применять в основном для отладки.