#http #http2 #multiplexing #flow-control
Вопрос:
Раздел 6.9 RFC 7540 описывает механизм управления потоком HTTP/2. Существует окно управления потоком для каждого соединения и другое окно управления потоком для всех потоков в этом соединении. Это позволяет получателю установить начальное окно управления потоком для потока:
Обе конечные точки могут регулировать начальный размер окна для новых потоков, включая значение для
SETTINGS_INITIAL_WINDOW_SIZE
воSETTINGS
фрейме, которое является частью предисловия к соединению.
И способ для приемника увеличить окна управления подключением и потоком:
Полезная нагрузка
WINDOW_UPDATE
кадра составляет один зарезервированный бит плюс 31-разрядное целое число без знака, указывающее количество октетов, которые отправитель может передать в дополнение к существующему окну управления потоком. Допустимый диапазон для увеличения окна управления потоком составляет от 1 до 2^31-1 (2 147 483 647) октетов.[…]
Отправитель, получающий фрейм WINDOW_UPDATE, обновляет соответствующее окно на сумму, указанную в фрейме.
И способ для приемника увеличивать или уменьшать окна управления потоком для всех потоков (но не для соединения) одновременно:
Когда значение
SETTINGS_INITIAL_WINDOW_SIZE
изменяется, получатель ДОЛЖЕН настроить размер всех окон управления потоком, которые он поддерживает, на разницу между новым значением и старым значением.
Но, насколько я могу судить, приемник не может уменьшить окно управления потоком одного потока без изменения начального размера окна.
Это правильно? Если да, то почему бы и нет? Это кажется разумным, если вы мультиплексируете множество долгоживущих потоков по одному соединению. Возможно, у вас есть некоторый бюджет памяти, контролируемый BDP, для общего подключения, распределенный по потокам, и вы настраиваете пропорцию, которую получает каждый поток, в соответствии с его недавним спросом на пропускную способность. Если один из них временно простаивает, вы хотели бы иметь возможность сбросить его окно, чтобы оно было небольшим, чтобы оно не ограничивало бюджет памяти, не влияя на другие потоки и не делая невозможным получение новых потоков.
(Конечно, я понимаю, что существует гонка, и отправитель, возможно, отправил данные до получения декремента. Но окну уже разрешено быть отрицательным из SETTINGS_INITIAL_WINDOW_SIZE
-за описанного выше механизма, так что, похоже, было бы разумно разрешить и здесь отрицательное окно.)
Действительно ли это невозможно сделать, не завися от дальнейшего прогресса отправителя, чтобы не использовать застрявшие байты в окне управления потоком?
Вот более подробная информация о том, почему меня интересует этот вопрос, потому что я осознаю проблему XY.
Я думаю о том, как решить проблему с управлением потоком RPC. У меня есть сервер с ограниченным бюджетом памяти и входящими потоками с разными приоритетами в отношении того, сколько этой памяти им должно быть разрешено потреблять. Я хочу реализовать что-то вроде взвешенной максимальной минимальной справедливости для них, настроив их окна управления потоком таким образом, чтобы они в сумме не превышали мой бюджет памяти, но когда мы не ограничены памятью, мы получаем максимальную пропускную способность.
По соображениям эффективности было бы желательно мультиплексировать потоки с разными приоритетами в одном соединении. Но затем, по мере изменения требований или появления других подключений, нам нужно иметь возможность регулировать окна управления потоком вниз, чтобы они по-прежнему составляли не более бюджета. Когда поток B появляется или получает более высокий приоритет, но поток A находится в большом бюджете управления потоком, нам нужно уменьшить окно A и увеличить B.
Даже без мультиплексирования, эта же проблема касается и соединения уровня: насколько я могу судить, нет никакого способа, чтобы настроить подключение, управление потоком окно вниз, не меняя первоначальный размер окна. Конечно, он будет скорректирован в сторону понижения, как клиент отправляет данные, но я не хочу зависеть от прогресса от клиента, так что может принимать сколь угодно долго.
Возможно, есть лучший способ достичь этого!
Ответ №1:
Сервер, на котором имеется N потоков, некоторые из которых простаивают, а некоторые активно загружают данные клиенту, обычно перераспределяет окно подключения для активных потоков.
Например, предположим, что вы смотрите фильм и одновременно загружаете большой файл с одного и того же сервера.
Окно подключения равно 100, и у каждого потока тоже есть окно 100 (очевидно, что в случае многих потоков сумма всех окон потока будет ограничена окном подключения, но если есть только один поток, он может быть максимальным).
Теперь, когда вы смотрите и загружаете, каждый поток получает 50.
Если вы приостанавливаете фильм, и сервер знает об этом (т. Е. он не исчерпывает окно потока фильмов), то теперь сервер должен обслуживать только один поток с окном подключения 100 и одним потоком (загрузочным), который также имеет окно 100, поэтому все окно перераспределяется в активный поток.
Проблемы возникают только в том случае, если клиент не сообщает серверу, что фильм был приостановлен. В этом случае сервер будет продолжать отправлять видеоданные до тех пор, пока окно видеопотока не будет исчерпано (или почти исчерпано), а клиент не подтвердит эти данные, поскольку они приостановлены. В этот момент сервер замечает, что данные не подтверждаются одним потоком, и прекращает отправку данных в него, но, конечно, часть окна подключения берется, уменьшая окно активного потока загрузки.
С точки зрения сервера, у него отличное соединение, при котором один поток (загружаемый) прекрасно работает на максимальной скорости, но другой поток прерывается и исчерпывает свое окно, что приводит к замедлению другого потока (возможно, к остановке), даже если это одно и то же соединение!
Очевидно, что это не может быть проблемой подключения/связи, потому что один поток (загружаемый) отлично работает на максимальной скорости. Поэтому это проблема приложения.
Реализация HTTP/2 на сервере не знает, что один из потоков-это фильм, который можно приостановить-это приложение, которое должно сообщить об этом серверу и сохранить окно подключения как можно большим.
Введение нового фрейма HTTP/2 для «приостановки» загрузки (или изменение семантики существующих фреймов для размещения команды «пауза») значительно усложнило бы протокол для функции, которая на 100% управляется приложением-это приложение должно инициировать отправку команды «пауза», но в этот момент оно может отправить свое собственное сообщение «пауза» на сервер без усложнения спецификации HTTP/2.
Это интересный случай, когда HTTP/1.1 и HTTP/2 ведут себя совершенно по-разному и требуют, чтобы другой код работал аналогичным образом.
С HTTP/1.1 у вас было бы одно соединение для фильма и одно для загрузки, они были бы независимыми, и клиентскому приложению не нужно было бы сообщать серверу, что фильм был приостановлен-оно могло бы просто прекратить чтение из подключения к фильму до тех пор, пока оно не станет перегруженным по протоколу TCP, не влияя на соединение для загрузки-при условии, что сервер не блокируется, чтобы избежать проблем с масштабируемостью.
Комментарии:
1. Спасибо. Я понимаю этот пример, но на самом деле он противоположен моей ситуации. В примере потоковой передачи/загрузки я говорю о случае, когда клиент хочет ограничить использование своей памяти, а у сервера возникают проблемы с обслуживанием фильма. (Возможно, хранилище, из которого он извлекается, оспаривается, но загрузка-нет.) Клиент является получателем здесь, и это тот, кто должен иметь возможность перераспределять байты в окне для потока фильмов для загрузки; в противном случае загрузка идет с половинной скоростью. Но, похоже, этого не может произойти, пока сервер не отправит больше байтов фильма.
2. Я не уверен, что понимаю ваше дело. Если у сервера возникает сбой при отправке байтов видео, клиент должен был бы подтвердить все байты видео, поэтому окно подключения полностью открыто (100) и доступно для единственного активного потока, оставшегося, потока загрузки файла, который затем можно загрузить с максимальной скоростью.
3. Вы правы насчет окна подключения, но я говорю об окнах для каждого потока. Как я уже упоминал в вопросе, у меня есть фиксированный бюджет памяти для подключения, и я хочу разделить его на различные потоки. Если в настоящее время у фильма 75% бюджета, а затем он простаивает, эти 75% бюджета задерживаются до тех пор, пока не будет доставлено больше байтов. (Или нам придется увеличить бюджет в 1,75 раза.) Теперь я добавил дополнительную информацию в нижней части вопроса о своем варианте использования.
4. Извините, мне все еще не ясно, в чем заключается ваше дело. Вы, кажется, ссылаетесь на клиента, но в редактировании вашего вопроса вы говорите, что у вас есть ограничение памяти сервера, но также говорите, что у вас его нет. Я, честно говоря, не вижу вашей проблемы-пока клиент подтверждает данные, он сохраняет окно подключения (и потока) полностью открытым, и байты будут отправляться только активным потокам. Если вы хотите изменить приоритеты и веса, вы можете сделать это с
PRIORITY
помощью фреймов, а не с помощью управления потоком. В двух словах, я не понимаю, почему вы настаиваете на том, чтобы уменьшить размер окна потока?5. Потоки HTTP2 являются двунаправленными; как клиент, так и сервер могут быть отправителями и подлежат управлению потоком. См. раздел 5.2.1 RFC. В моем первоначальном вопросе я ссылался только на «отправителя» и «получателя» по этой причине, но в обновлении я подробно рассказываю о своем конкретном случае, когда получателем является сервер. В вашем примере клиент является получателем, поэтому вопрос перевернут.