Наблюдение за доступностью веб‑сайтов

Запускайте проверки ограниченной группой, а печать — отдельно от лимита сетевых работников. Результаты сохраняйте под mutex, но сетевой запрос и вывод выполняйте без удержания общей блокировки.

Короткий ответ

Что ответить на собеседовании

Запускайте проверки ограниченной группой, а печать — отдельно от лимита сетевых работников. Результаты сохраняйте под mutex, но сетевой запрос и вывод выполняйте без удержания общей блокировки.

Подробный разбор

Ответ с пояснениями

Условие

Требуется создать механизм наблюдения за набором веб‑ресурсов, который через определённые интервалы посылает сетевые запросы к каждому адресу, фиксирует полученный HTTP‑статус и длительность обращения, а затем выводит агрегированную статистику. Необходимо учитывать ограничение на количество одновременно исполняемых запросов и позволить задавать частоту опросов через параметр RequestFrequency. Реализация должна использовать конкурентный запуск (через errgroup.Group) и обеспечивать безопасный доступ к общей структуре StatusMap, где хранятся результаты проверок.

const (
    // you can limit concurrent net request. It's optional
    MaxGoroutines = 1
    // timeout for net requests
    Timeout = 2 * time.Second
)

type SiteStatus struct {
    Name          string
    StatusCode    int
    TimeOfRequest time.Time
}

type Monitor struct {
    StatusMap        map[string]SiteStatus
    Mtx              *sync.Mutex
    G                errgroup.Group
    Sites            []string
    RequestFrequency time.Duration
}

func NewMonitor(sites []string, requestFrequency time.Duration) *Monitor {
    return &Monitor{
        StatusMap:        make(map[string]SiteStatus),
        Mtx:              &sync.Mutex{},
        Sites:            sites,
        RequestFrequency: requestFrequency,
    }
}

func (m *Monitor) Run(ctx context.Context) error {
    // run printStatuses and checkSite in different goroutines
    return nil
}

func (m *Monitor) checkSite(ctx context.Context, site string) {
    // check site and write result to StatusMap
}

func (m *Monitor) printStatuses(ctx context.Context) error {
    // iterate over map and print results
    return nil
}

Ответ

Запускайте проверки ограниченной группой, а печать — отдельно от лимита сетевых работников. Результаты сохраняйте под mutex, но сетевой запрос и вывод выполняйте без удержания общей блокировки.

Добавьте в SiteStatus поля Duration time.Duration и Error string. Следующие методы используют типы и конструктор из условия; нужны context, fmt, net/http, time и golang.org/x/sync/errgroup:

func (m *Monitor) Run(ctx context.Context) error {
    if m.RequestFrequency <= 0 || MaxGoroutines < 1 {
        return fmt.Errorf("invalid monitor settings")
    }
    m.G.SetLimit(MaxGoroutines)
    group, ctx := errgroup.WithContext(ctx)
    group.Go(func() error { return m.printStatuses(ctx) })
    group.Go(func() error {
        ticker := time.NewTicker(m.RequestFrequency)
        defer ticker.Stop()
        for {
            for _, site := range m.Sites {
                if ctx.Err() != nil { break }
                site := site
                m.G.Go(func() error {
                    m.checkSite(ctx, site)
                    return nil
                })
            }
            if err := m.G.Wait(); err != nil { return err }
            select {
            case <-ctx.Done(): return ctx.Err()
            case <-ticker.C:
            }
        }
    })
    return group.Wait()
}
func (m *Monitor) checkSite(ctx context.Context, site string) {
    ctx, cancel := context.WithTimeout(ctx, Timeout)
    defer cancel()
    started := time.Now()
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, site, nil)
    status := SiteStatus{Name: site, TimeOfRequest: started}
    if err == nil {
        var resp *http.Response
        resp, err = http.DefaultClient.Do(req)
        if err == nil {
            status.StatusCode = resp.StatusCode
            resp.Body.Close()
        }
    }
    status.Duration = time.Since(started)
    if err != nil { status.Error = err.Error() }
    m.Mtx.Lock()
    m.StatusMap[site] = status
    m.Mtx.Unlock()
}
func (m *Monitor) printStatuses(ctx context.Context) error {
    ticker := time.NewTicker(m.RequestFrequency)
    defer ticker.Stop()
    for {
        select {
        case <-ctx.Done(): return ctx.Err()
        case <-ticker.C:
            m.Mtx.Lock()
            snapshot := make([]SiteStatus, 0, len(m.StatusMap))
            for _, status := range m.StatusMap {
                snapshot = append(snapshot, status)
            }
            m.Mtx.Unlock()
            for _, status := range snapshot {
                fmt.Printf("%s: status=%d duration=%s error=%s\n",
                    status.Name, status.StatusCode, status.Duration, status.Error)
            }
        }
    }
}

Run вызывается один раз для экземпляра, Sites и настройки во время работы не меняются. Циклы проверок не перекрываются; если обработка дольше периода, новый цикл ждёт её завершения. Длительность здесь — до получения заголовков/закрытия тела, не скачивание всего сайта. Для недоступного адреса код 0 и текст ошибки. Печатается снимок последнего результата каждого сайта, не исторические процентили. Ошибка одного сайта не отменяет остальные проверки; общая отмена завершает оба цикла.

Источник: pkg.go.dev.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее