Введение в Processor
Это руководство поможет понять, когда и как использовать Processor и какие преимущества он даёт по сравнению с функциями уровня пакета.
Функции пакета vs Processor
CyberGo JSON предлагает два стиля API:
| Аспект | Функции уровня пакета | Processor |
|---|---|---|
| Типичный вызов | json.GetString(data, "name") | p.GetString(data, "name") |
| Способ создания | Не требуется, вызывайте напрямую | p, err := json.New() |
| Конфигурация | Передача cfg ...Config при каждом вызове | Задаётся один раз при создании, затем переиспользуется |
| Кэш | Общий глобальный кэш | Собственный кэш, управляемый и очищаемый |
| Управление ресурсами | Автоматическое (глобальный процессор) | Ручное Close() |
| Система хуков | Не поддерживается | Поддерживается AddHook |
| Предварительный разбор | Не поддерживается | Поддерживается PreParse + GetFromParsed |
| Предкомпиляция путей | Не поддерживается | Поддерживается CompilePath + GetCompiled |
| Сценарии применения | Простые операции, скрипты, редкие вызовы | Частые операции, пользовательская конфигурация, серверная сторона |
Быстрый выбор
- Функции пакета: периодическая работа с JSON, отсутствие желания управлять жизненным циклом, быстрые скрипты
- Processor: нужна пользовательская конфигурация, частые запросы к одним и тем же данным, нужны хуки/аудит
Когда использовать Processor
Сценарий 1: пользовательская конфигурация
Функции уровня пакета используют конфигурацию по умолчанию. Если нужен режим безопасности, пользовательский кодировщик или хуки, используйте Processor:
// Функция пакета — всегда конфигурация по умолчанию
val := json.GetString(data, "name")
// Processor — конфигурация настраивается
cfg := json.SecurityConfig() // режим безопасности
p, err := json.New(cfg)
if err != nil {
panic(err)
}
defer p.Close()
// Все последующие операции используют безопасную конфигурацию
val, err := p.Get(data, "name")Сценарий 2: частые запросы к одним и тем же данным (оптимизация PreParse)
При многократных запросах к одному JSON PreParse выполняет разбор один раз, а последующие запросы переиспользуют результат:
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close()
// Однократный разбор
parsed, err := p.PreParse(largeJSON)
if err != nil {
panic(err)
}
defer parsed.Release() // возврат в пул объектов после использования
// Многократные запросы — переиспользование результата разбора, без повторного парсинга
name, _ := p.GetFromParsed(parsed, "user.name")
email, _ := p.GetFromParsed(parsed, "user.email")
tags, _ := p.GetFromParsed(parsed, "tags")
// Можно получить и сам результат разбора (map[string]any / []any)
data := parsed.Data()
_ = data
// Изменение тоже может опираться на предразобранный результат: SetFromParsed возвращает новый ParsedJSON, исходный не меняется
modified, err := p.SetFromParsed(parsed, "user.age", 31)
if err != nil {
panic(err)
}
newAge, _ := p.GetFromParsed(modified, "user.age")Сравнение производительности
- Функция пакета
GetString: каждый вызов разбирает JSON (кэш есть, но доля попаданий зависит от сценария) PreParse+GetFromParsed: разбор один раз, N запросов выполняют только навигацию, ноль повторных разборов
Сценарий 3: частые запросы по одному пути (оптимизация CompilePath)
PreParse оптимизирует случай «один JSON запрашивается много раз»; если же «один и тот же путь многократно выполняется на большом количестве разных JSON», используйте предкомпиляцию пути CompilePath — разбор и проверка пути выполняются один раз, после чего каждый запрос сразу переходит к навигации:
package main
import (
"fmt"
"github.com/cybergodev/json"
)
func main() {
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close()
// Путь компилируется один раз (разбор + проверка)
compiled, err := p.CompilePath("user.name")
if err != nil {
panic(err)
}
defer compiled.Release() // возврат в пул объектов
// Повторяющиеся запросы в горячем пути: без разбора пути, только навигация
for _, data := range []string{
`{"user":{"name":"Alice"}}`,
`{"user":{"name":"Bob"}}`,
} {
val, err := p.GetCompiled(data, compiled)
if err != nil {
panic(err)
}
fmt.Println(val)
}
// Вывод:
// Alice
// Bob
}Разделение труда двух оптимизаций
| Оптимизация | Устраняемые накладные расходы | Сценарий применения |
|---|---|---|
PreParse + GetFromParsed | Повторный разбор JSON-документа | Один JSON, много разных путей |
CompilePath + GetCompiled | Повторный разбор выражения пути | Один путь на множестве JSON (горячий путь) |
Это независимые направления оптимизации — выбирайте по узкому месту. Обратите внимание: у GetCompiled пока есть только вариант для запросов, Set/Delete предкомпилированные пути пока не поддерживают; изменение на стороне предразбора доступно через SetFromParsed.
Сценарий 4: хуки и аудит
Когда нужны журналирование, мониторинг производительности или проверка входных данных, Processor поддерживает систему хуков:
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close()
// Добавить хук логирования
p.AddHook(json.LoggingHook(slog.Default()))
// Добавить хук замеров времени
p.AddHook(json.TimingHook(&metricsRecorder))
// Хуки срабатывают автоматически для всех операций
result, err := p.Set(data, "user.name", "Alice")Подробнее см. Система хуков Hook.
Сценарий 5: разделение Processor между горутинами
Processor потокобезопасен — правильный паттерн: создать один раз, разделять всей группой, закрыть один раз в конце, а не создавать по процессору на каждый запрос (последнее лишь добавляет накладные расходы создания и умножает стоимость управления ресурсами):
package main
import (
"fmt"
"sync"
"github.com/cybergodev/json"
)
func main() {
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close() // выполнится только после завершения всех горутин
data := `{"user":{"name":"Alice","age":30}}`
var wg sync.WaitGroup
for i := 1; i <= 8; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
name := p.GetString(data, "user.name")
age := p.GetInt(data, "user.age")
fmt.Printf("горутина %d: %s (%d)\n", i, name, age)
}(i)
}
wg.Wait()
stats := p.GetStats()
fmt.Println("Всего операций:", stats.OperationCount)
}
// Вывод (порядок горутин не определён):
// горутина 5: Alice (30)
// горутина 2: Alice (30)
// ...
// Всего операций: 16MaxConcurrency — мягкий предел
По умолчанию MaxConcurrency = 50: когда операций в полёте больше этого значения, новые операции немедленно завершаются ошибкой ErrConcurrencyLimit (без постановки в очередь). В высоконагруженных сервисах увеличьте значение по необходимости или организуйте ограничение частоты и повторные попытки на стороне вызывающего.
Сценарий 6: единая глобальная конфигурация
За функциями уровня пакета стоит глобальный процессор. Когда хочется, чтобы всё приложение — включая старый код, параметры которого уже не переделать, — работало с одной конфигурацией, замените его одним вызовом SetGlobalProcessor: все пакетные вызовы json.Get/json.Marshal и т.д. немедленно переключатся. Полный пример и предостережения — в разделе Глобальный процессор ниже.
Управление жизненным циклом
Processor удерживает ресурсы (кэш, горутины), после использования его обязательно нужно закрыть:
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close() // гарантия освобождения ресурсов
// Использование Processor...
result, err := p.GetString(data, "name")Последствия забытого Close
- Память кэша не освобождается
- Утечка фоновых горутин
- При высокой конкурентности возможно исчерпание ресурсов
Проверка состояния
if p.IsClosed() {
// Processor закрыт и больше не может использоваться
}IsClosed возвращает true в обоих состояниях: полностью закрыт либо закрывается (период ожидания дренирования) или закрытие превысило таймаут. В обоих состояниях новые операции отклоняются с ошибкой, поэтому этой проверки достаточно как единственного критерия «можно ли ещё использовать».
Мониторинг и диагностика
Processor имеет встроенную статистику работы и проверку здоровья — удобно подключать к мониторингу сервисов:
package main
import (
"fmt"
"github.com/cybergodev/json"
)
func main() {
p, err := json.New()
if err != nil {
panic(err)
}
defer p.Close()
_, _ = p.Get(`{"user":{"name":"Alice"}}`, "user.name")
// Статистика работы: число операций, ошибок, доля попаданий в кэш и память
stats := p.GetStats()
fmt.Printf("операции=%d ошибки=%d попадания=%.2f записи кэша=%d\n",
stats.OperationCount, stats.ErrorCount, stats.HitRatio, stats.CacheSize)
// Проверка здоровья: результаты отдельных проверок кэша, памяти и др.
health := p.GetHealthStatus()
fmt.Println("здоров:", health.Healthy)
for name, check := range health.Checks {
fmt.Printf(" %s: %s\n", name, check.Message)
}
// Чтение текущей конфигурации (возвращается копия, изменения не затрагивают Processor)
cfg := p.GetConfig()
fmt.Println("кэш включён:", cfg.EnableCache)
}Версия на уровне пакета
У глобального процессора есть и пакетные точки мониторинга: json.GetStats() и json.GetHealthStatus() — удобны для глобальной диагностики из кода, не держащего ссылку на Processor. Статистика кэша и полное использование ClearCache/WarmupCache описаны в Стратегиях кэширования и предпарсинга.
Глобальный процессор
Функции уровня пакета (Get, Set, Marshal и др.) внутри используют глобальный процессор. Его можно заменить:
// Создание процессора с пользовательской конфигурацией
cfg := json.SecurityConfig()
p, err := json.New(cfg)
if err != nil {
panic(err)
}
// Назначение глобальным процессором
json.SetGlobalProcessor(p)
// Теперь все функции уровня пакета используют безопасную конфигурацию
val := json.GetString(data, "name")
// Очистка при завершении приложения
defer json.ShutdownGlobalProcessor()Детали поведения:
SetGlobalProcessorпотокобезопасен, передачаnil— no-op; при замене старый процессор закрывается автоматическиShutdownGlobalProcessor— полная очистка при выходе: помимо закрытия глобального процессора она закрывает процессоры «с кэшированием по конфигурации» и очищает глобальные кэши путей/кодирования; после этого пакетные функции автоматически создают новый процессор по умолчанию- Пакетные функции с
cfg(например,json.Get(data, path, json.SecurityConfig())) идут через процессоры с кэшированием по конфигурации, минуя глобальный процессор — два механизма работают параллельно и не влияют друг на друга
Сценарии применения
- Единая глобальная политика безопасности
- Глобальное действие пользовательского кодировщика
- Замена конфигурации по умолчанию без передачи Config повсюду
Дерево решений
Нужно работать с JSON?
├── Редкое использование, скрипты и утилиты
│ └── → функции пакета json.GetString / json.Set / json.Marshal
├── Редкое использование, но нужна конфигурация безопасности/кодирования
│ └── → функции пакета с хвостовым cfg: json.Get(data, path, json.SecurityConfig())
├── Частое использование или нужны возможности процессора (хуки и др.)
│ └── → Processor json.New(cfg)
├── Многократные запросы к одному JSON
│ └── → Processor + PreParse
├── Один путь на большом количестве JSON (горячий путь)
│ └── → Processor + CompilePath
├── Параллельная обработка несколькими горутинами
│ └── → один общий Processor (потокобезопасен), не создавайте по одному на запрос
├── Нужны аудит/мониторинг/логирование
│ └── → Processor + AddHook
├── Нужны метрики времени выполнения/проверка здоровья
│ └── → GetStats / GetHealthStatus (методы Processor и функции пакета)
└── Единая глобальная конфигурация
└── → SetGlobalProcessorЧто дальше
- Синтаксис выражений пути — полный синтаксис запросов по путям
- API Processor — полный справочник методов
- Оптимизация производительности — глубокая настройка производительности
- Шпаргалка — быстрый справочник по API