Skip to content

Повторное использование Processor и кэш

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

Два режима вызова

Функции уровня пакета (однократный вызов)

go
result, err := html.Extract(data)

Внутренне использует sync.Pool для управления временными Processor: при каждом вызове Processor извлекается из пула, а после использования возвращается обратно.

Применимые сценарии: редкие вызовы (например, CLI-утилиты, одноразовые скрипты)

Жизненный цикл:

text
Вызов Extract()
  → Получение Processor из sync.Pool (или создание нового)
  → Выполнение извлечения
  → Возврат в sync.Pool

Экземпляр Processor (режим повторного использования)

go
p, _ := html.New()
defer p.Close()

for _, page := range pages {
    result, _ := p.Extract(page)
}

Создаётся независимый экземпляр Processor с ручным управлением жизненным циклом.

Применимые сценарии: высокочастотные вызовы (например, веб-сервисы, сканеры)

Жизненный цикл:

text
html.New()
  → Создание Processor (кэш, аудит, статистика)
  → Циклический вызов p.Extract() (повторное использование кэша)
  → defer p.Close()

Как выбрать

СценарийРекомендуемый способПричина
CLI-утилиты, однократная обработкаФункции пакетаПросто и прямолинейно, не требует управления
Веб-сервисы, API-бэкендЭкземпляр ProcessorУскорение через кэш, мониторинг статистики
Пакетные сканерыЭкземпляр ProcessorДедупликация через кэш, контролируемые ресурсы
Тестовый кодФункции пакетаБез сохранения состояния, изоляция тестов

Механизм кэширования

Экземпляр Processor имеет встроенный кэш на основе контента. Одинаковый HTML-ввод не обрабатывается повторно.

Настройка кэша

go
cfg := html.DefaultConfig()
cfg.MaxCacheEntries = 2000     // Максимальное количество записей в кэше (0=отключить)
cfg.CacheTTL = time.Hour       // Время жизни записей кэша
cfg.CacheCleanup = 5 * time.Minute // Интервал фоновой очистки
ПараметрПо умолчаниюОписание
MaxCacheEntries2000Максимальная ёмкость кэша, установка в 0 отключает кэш
CacheTTL1 часВремя жизни записей
CacheCleanup5 минутИнтервал фоновой очистки устаревших записей

Генерация ключа кэша

Ключ кэша генерируется на основе контента после конвертации в UTF-8:

  • Для контента менее 64 КБ: хеш вычисляется от полного содержимого
  • Для контента более 64 КБ: используется алгоритм 5-точечной выборки (начало + конец + равномерная выборка)

Одинаковый HTML-контент при повторных вызовах попадает в кэш напрямую, минуя этапы парсинга и извлечения.

Мониторинг попаданий в кэш

go
p, _ := html.New()
defer p.Close()

// Обработка пакета страниц
for _, page := range pages {
    p.Extract(page)
}

// Получение статистики
stats := p.GetStatistics()
fmt.Printf("Всего обработано: %d\n", stats.TotalProcessed)
fmt.Printf("Попаданий в кэш: %d\n", stats.CacheHits)
fmt.Printf("Промахов кэша: %d\n", stats.CacheMisses)

hitRate := float64(stats.CacheHits) / float64(stats.TotalProcessed) * 100
fmt.Printf("Процент попаданий: %.1f%%\n", hitRate)

Рекомендуемые паттерны

Singleton веб-сервиса

В веб-сервисах рекомендуется использовать Singleton Processor:

go
var processor *html.Processor

func init() {
    cfg := html.DefaultConfig()
    cfg.MaxCacheEntries = 5000
    cfg.CacheTTL = 30 * time.Minute
    cfg.ProcessingTimeout = 10 * time.Second

    var err error
    processor, err = html.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
}

func handleExtract(w http.ResponseWriter, r *http.Request) {
    data, _ := io.ReadAll(r.Body)
    result, err := processor.Extract(data)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    json.NewEncoder(w).Encode(result)
}

Пакетная обработка сканера

go
p, _ := html.New(html.DefaultConfig())
defer p.Close()

urls := crawlURLs()
pages := fetchPages(urls) // [][]byte

batch := p.ExtractBatch(pages)
fmt.Printf("Успешно: %d, Неудачно: %d\n", batch.Success, batch.Failed)

Регулярное обслуживание

Долго работающий Processor требует регулярного обслуживания:

go
// Регулярная очистка кэша (предотвращение роста памяти)
go func() {
    ticker := time.NewTicker(10 * time.Minute)
    for range ticker.C {
        p.ClearCache()
    }
}()

// Регулярный сброс статистики (с сохранением кэша)
go func() {
    ticker := time.NewTicker(time.Hour)
    for range ticker.C {
        stats := p.GetStatistics()
        log.Printf("Обработано %d раз, ошибок %d",
            stats.TotalProcessed, stats.ErrorCount)
        p.ResetStatistics()
    }
}()

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

Повторная обработка одного и того же HTML 1000 раз (для справки):

РежимПервая обработкаПопадание в кэш
Функции пакетаБазовыйНет кэша
Processor (без кэша)≈Базовый≈Базовый
Processor (с кэшем)≈Базовый≈1/10 от базового

Условия действия кэша

Кэш работает только на экземпляре Processor. Функции уровня пакета переиспользуют Processor через sync.Pool, но конфигурация пула отключает кэш (MaxCacheEntries = 0) и очищает кэш при возврате, поэтому кэширование недоступно.

Частые заблуждения

ЗаблуждениеПравильный подход
Создание Processor через html.New() при каждом вызовеПовторное использование одного экземпляра
Забыли вызвать p.Close()Используйте defer p.Close()
Ожидание кэша от функций уровня пакетаКэш работает только на экземплярах Processor
Использование Processor после закрытияПроверяйте ошибку ErrProcessorClosed

Следующие шаги