Запускайте проверки ограниченной группой, а печать — отдельно от лимита сетевых работников. Результаты сохраняйте под 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.