Перейти к производительности сервера HTTP - PullRequest
0 голосов
/ 25 апреля 2018

Я пишу http-сервер сборщика событий, который был бы под большой нагрузкой.Следовательно, в обработчике http я просто десериализирую событие и затем выполняю фактическую обработку вне цикла http запрос-ответ в процедуре.

При этом я вижу, что если я загружаю сервер со скоростью 400 запросов в секунду, то задержка составляет менее 20 мс для 99 процентилей.Но как только я увеличиваю частоту запросов до 500 в секунду, задержка достигает 800 мс.

Может ли кто-нибудь помочь мне с некоторыми идеями о том, в чем причина, чтобы я мог исследовать больше.

package controller

import (
    "net/http"
    "encoding/json"
    "event-server/service"
    "time"
)

func CollectEvent() http.Handler {
    handleFunc := func(w http.ResponseWriter, r *http.Request) {
        startTime := time.Now()
        stats.Incr("TotalHttpRequests", nil, 1)
        decoder := json.NewDecoder(r.Body)
        var event service.Event
        err := decoder.Decode(&event)
        if err != nil {
            http.Error(w, "Invalid json: " + err.Error(), http.StatusBadRequest)
            return
        }
        go service.Collect(&event)
        w.Write([]byte("Accepted"))
        stats.Timing("HttpResponseDuration", time.Since(startTime), nil, 1)
    }

    return http.HandlerFunc(handleFunc)
}

Я провел тест с 1000 запросов в секунду и профилировал его.Ниже приведены результаты.

(pprof) top20
Showing nodes accounting for 3.97s, 90.85% of 4.37s total
Dropped 89 nodes (cum <= 0.02s)
Showing top 20 nodes out of 162
      flat  flat%   sum%        cum   cum%
     0.72s 16.48% 16.48%      0.72s 16.48%  runtime.mach_semaphore_signal
     0.65s 14.87% 31.35%      0.66s 15.10%  syscall.Syscall
     0.54s 12.36% 43.71%      0.54s 12.36%  runtime.usleep
     0.46s 10.53% 54.23%      0.46s 10.53%  runtime.cgocall
     0.34s  7.78% 62.01%      0.34s  7.78%  runtime.mach_semaphore_wait
     0.33s  7.55% 69.57%      0.33s  7.55%  runtime.kevent
     0.30s  6.86% 76.43%      0.30s  6.86%  syscall.RawSyscall
     0.10s  2.29% 78.72%      0.10s  2.29%          runtime.mach_semaphore_timedwait
     0.07s  1.60% 80.32%      1.25s 28.60%  net.dialSingle
     0.06s  1.37% 81.69%      0.11s  2.52%  runtime.notetsleep
     0.06s  1.37% 83.07%      0.06s  1.37%  runtime.scanobject
     0.06s  1.37% 84.44%      0.06s  1.37%  syscall.Syscall6
     0.05s  1.14% 85.58%      0.05s  1.14%  internal/poll.convertErr
     0.05s  1.14% 86.73%      0.05s  1.14%  runtime.memmove
     0.05s  1.14% 87.87%      0.05s  1.14%  runtime.step
     0.04s  0.92% 88.79%      0.09s  2.06%  runtime.mallocgc
     0.03s  0.69% 89.47%      0.58s 13.27%  net.(*netFD).connect
     0.02s  0.46% 89.93%      0.40s  9.15%  net.sysSocket
     0.02s  0.46% 90.39%      0.03s  0.69%  net/http.(*Transport).getIdleConn
     0.02s  0.46% 90.85%      0.13s  2.97%  runtime.gentraceback
(pprof) top --cum
Showing nodes accounting for 70ms, 1.60% of 4370ms total
Dropped 89 nodes (cum <= 21.85ms)
Showing top 10 nodes out of 162
      flat  flat%   sum%        cum   cum%
         0     0%     0%     1320ms 30.21%  net/http.(*Transport).getConn.func4
         0     0%     0%     1310ms 29.98%  net.(*Dialer).Dial
         0     0%     0%     1310ms 29.98%  net.(*Dialer).Dial-fm
         0     0%     0%     1310ms 29.98%  net.(*Dialer).DialContext
         0     0%     0%     1310ms 29.98%  net/http.(*Transport).dial
         0     0%     0%     1310ms 29.98%  net/http.(*Transport).dialConn
         0     0%     0%     1250ms 28.60%  net.dialSerial
      70ms  1.60%  1.60%     1250ms 28.60%  net.dialSingle
         0     0%  1.60%     1170ms 26.77%  net.dialTCP
         0     0%  1.60%     1170ms 26.77%  net.doDialTCP
(pprof) 

1 Ответ

0 голосов
/ 25 апреля 2018

проблема

Я использую другую программу, потому что я не хочу, чтобы обработка происходила в цикле http запрос-ответ.

Это распространенная ошибка (и, следовательно, ловушка). Строка рассуждений кажется разумной: вы пытаетесь обрабатывать запросы «где-то еще», пытаясь обрабатывать входящие HTTP-запросы как можно быстрее.

Проблема в том, что это "где-то еще" - это еще какой-то код, который запускается одновременно с остальной частью вашего оттока обработки запросов. Следовательно, если этот код работает на медленнее , чем скорость входящих запросов, ваши рабочие программы будут накапливаться, по существу, истощая один или больше ресурсов. Что именно - зависит от фактической обработки: если он связан с процессором, это создаст естественную конкуренцию для процессора между всеми этими GOMAXPROCS аппаратными потоками исполнения; если он связан с сетевым вводом-выводом, он создаст нагрузку на Scheruler времени выполнения Go, который должен разделить доступные кванты исполнения, которые он имеет на руках между всеми этими горутинами, которых хотели казнить; если он связан с дисковым вводом / выводом или другими системными вызовами, у вас будет создание потоков ОС, и так далее, и так далее ...

По сути, вы в очереди рабочие единицы , преобразованные из входящие HTTP-запросы, но очереди не устраняют перегрузку. Они могут быть использованы для поглощения коротких пиков перегрузки, но это работает только тогда, когда такие пики "окружены" периодами нагрузки, по крайней мере, немного ниже максимальной емкости, обеспечиваемой вашим система. То, что вы в очереди, прямо не видно в вашем случае, но это там, и это демонстрируется нажатием вашей системы мимо ее естественного емкость - ваша «очередь» начинает расти бесконечно.

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

Что с этим делать?

К сожалению, почти невозможно дать ваше простое решение. так как мы не работаем с вашим кодом в ваших настройках с вашей рабочей нагрузкой. Тем не менее, вот несколько направлений для изучения.

В самом широком масштабе, попробуйте посмотреть, есть ли у вас легко заметное узкое место в вашей системе, которое вы в настоящее время не видите. Например, если все эти параллельные рабочие программы в конечном итоге общаться с экземпляром RDBM, его дисковый ввод / вывод может довольно легко serialize все те goroutines, которые будут просто ждать своей очереди, чтобы иметь их данные приняты. Узкое место может быть проще - скажем, в каждой рабочей программы вы небрежно выполняете какую-то длительную операцию, удерживая блокировку утверждается всеми этими горутинами; это, очевидно, сериализует их всех.

Следующим шагом будет на самом деле измерение (я имею в виду, написав тест) сколько времени требуется одному работнику, чтобы завершить свою единицу работы. Затем вам нужно измерить, как меняется это число при увеличении фактор параллелизма. После сбора этих данных вы сможете сделать обоснованные прогнозы о том, как реалистично оценит вашу систему умеет обрабатывать запросы.

Следующий шаг - продумать стратегию создания вашей системы. выполнить эти рассчитанные ожидания. Обычно это означает ограничение скорости входящих запросов. Существуют различные подходы для достижения этой цели. Посмотрите на golang.org/x/time/rate для ограничителя скорости на основе времени, но можно начать с более низких технологий подходы, такие как использование буферизованного канала в качестве счетного семафора. Запросы, которые могут превысить ваши возможности, могут быть отклонены (обычно с кодом состояния HTTP 429, см. this ). Вы могли бы также рассмотреть возможность их краткой очереди, но я бы попробовал это только служить вишней на пироге, то есть, когда у вас есть отдых разобрался полностью.

Вопрос о том, что делать с отклоненными запросами, зависит от вашего установка. TКак правило, вы пытаетесь «масштабироваться горизонтально», развертывая более одной службы для обработки ваших запросов и обучения ваших клиентов переключению доступных служб.(Я бы подчеркнул, что это означает несколько независимых сервисов - если все они совместно используют какой-то целевой приемник, который собирает их данные, они могут быть ограничены конечной емкостью этого приемника, и добавление большего количества систем не принесет выгодывам что угодно.)

Позвольте мне повторить, что у общей проблемы нет волшебных решений: если ваша полная система (с помощью этой службы HTTP, которую вы пишете, является лишь ее внешним интерфейсом, шлюзом, частью), способна толькообрабатывать N RPS нагрузки, никакой разброс go processRequest() не заставит его обрабатывать запросы в более высоком темпе.Простое параллелизмное предложение Go - это не серебряная пуля , а пулемет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...