HTTP/3 здесь: применение протокола QUIC в OPPO

ЧАСТЬ

0 0
 гид
В последние годы протокол QUIC вызвал бурный рост в области сетевых коммуникаций, и такие названия протоколов, как QUIC и HTTP/3, можно увидеть повсюду на различных технических веб-сайтах. Так что же такое QUIC? Каковы отношения и различия между HTTP/3 и QUIC? Эта статья начинается с принципа превосходных функций протокола QUIC и знакомит с реализацией и применением протокола QUIC собственной разработки OPPO (протокола OQUIC).
ЧАСТЬ

0 1
 Фон QUIC


С развитием Интернета, особенно с добавлением мобильного Интернета, с одной стороны, сеть становится все более и более переполненной, а с другой стороны, у людей все выше требования к задержке в сети. Однако в Интернете появляется все больше и больше крупных ресурсов, таких как картинки и видео, а аудио и короткие видеоролики становятся все более популярными.У людей повышенные требования к передаче по сети в реальном времени.Они надеются, что страницу можно будет открыть мгновенно и короткое видео идет плавно без задержек. В последние десятилетия специалисты в области сетевой оптимизации посвятили себя исследованиям ускорения передачи данных по сети.Наконец, в 2013 году Google оглушил мир: использование протокола «QUIC» для замены традиционного протокола TCP, который до сих пор очень популярен.

1.1 Почему TCP больше не работает?

1.1.1 Соединение устанавливается очень медленно
Процесс установления соединения TCP должен проходить через 1 RTT (время приема-передачи), процесс установления MPTCP медленнее (всего требуется 3 RTT), а протокол HTTP/2 и предыдущие версии требуют установления TLS после установления TCP. , Даже для рукопожатия TLS требуется 1 ~ 2 RTT.С точки зрения пользователей, которые находятся далеко от клиента и сервера, этот процесс установления соединения уже сводит с ума.


1.1.2 Проблема блокировки начала очереди 
Из-за надежных характеристик передачи TCP данные должны приходить по порядку. TCP использует порядковые номера пакетов (Sequence Number) для обеспечения порядка. Однако в сложном процессе передачи по сети пакет данных, отправленный первым, не обязательно первым достигнет пункта назначения. Если пакет потерян, TCP требует, чтобы окно приема было заблокировано и ждало повторной передачи перед сдвигом, чтобы продолжать получать пакеты с большими порядковыми номерами. блокировка линии. Протокол HTTP/2 использует концепцию потока для решения проблемы блокировки начала строки в протоколе HTTP, но проблема блокировки начала строки в протоколе TCP не может быть решена.
1.1.3 Жесткость протокола
Протоколу TCP уже пятьдесят лет, и он реализован в ядре. Каждый раз, когда протокол TCP обновляется, операционная система нуждается в обновлении, поэтому, даже если TCP имеет лучшие обновления функций, его сложно быстро продвигать.
1.1.4 Жесткость промежуточного ПО
Поскольку протокол TCP использовался слишком долго, промежуточные устройства, такие как брандмауэры и шлюзы NAT, укрепились.Если вы хотите внести серьезные изменения в TCP, промежуточное устройство сначала «не согласится», и следствием этого будет что пользователь обновляет свой локальный TCP.Невозможно получить доступ в Интернет после соглашения.

1.2  Как протокол QUIC решает вышеуказанные проблемы?

1.2.1 Цзяньлянь Экспресс
Протокол QUIC реализован на базе UDP. UDP — это ненадежная передача и не требует установления соединения. Протокол QUIC обеспечивает надежную передачу на прикладном уровне. весь процесс установления соединения QUIC не требует задержки в сети. Процесс реализации 0-RTT подробно описан в главе 2.1.
1.2.2 Протокол пользовательского режима
QUIC — это протокол транспортного уровня, реализованный в пользовательском режиме, поэтому ему нужны только две конечные точки для выполнения сопоставления протоколов, он не требует обновления операционной системы и более прозрачен для промежуточных устройств.
1.2.3 Отсутствие проблемы блокировки заголовка очереди
Протокол QUIC реализован на основе UDP, поэтому он естественным образом решает проблему блокировки заголовка TCP.

1.3 Что такое HTTP/3?

Думаю, у многих возникнут сомнения по поводу этих понятий: почему одни называют это QUIC, другие — HTTP/3, а iQUIC, gQUIC, что это такое?
Еще в 2012 году, когда Google предложила концепцию протокола QUIC, содержание протокола QUIC включало два уровня (транспортный уровень и прикладной уровень), не только функции транспортного уровня, но и функции прикладного уровня. Протокол QUIC в настоящее время также называется HTTP/2 поверх QUIC. Позже организация IETF выяснила: эй, этот протокол хорош, мне нужно его стандартизировать. Поэтому усилиями IETF протокол QUIC был преобразован, а оригинальный QUIC от Google был раздет на двухуровневый протокол.Транспортный уровень называется протоколом QUIC, который отвечает только за функции транспортного уровня; прикладной уровень по-прежнему называется протоколом HTTP, но он был обновлен. Номер версии называется протоколом HTTP/3. Когда IETF стандартизировала функцию транспортного уровня QUIC, она также оптимизировала ее. Оптимизированный протокол QUIC называется iQUIC (i относится к IETF). Естественно, чтобы различать, набор Google перед оптимизацией называется gQUIC (g относится к Google ). Поскольку gQUIC становится все менее популярным (даже Google делает iQUIC), протокол QUIC, реализованный OPPO, — это протокол iQUIC.

ЧАСТЬ

02
 В чем особенности QUIC


2.1 0-РТТ

Процесс рукопожатия 0-RTT в протоколе gQUIC отличается от процесса в протоколе iQUIC. Ключевым моментом gQUIC является SCFG, а ключевым моментом iQUIC — PSK. В этой статье объясняется только процесс iQUIC.
Наш процесс рукопожатия QUIC — это стандартный процесс TLS1.3 с использованием библиотеки BoringSSL (TLS1.3 OpenSSL поддерживает только TCP, а не QUIC, в настоящее время только BoringSSL поддерживает QUIC).
Во-первых, при установлении соединения между клиентом и сервером не всегда достигается 0-RTT. В следующих двух случаях:
1) Клиент и сервер никогда не устанавливали соединение, а рукопожатие QUIC требует полного RTT.
2) После истечения срока действия файла PSK, сохраненного клиентом, рукопожатие QUIC требует полного RTT.
Давайте сначала посмотрим на полный процесс RTT рукопожатия QUIC:


Клиент отправляет приветствие клиенту:
1) Выберите эллиптическую кривую, которая является базовой точкой G эллиптической кривой.
2) Сгенерируйте случайное число в качестве закрытого ключа эллиптической кривой клиента ( Ra ) и сохраните его локально.
3) Рассчитать открытый ключ клиента на основе эллиптической кривой на основе базовой точки G и закрытого ключа: Pa(x, y) = Ra * G(x, y)
Случайный клиент
4) Наборы алгоритмов, поддерживаемые клиентом, эллиптическая кривая, используемая клиентом, режим psk и другая информация.

Сервер возвращает Server Hello:
1) Сгенерируйте случайное число в качестве закрытого ключа эллиптической кривой ( Rb ) сервера и сохраните его локально.
2) Рассчитайте открытый ключ сервера на основе эллиптической кривой на основе базовой точки G и закрытого ключа: Pb(x, y) = Rb * G(x, y)
3) Снова сгенерируйте случайное число для расчета окончательного ключа сеанса.

На данный момент у клиента есть: закрытый ключ клиента, открытый ключ сервера, у сервера: закрытый ключ сервера, открытый ключ клиента;
Клиент вычисляет Sa(x, y) = Ra * Pb(x, y) ;  
Сервер вычисляет  Sb(x, y) = Rb *Pa(x, y ) ;
В соответствии с алгоритмом эллиптической кривой: Sa = Sb = S извлеките вектор x из S в качестве предварительного главного ключа (  pre-master) 
Окончательный сеансовый ключ вычисляется случайным образом на клиенте, случайным образом на сервере и предварительным мастером.
На этом этапе соединение между клиентом и сервером установлено, и клиент может отправлять HTTP-запросы.
После установления соединения 1-RTT сервер также отправит сообщение New Session Ticket. Это сообщение очень важно и является основой 0-RTT.


После рукопожатия 1-RTT клиент и сервер изменились с «незнакомцев» на «друзей», и когда клиент снова получит доступ к бизнесу, 0-RTT QUIC будет «запущен».


Клиент и сервер совместно используют один и тот же PSK (полученный NewSessionTicket при первом рукопожатии), клиент переносит данные («ранние данные») в первом отправленном сообщении и использует сеансовый ключ восстановления PSK для шифрования ранних данных.
На рисунке также видно, что 0-RTT по-прежнему имеет сообщения ClientHello и ServerHello, поэтому 0-RTT не требует рукопожатия, а просто передает данные HTTP-запроса во время рукопожатия.


Одновременно выполняются только три условия, включен режим восстановления сеанса 0RTT, в противном случае — восстановление сеанса 1RTT:
1) После первого полного рукопожатия сервер отправляет новый билет сеанса, и расширение max_early_data_size в билете сеанса указывает, что он готов принять Early_data.
2) При восстановлении сеанса PSK раннее расширение данных настраивается в расширении ClientHello, указывая, что Клиент должен включить режим 0RTT.
3) Сервер передает раннее расширение данных в сообщении EnCrypted Extensions, указывая, что он согласен читать ранние данные.


2.2 Миграция соединения

2.2.1 Знакомство с функциями переноса соединения
Миграция соединения — одна из самых сложных функций в протоколе QUIC.
TCP-соединение однозначно идентифицируется четверкой. При изменении любой из четверок (исходный IP-адрес, исходный порт, целевой IP-адрес, целевой порт) текущее соединение будет разорвано, и его необходимо будет восстановить. Что такое миграция соединения? То есть при изменении любого элемента четверки текущее соединение не будет прервано, а данные могут продолжать передаваться. В жизни наши мобильные телефоны часто переключаются между Wi-Fi и сотовыми данными.Когда мы выходим из дома, мы автоматически переключаемся на сотовые данные.Когда мы возвращаемся домой, мы автоматически переключаемся на Wi-Fi.Когда мы заходим в общественные места, такие как компании, рестораны, и кафе, мы автоматически переключимся на Wi-Fi. Переключились на сотовые данные после ухода. Каждый переключатель приведет к изменению исходного IP-адреса и номера порта, каждый переключатель приведет к отключению текущего соединения, а каждый переключатель вызовет короткий период отсутствия сети, видео зависнет, а веб-страница не загрузится. .. Миграция соединения протокола QUIC Функция очень хорошо решает эту проблему QUIC однозначно идентифицирует соединение на основе идентификатора соединения. При изменении исходного адреса QUIC по-прежнему может гарантировать сохранение соединения и нормальную отправку и получение данных.


Из приведенного выше рисунка видно, что при переносе подключения идентификатор подключения (conn_id) не меняется, хотя исходный IP меняется после переключения сетевого канала (WIFI -> Cellular), сервер QUIC будет приходить на основе идентификатор соединения. Определите, является ли это тем же соединением QUIC.
Стоит отметить, что при переносе соединения существует определенный риск атаки, для предотвращения сторонних атак протокол предусматривает необходимость проверки адреса перед отправкой последующих данных для подтверждения надежности пира. Этот процесс завершается с помощью кадра ПК (вызов пути) и кадра PR (ответ пути).





2.2.2 Сложности реализации миграции соединений
Давайте сначала рассмотрим наиболее распространенную архитектуру QUIC.


Данные клиента проходят через четырехуровневый балансировщик нагрузки, затем через семиуровневый кластер шлюзов и, наконец, попадают на внутренний бизнес-сервер. Мы развертываем клиент QUIC на стороне клиента, а сервер QUIC развертываем в контейнере кластера шлюза уровня 7.
Вопрос первый
Клиент склонен к ситуациям «мертвого ожидания»


Решение: можно получить статус сетевой карты (пользователь должен авторизовать приложение), а также можно использовать IP2 для отправки сообщения об обнаружении, чтобы уведомить сервер о выполнении переноса соединения.
вопрос второй
四层负载均衡器( DPVS )需要将同一条连接上的数据包负载均衡到同一个七层网关容器中。
当连接迁移发生时,需要保证连接不断的同时,也要保证连接的正确性。也就是连接迁移之前:客户端 A 是与 服务端 B 进行通信的,发生连接迁移后,我们需要保证客户端 A 的数据包仍然必须发送到服务端 B 上,不能发送到其他服务端上(这样会导致连接出现错误)。
解决方案:传统意义上,四层负载均衡器一般按照四元组(源 IP、源 PORT、目的 IP、目的 PORT )进行一致性哈希选择后端七层网关,但是连接迁移后,四元组发生变化,所以会哈希到其他的七层网关。要解决这个问题,四层负载均衡器就不能再以四元组进行哈希,而是根据 QUIC 协议的连接 ID 进行选择上游七层网关。
我们使用一个讨巧的方案来解决这个问题:服务端在生成SCID时,将本端的IP和端口号进行编码,作为 SCID 的一部分。
四层负载均衡器需要解析客户端发送的 QUIC 包中的 DCID(服务端的 SCID 在客户端发送的包中是  DCID ) ,这样即可保证四层负载均衡器将连接迁移后的数据包发送到同一个服务端。
问题三
在七层网关是多核的情况下,内核如何将同一个连接的数据交给同一个进程
解决方案:内核传统的做法是,通过四元组进行哈希,找到 socket fd,同一个 fd 对应着唯一的应用层进程。通过 eBPF 修改这一过程,通过连接 ID 进行哈希。


2.3 更优秀的拥塞控制算法

QUIC 协议的拥塞控制算法的优势,主要表现在两个方面:
第一:灵活性
QUIC 协议可以针对连接的每个流进行配置不同的拥塞控制算法,我们知道每个拥塞控制算法都有各自的适应场景,换句话说,不同的业务场景,不同的网络环境用不同的拥塞控制算法更为合适。在传统 TCP 连接里,拥塞控制算法的选择是在内核中进行配置。
linux 上查询当前系统支持的拥塞控制算法:
   
   
   
   
   
sysctl net.ipv4.tcp_available_congestion_control
linux 上查询系统当前正在使用的拥塞控制算法:
   
   
   
   
   
sysctl net.ipv4.tcp_congestion_control
linux 上修改当前系统使用的拥塞控制算法(以bbr算法为例):
   
   
   
   
   
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
可见,一旦配置为某个拥塞控制算法,那么这台服务器上所有的业务所有的连接都只能使用该拥塞控制算法;
而 QUIC 不同,由于实现在应用层,我们可以随时更改拥塞控制算法,也可以对每个连接中不同的流使用不同的拥塞控制算法。
第二:更精确性
TCP 为了保证可靠性,使用了基于字节序号的 Sequence Number 及 Ack 来确认消息的有序到达。
QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 sequence number,并且每个 Packet Number 都严格递增,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。而 TCP 呢,重传 packet 的 sequence number 和原始的 packet 的 Sequence Number 保持不变,也正是由于这个特性,引入了 TCP 重传的歧义问题。


如上图的左流程,TCP 重传的歧义问题,就会导致 RTT 或者过大或者过小,而 RTT 是拥塞控制算法的重要输入参数,在 RTT 不准确的情况下,拥塞控制算法就无法做到精确。
QUIC 由于 Packet Number 严格递增,不会出现重传的歧义问题,拥塞控制算法更为精确。
另外,在普通的 TCP 里面,如果发送方收到三个重复的 ACK 就会触发快速重传,如果太久没收到 ACK 就会触发超时重传,而 QUIC 使用 NACK  ( Negative Acknowledgement ) 可以直接告知发送方哪些包丢了,不用等到超时重传。TCP 有一个 SACK 的选项,也具备 NACK 的功能,QUIC 的 NACK 有一个区别它每次重传的报文序号都是新的。
但是单纯依靠严格递增的 Packet Number 肯定是无法保证数据的顺序性和可靠性。QUIC 又引入了一个 Stream Offset 的概念,即一个 Stream 可以经过多个 Packet 传输,Packet Number 严格递增,没有依赖。但是 Packet 里的 Payload 如果是 Stream 的话,就需要依靠 Stream 的 Offset 来保证应用数据的顺序。

2.4 两级流量控制

所谓流控,就是接收端需要控制发送端的发送速度,以免发送端发送速度过快,导致自己“无能力”接收。TCP 的流量控制是经典的“滑动窗口”算法。但由于 TCP 的队头阻塞问题,一旦有某个 ACK 包丢了,就会导致整条连接上窗口无法向右滑动,很快就会出现“零窗口”的情况,此时数据无法再进行发送。


QUIC 采用两级流量控制,连接和流都进行流量控制。两级流量控制并不是 QUIC 协议的专属,HTTP/2 也同时提供流级和连接级别的流量控制。
流级流量控制就是 QUIC 某一条流接收端告诉另外一端可以接受多少这种流多少数据。针对的是特定流号的流,而不是整个链接。本质来说,就是接收端告诉对端最多能发到偏移到多少的流数据。例如,某一条流 N 告诉接收到可以到偏移200字节的位置。但是发送端已经发送150字节,那么发送端最多就只能发送50字节。等发送端把150字节处理完毕,又重新发送 WINDOW_UPDATE 到400字节的偏移。发送收到后,已经发送150,那么就再能发送250字节。
流级别的流量控制虽然能起到控制流量的效果,但是不够充分,数据发送端可以在同一个连接创建多条流来发送数据,每条流都达到最大值的攻击方法。因此还需要连接级别的流量控制。
连接级别流量控制和流级别的一样,但是消耗字节,最大接收偏移都是穿插所有流,是所有流的最大值或者总和。


2.5 流的多路复用

TCP 的有序性带来了队头阻塞问题,一条连接上,其中一个 packet 丢失了之后,该后续 packet 必须等到丢失的 packet 重传之后,把完整有序的数据交给应用层。这在多并发请求时候带来的影响很大,假设我们在一条连接上需要发送多个请求,Request 1 其中一个 packet 丢了之后,在其被重传成功之前,这条连接后面所有的所有的数据都不能被正常交付给应用层,即使 request 2 的所有数据都能按序到达,也即是请求之间会互相影响。
QUIC 协议由于基于 UDP,不存在这样的问题,假设 Request 1 的某个 packet丢了,它只会影响到 Request 1,不会影响并发的其他请求。



2.6 QUIC/TCP 多路竞速

据业内统计,全球有7%地区的运营商对 UDP 有限速或者禁闭,除了运营商还有很多企业、公共场合也会限制UDP流量甚至禁用 UDP。这对使用 UDP 来承载 QUIC 协议的场景会带来致命的伤害。
对此,OPPO 的 QUIC 协议采用多路竞速的方式使用 TCP 和 QUIC 同时建连。除了在建连进行竞速以外,还可以对网络 QUIC 和 TCP 的传输延时进行实时监控和对比,如果有链路对 UDP 进行了限速,可以动态从 QUIC 切换到 TCP。



2.7 QUIC 的 PING 帧

为了实时探测 QUIC 连接的“活性”,防止使用“坏死”连接导致请求失败,OPPO 的 QUIC 实现了自己的 PING 帧机制。不同于 HTTP/2 的 PING Request 和 PING Response 机制,QUIC 的 PING 帧的接收方只需要应答( ACK )包含该帧的包。
当连接建立后,就开始发送 PING 帧,PING帧间隔时间为5s、10s、15s。如果连续三次 PING 帧都无 ACK,就主动断开连接,并且发送 CC 帧( Connection Close )给服务端,服务端收到 CC 帧后释放连接资源。
PART

03
 QUIC 在 OPPO 的应用


通过弱网实验测试,QUIC 在开启 0-RTT 时,其延迟要比 HTTP 降低20%,比 HTTPS 要降低50%以上。现在主要在海外商店、小布助手等多个业务上线使用 QUIC。


在海外软件商店大规模灰度上线后,接口成功率提升3%~13%,秒开率提升2%~19%。


PART

04
 后记


OPPO 的 QUIC 协议从2020年开始进行研究,然后经过持续两年的迭代优化,现在与Google、华为、腾讯等大厂的 QUIC 协议的性能水平基本一致。经过在海外软件商店等业务长达2年的灰度验证,稳定性得到了严格的验证,目前也有多个业务决定全量接入 QUIC 协议。
我们 QUIC 团队也在继续致力于网络传输优化领域的研究,希望能为 OPPO 的更多业务继续贡献自己的力量。

作者介绍

Longyan LI      OPPO 高级工程师

2020年加入 OPPO 后, 从0-1建设了 OPPO 的 QUIC 协议,并在多个业务落地,取得良好效果。曾供职于华为,拥有多年网络协议栈的开发经验。

About AndesBrain

安第斯智能云
OPPO 安第斯智能云(AndesBrain)是服务个人、家庭与开发者的泛终端智能云,致力于“让终端更智能”。作为 OPPO 三大核心技术之一,安第斯智能云提供端云协同的数据存储与智能计算服务,是万物互融的“数智大脑”。

本文分享自微信公众号 - 安第斯智能云(OPPO_tech)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

工信部:不得为未备案 App 提供网络接入服务 Go 1.21 正式发布 阮一峰发布《TypeScript 教程》 Vim 之父 Bram Moolenaar 因病逝世 某国产电商被提名 Pwnie Awards“最差厂商奖” HarmonyOS NEXT:使用全自研内核 Linus 亲自 review 代码,希望平息关于 Bcachefs 文件系统驱动的“内斗” 字节跳动推出公共 DNS 服务 香橙派新产品 Orange Pi 3B 发布,售价 199 元起 谷歌称 TCP 拥塞控制算法 BBRv3 表现出色,本月提交到 Linux 内核主线
{{o.name}}
{{m.name}}

рекомендация

отmy.oschina.net/u/4273516/blog/8597013