Я задаю этот вопрос, потому что у меня было очень странное и удивительное впечатление, о котором я собираюсь рассказать.
Я настраиваю сервер HTTP API, чтобы наблюдать за его поведением при наличии задержки между сервером и клиентами , У меня была установка, состоящая из одного сервера и дюжины клиентов, связанных с 10Gbps Ethe rnet fabri c. Я измерил время, необходимое для обслуживания определенных запросов API в 5 сценариях ios. В каждом сценарии я устанавливаю задержку между сервером и клиентами на одно из значений: без задержки (я называю это базовым значением), 25 мс, 50 мс, 250 мс или 400 мс, используя утилиту tc-netem(8)
.
, потому что Я использую сегменты гистограммы для количественной оценки времени обслуживания, я заметил, что все запросы были обработаны менее чем за 50 мс, независимо от сценария, что явно не имеет никакого смысла, как, например, в случае 400 мс, это должно быть по крайней мере около 400 мс (так как я измеряю только длительность с момента обращения запроса к серверу до момента возврата функции HTTP Write()
). Обратите внимание, что размер объектов ответа составляет от 1 до 10 КБ.
Изначально у меня были сомнения, что функция *http.ResponsWriter
Write()
была асинхронной и возвращалась непосредственно перед тем, как данные были получены клиентом. Итак, я решил проверить эту гипотезу, написав игрушечный HTTP-сервер, который обслуживает содержимое файла, сгенерированного с использованием dd(1)
и /dev/urandom
, чтобы иметь возможность перенастроить размер ответа. Вот сервер:
var response []byte
func httpHandler(w http.ResponseWriter, r * http.Request) {
switch r.Method {
case "GET":
now: = time.Now()
w.Write(response)
elapsed: = time.Since(now)
mcs: = float64(elapsed / time.Microsecond)
s: = elapsed.Seconds()
log.Printf("Elapsed time in mcs: %v, sec:%v", mcs, s)
}
}
func main() {
response, _ = ioutil.ReadFile("BigFile")
http.HandleFunc("/hd", httpHandler)
http.ListenAndServe(":8089", nil)
}
Затем я запускаю сервер следующим образом: dd if=/dev/urandom of=BigFile bs=$VARIABLE_SIZE count=1 && ./server
со стороны клиента, я выдаю time curl -X GET $SERVER_IP:8089/hd --output /dev/null
Я пытался с много значений $ VARIABLE_SIZE из диапазона [1Kb, 500Mb] с использованием эмулированной задержки в 400 мс между сервером и каждым из клиентов. Короче говоря, я заметил, что метод Write()
блокируется до тех пор, пока данные не будут отправлены, когда размер ответа будет достаточно большим, чтобы его можно было визуально заметить (порядка десятков мегабайт). Однако, когда размер ответа невелик, сервер не сообщает о ментально-нормальном времени обслуживания по сравнению со значением, сообщаемым клиентом. Для файла размером 10 КБ клиент сообщает о 1,6 секундах, а сервер сообщает об 67 микросекундах (что совершенно не имеет смысла, даже для меня, как человека, я заметил небольшую задержку порядка секунды, о чем сообщает клиент) ,
Чуть дальше go я попытался выяснить, начиная с какого размера ответа сервер возвращает мысленно приемлемое время. После многих испытаний с использованием алгоритма двоичного поиска я обнаружил, что сервер всегда возвращает несколько микросекунд [20us, 600us] для ответов размером менее 86501 байт и возвращает ожидаемое (приемлемое) время для запросов, которые> = 86501 байт (обычно половина времени сообщается клиентом). Например, для ответа 86501 байт клиент сообщил 4 секунды, а сервер - 365 микросекунд. Для 86502 байтов клиент сообщил о 4s, а сервер сообщил о 1.6s. Я повторил этот опыт много раз, используя разные серверы, поведение всегда одинаково. Число 86502 выглядит как волшебный c !!
Этот опыт объясняет странные наблюдения, которые у меня были изначально, потому что все ответы API имели размер менее 10 Кб. Однако это открывает дверь для серьезного вопроса. Что, черт возьми, происходит на земле и как объяснить это поведение?
Я пытался искать ответы, но ничего не нашел. Единственное, о чем я могу думать, это, может быть, это связано с размером сокетов Linux и с тем, делает ли Go системный вызов неблокирующим способом. Однако AFAIK, TCP
пакеты, передающие ответы HTTP
, должны быть подтверждены получателем (клиентом) до того, как отправитель (сервер) сможет вернуться! Нарушение этого предположения (как это выглядит в данном случае) может привести к бедствиям! Может кто-нибудь дать объяснение этому странному поведению?
Технические данные:
Go version: 12
OS: Debian Buster
Arch: x86_64