HTTP/3 の登場: OPPO における QUIC プロトコルのアプリケーション

一部

0 0
 ガイド
近年、ネットワーク通信の分野ではQUICプロトコルが盛り上がりを見せており、各種技術サイトのあちこちでQUICやHTTP/3などのプロトコル名を見かけるようになりました。 では、QUICとは一体何なのでしょうか?HTTP/3 と QUIC の関係と違いは何ですか? この記事では、QUIC プロトコルの優れた機能の原理から始まり、OPPO が自社開発した QUIC プロトコル (OQUIC プロトコル) の実装と応用について紹介します。
一部

0 1
 クイック背景


インターネットの発展、特にモバイル インターネットの追加により、一方ではネットワークがますます混雑し、他方ではネットワーク遅延に対する人々の要求がますます高くなっています。しかし、インターネット上には写真や動画などの大きなリソースが増え、音声や短い動画の人気も高まっており、リアルタイムのネットワーク送信に対する要求が高まっており、ページを瞬時に開けることが望まれています。短いビデオも遅延なくスムーズです。ここ数十年、ネットワーク最適化の分野の専門家たちは、データ ネットワーク伝送を高速化する研究に専念してきましたが、ついに 2013 年、Google は世界に雷電を落としました。つまり、従来の TCP プロトコルに代わる「QUIC」プロトコルの使用です。は今でも非常に人気があります。

1.1 TCP が「機能しなくなった」のはなぜですか?

1.1.1 接続の確立が非常に遅い
TCP の接続確立プロセスは 1 RTT (ラウンド トリップ タイム) を通過する必要があり、MPTCP の確立プロセスは遅くなり (合計 3 RTT が必要)、HTTP/2 以前のバージョンのプロトコルでは TCP 確立後に TLS 確立が必要です。 . TLS ハンドシェイクでも 1 ~ 2 RTT が必要で、クライアントとサーバーから遠く離れたユーザーの観点からすると、この接続確立プロセスはすでに非常に面倒です。


1.1.2 キューの先頭のブロック問題 
TCP の信頼性の高い伝送特性により、データは順番に到着する必要があります。TCP はパケットのシーケンス番号 (シーケンス番号) を使用して順序を保証します。しかし、複雑なネットワーク伝送プロセスでは、最初に送信されたデータ パケットが最初に宛先に到着するとは限りません。パケットが失われた場合、TCP では、より大きなシーケンス番号を持つパケットを受信し続けるために、受信ウィンドウをブロックし、スライディングする前に再送信が到着するまで待機する必要があります。ブロックして待機する必要があるこの動作は、ヘッドオブ-ラインブロック。HTTP/2 プロトコルは、フローの概念を使用して HTTP プロトコルの行頭ブロック問題を解決しますが、TCP プロトコルの行頭ブロック問題は解決できません。
1.1.3 契約の厳格性
TCP プロトコルは 50 年前に誕生し、カーネルに実装されています。TCP プロトコルが更新されるたびにオペレーティング システムをアップグレードする必要があるため、TCP に優れた機能更新があったとしても、すぐに普及させるのは困難です。
1.1.4 ミドルウェアの剛性
TCP プロトコルはあまりにも長い間使用されてきたため、ファイアウォールや NAT ゲートウェイなどの中間デバイスが固定化されています。TCP に大幅な変更を加えたい場合、最初に中間デバイスが「同意しない」ことになり、その結果は次のとおりです。ユーザーがローカル TCP を更新することを要求し、契約後にインターネットにアクセスできなくなります。

1.2  QUIC プロトコルは上記の問題をどのように解決しますか?

1.2.1 建連エクスプレス
QUIC プロトコルは UDP に基づいて実装されています。UDP は信頼性の低い伝送であり、接続を確立する必要はありません。QUIC プロトコルはアプリケーション層で確実な伝送を保証します。QUIC プロトコルには特に顕著な特徴があります: 0-RTT、つまり、 QUIC の接続確立プロセス全体では、ネットワークの遅延は必要ありません。0-RTT の実装プロセスについては、第 2.1 章で詳しく説明されています。
1.2.2 ユーザーモードプロトコル
QUIC はユーザー モードで実装されたトランスポート層プロトコルであるため、プロトコル マッチングの実行には 2 つのエンドポイントのみが必要で、オペレーティング システムの更新は必要なく、中間デバイスに対してより透過的です。
1.2.3 キューヘッドがブロックされていない問題
QUIC プロトコルは UDP に基づいて実装されているため、TCP 行頭ブロックの問題は自然に解決されます。

1.3 HTTP/3 とは何ですか?

これらの概念について多くの人が疑問を抱くと思います。なぜある人は QUIC と呼び、ある人は HTTP/3、iQUIC、gQUIC、これらは何なのか?
Google が QUIC プロトコルの概念を提案した 2012 年の時点では、QUIC プロトコルの内容には 2 つの層(トランスポート層とアプリケーション層)が含まれており、トランスポート層の機能だけでなくアプリケーション層の機能も含まれていました。このときのQUICプロトコルはHTTP/2 over QUICとも呼ばれます。その後、IETF 組織は「このプロトコルは優れているので、標準化する必要がある」と気づきました。したがって、IETF の努力により、QUIC プロトコルが変換され、Google のオリジナルの QUIC は 2 層プロトコルに取り除かれました。トランスポート層は QUIC プロトコルと呼ばれ、トランスポート層の機能のみを担当します。アプリケーション層は依然として HTTP プロトコルと呼ばれていますが、アップグレードされており、HTTP/3 プロトコルと呼ばれるバージョン番号です。IETFはQUICのトランスポート層機能を標準化する際に、最適化も行いました。最適化されたQUICプロトコルをiQUICと呼びます(iはIETFを指します)。当然区別するため、最適化前のGoogleセットをgQUICと呼びます(gはGoogleを指します) )。gQUIC はますます普及しなくなっているため (Google でも iQUIC を行っています)、OPPO が実装する QUIC プロトコルは iQUIC プロトコルです。

一部

02
 QUICの特徴は何ですか


2.1 0-RTT

gQUIC プロトコルの 0-RTT ハンドシェイク プロセスは、iQUIC プロトコルのプロセスとは異なります。gQUICのキーポイントはSCFG、iQUICのキーポイントはPSKです。この記事では iQUIC プロセスのみを説明します。
当社の QUIC ハンドシェイク プロセスは、BoringSSL ライブラリを使用した TLS1.3 標準プロセスです (OpenSSL の TLS1.3 は TCP のみをサポートし、QUIC はサポートしません。現在、QUIC をサポートしているのは BoringSSL のみです)。
まず、クライアントとサーバー間の接続の確立は常に 0-RTT を達成するとは限りません。次の 2 つの場合:
1) クライアントとサーバーは接続を確立したことがなく、QUIC ハンドシェイクには完全な RTT が必要です。
2) クライアントによって保存された PSK ファイルの有効期限が切れた後、QUIC ハンドシェイクには完全な RTT が必要になります。
まず、QUIC ハンドシェイクの完全な RTT プロセスを見てみましょう。


クライアントは Client Hello を送信します。
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 に従って 、S の x ベクトルをプレマスター キー ( プレマスター) として抽出します。
最終的なセッション キーは、クライアント ランダム、サーバー ランダム、およびプリマスターによって計算されます。
この時点で、クライアントとサーバー間の接続が確立され、クライアントは HTTP リクエストを送信できるようになります。
1-RTT 接続が確立された後、サーバーは新しいセッション チケット メッセージも送信します。このメッセージは非常に重要であり、0-RTT の基礎となります。


1-RTT ハンドシェイク後、クライアントとサーバーは「他人」から「友達」になり、クライアントが再びビジネスにアクセスすると、QUIC の 0-RTT が「開始」されます。


クライアントとサーバーは同じ PSK (最初のハンドシェイクで NewSessionTicket によって取得) を共有し、クライアントは送信される最初のメッセージでデータ (「初期データ」) を運び、PSK 回復セッション キーを使用して初期データを暗号化します。
また、この図から、0-RTT にはまだ ClientHello メッセージと ServerHello メッセージが含まれていることがわかります。そのため、0-RTT はハンドシェイクを必要とせず、ハンドシェイク中に HTTP リクエスト データを運ぶだけです。


同時に 3 つの条件のみが満たされると、0RTT セッション リカバリ モードが有効になり、それ以外の場合は 1RTT セッション リカバリになります。
1) 最初の完全なハンドシェイクの後、サーバーは新しいセッション チケットを送信し、セッション チケット内の max_early_data_size 拡張子は、early_data を受け入れる意思があることを示します。
2) PSK セッションの回復では、初期のデータ拡張が ClientHello の拡張で構成され、クライアントが 0RTT モードを有効にする必要があることを示します。
3) サーバーは、初期データの読み取りに同意することを示す、EnCrypted Extensions メッセージで初期データ拡張機能を伝送します。


2.2 接続の移行

2.2.1 コネクションマイグレーションの機能紹介
接続の移行は、QUIC プロトコルの中で最も複雑な機能の 1 つです。
TCP 接続は、4 つの記号によって一意に識別されます。4 つの要素 (送信元 IP、送信元ポート、宛先 IP、宛先ポート) のいずれか 1 つが変更されると、現在の接続は切断され、接続を再確立する必要があります。接続移行とは何ですか? つまり、クアドルプルのいずれかの要素が変更されても、現在の接続は中断されず、データの送信を継続できます。生活の中で、私たちの携帯電話は、Wi-Fi とモバイルデータ通信を頻繁に切り替えます。家を出るときは、自動的にモバイルデータ通信に切り替わります。家に帰ると、自動的に Wi-Fi に切り替わります。会社やレストランなどの公共の場所に入るときは、カフェやカフェでは自動的に Wi-Fi に切り替わり、外出後はモバイルデータ通信に切り替わります。各スイッチによりソース IP とポート番号が変更され、各スイッチにより現在の接続が切断され、各スイッチにより短期間ネットワークが切断され、ビデオがフリーズし、Web ページの読み込みに失敗します。 .. QUIC プロトコルの接続マイグレーション この機能は、この問題をうまく解決し、QUIC は接続 ID に基づいて接続を一意に識別します。送信元アドレスが変更された場合でも、QUIC は接続の存続とデータの通常の送受信を保証できます。


上の図から、接続の移行が発生しても、接続 ID ( conn_id ) は変更されないことがわかります。ネットワーク チャネル (WIFI -> Cellular) の切り替え後にソース IP は変更されますが、QUIC サーバーは、接続 ID。同じ QUIC 接続であるかどうかを判断します。
接続の移行には一定の攻撃のリスクが存在することに注意が必要であり、第三者からの攻撃を防ぐために、プロトコルでは、後続のデータを送信する前にアドレス検証を実行してピアの信頼性を確認する必要があると規定されています。このプロセスは、PC フレーム (Path Challenge) と PR フレーム (Path Response) を通じて完了します。





2.2.2 接続移行の実装の難しさ
まず、最も一般的な QUIC アーキテクチャを見てみましょう


クライアント データは 4 層のロード バランサーを通過し、次に 7 層のゲートウェイ クラスターを通過し、最終的にビジネス バックエンド サーバーに到達します。クライアント側に QUIC クライアントをデプロイし、レイヤー 7 ゲートウェイ クラスター コンテナーに QUIC サーバーをデプロイします。
質問 1
クライアントは「デッドウェイト」状況になりやすい


解決策: ネットワーク カードのステータスを取得でき (ユーザーは APP を認証する必要があります)、IP2 を使用して検出メッセージを送信し、サーバーに接続の移行を実行するように通知することもできます。
質問2
四层负载均衡器( 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