技術的發展老是讓人應接不暇,2018年10月,HTTP/3又發佈了。雖然已經有一些中文技術媒體作了報道,但大多數是翻譯的,並且內容大同小異。最近我專門學習了點關於HTTP/3的知識,在這裏隨便寫寫,和你們作個分享。 |
先簡單回顧一下HTTP/2吧。自從1999年HTTP 1.1發佈以後,Web一直在迅猛發展,惋惜HTTP協議一直沒有更新。等不及的Google本身搞了個SPDY(讀音是「speedy」),並依靠Chrome瀏覽器大肆推廣。看到SPDY的效果確實很好(能夠帶來近50%的性能提高),IETF推進制定了HTTP/2。 SPDY和HTTP/2的主要特性展現以下:
現在HTTP/2已經不新鮮了,根據2019年2月對訪問量最大的1000萬個網站的統計,33.5%已經支持HTTP/2。在國內,若是你打開瀏覽器看看調試模式,會發現各大廠已經普遍使用HTTP/2,尤爲是放置css、js、圖片的資源站,HTTP/2基本是標配。這也很好理解,基本什麼都不用作,就能夠直接享受多路複用帶來的好處,何樂而不爲?css
在傳統HTTP中,概念模型很是簡單:下層TCP通信與上層HTTP徹底不搭架,但TTP與TCP的「鏈接」是重合的,TCP傳輸的單位是packet,HTTP則採用request-response的模型。linux
在HTTP/2中,概念模型有所變化,HTTP/2中傳輸的基本單位是幀(frame)。與HTTP 1.1的明文傳輸不一樣的是,HTTP/2的幀是二進制的,同時TCP承載的「邏輯鏈接」叫數據流(stream),全部的狀態流轉、流控、優先級等等特性都是在數據流上實現的。HTTP/2中爲你們所津津樂道的「多路複用」,簡單說就是把數據流分解爲多個幀,多個數據流的幀混合以後以同一個TCP鏈接來發送。web
值得注意的是,HTTP有1.0和1.1的區分,因此寫做HTTP 1.0,HTTP 1.1,但HTTP/2不會有其它小版本,因此不要寫做HTTP 2.0,而應當寫成HTTP/2。瀏覽器
雖然HTTP/2已經帶來了巨大的性能提高,但你們對性能的渴求是沒有止境的。在應用層的許多問題解決以後,下一個優化的重點就是傳輸層了。不管SPDY仍是HTTP/2,傳輸層協議都是TCP,TCP有一些孃胎裏帶來的問題,好比慢啓動,若是擁塞窗口尺寸設置不合理,TCP的性能會急劇降低。關於這個問題,網絡上已經有許多討論,這裏不贅述。緩存
另外一個重要問題是,HTTP/2的多路複用帶來的效果並不如想象的那麼好。雖然HTTP/2中的傳輸鏈接能夠多路複用,但仍然沒法避免隊頭阻塞的狀況出現。由於TCP是須要保證有序的,假如單個TCP鏈接同時承載了四路邏輯鏈接,其中某個邏輯鏈接丟包了,則其它三路都會受影響,都必須從丟包的時刻開始重傳,這無疑是極大的浪費。測試代表,若是丟包率超過2%,那麼HTTP/2甚至不如HTTP 1.1,由於HTTP 1.1中各鏈接物理隔離,不會互相影響。
因此思路天然就是「改掉TCP的這些毛病」。考慮到現實中已經有成千上萬的網絡設備,它們只能識別TCP和UDP,軟件不會進化,若是更新TCP協議固然不可行——雖然2014年12月發佈了TCP的Fast Open,但現實應用中的狀況並不讓人滿意。所以,可用的只有UDP了。對了,還有人考慮過SCTP,但SCTP在隊頭阻塞、TLS、四次握手等方面仍然存在缺陷,尚不能讓人滿意。安全
大概有人聽過QUIC(讀音quick),知道它是基於UDP的HTTP,也知道它依然是Google最早提出來的。確實,上次是Google率先搞出了SPDY,此次Google又率先搞出了QUIC。根據Google本意,QUIC是把傳統的HTTP/TCP/IP協議棧中的TCP換成UDP(固然須要加密),能經過加密的UDP傳輸HTTP/2的幀。網絡
按照Google的說法,這樣的好處不少,好比UDP創建鏈接的延遲會低不少,並且避免了隊頭阻塞。除此以外,Google還提供了一個很是誘人的特性FEC(Forward Error Correction)。簡單說,它想作到的是,一旦有packet丟失,接收方能夠根據以前和以後的packet推斷出丟失packet的數據,這樣就避免了重傳。可是這樣必然要求增長冗餘載荷,或者說,這就是網絡協議中的RAID 5。按照目前看到的資料,其冗餘比例大概是10%,也就是說,每10個pakcet中的冗餘信息,就能夠重構一個packet。curl
儘管Google的QUIC很先進,但QUIC不止這一家,IETF也有QUIC,現在已經更名HTTP/3,因此Google的QUIC有時候也寫做gQUIC。與Google單純在傳輸層動手,應用層基本沿用HTTP/2不一樣,IETF的QUIC是一個混合方案,既包括傳輸層的改動,也包括HTTP層的改動(好比全新的頭部壓縮)。從另外一個角度來講,它更「完整」。雖然理論上QUIC也能夠支持HTTP以外的其它上層應用,但目前這只是計劃而已,初版QUIC並不包含這方面內容。工具
在2018年11月,IETF正式宣佈,HTTP-over-QUIC改名爲HTTP/3。
本文討論的是IETF版本的QUIC,Google已經宣佈,會逐步把IETF的規範歸入本身的協議版本,實現相同的規範。性能
雖然TCP有各類問題,但換成UDP的話,TCP的很多功能也須要原樣移植過來。許多人都知道,TCP是可靠的傳輸協議,而UDP是不可靠的。HTTP/3固然不能不可靠,因此它必須本身實現有序性、錯誤偵測、重傳、擁塞控制、傳輸節奏調整等等特性。
HTTP/2「彷佛」必須用到HTTPS,但規範並不強求HTTP/2使用HTTPS,也就是說,若是你用HTTP來跑HTTP/2,理論上也是能夠成立的,雖然這有點怪異。
與此相反,QUIC的全部鏈接都是加密的,目前採用的是TLS 1.3。若是你仔細觀察上面的圖就會發現,TLS 1.3是「囊括」在QUIC當中的,也就是說,QUIC創建鏈接的握手過程中就同時完成了加密握手。HTTP/3的握手很快,若是兩臺主機之間創建過鏈接,而且緩存了以前的secret,只要客戶端驗證以前緩存的server config就能夠直接創建鏈接,至關於0-RTT,不然也只須要1-RTT就能夠創建鏈接。此外,QUIC還允許在0-RTT的狀況下從一開始就捎帶數據,傳統的「創建鏈接-加密握手-發送數據」現在能夠三步並做一步(這個0-RTT和1-RTT的實現都很是有意思,有興趣的話應當找資料來看看)。
QUIC中雖然也有鏈接(Connection),也基於IP和port創建,但它並不能直接與TCP的鏈接對應,也不一樣於HTTP/2中的鏈接。緣由在於QUIC創建鏈接時既完成了經典的傳輸握手,又完成了加密握手——你能夠認爲這樣分層責任不清晰,但它確實提高了效率。QUIC的鏈接與HTTP/2相似,一個物理鏈接也能夠承載多個邏輯鏈接(也就是數據流)。但與HTTP/2不一樣的是,QUIC中的邏輯鏈接是彼此獨立的,因此避免了TCP上出現的「邏輯鏈接甲丟包致使邏輯鏈接乙、丙、丁都須要重傳」的狀況。
QUIC鏈接的另外一個特色是,每一個鏈接都有一組鏈接ID。鏈接各端能夠設定本身的鏈接ID,同時承認對方的鏈接ID。鏈接ID的做用在於從邏輯上標識當前鏈接。因此,若是用戶的IP發生變化而鏈接ID沒有變化,由於packet包含了網絡ID標識符,因此只須要繼續發送數據包就能夠從新創建鏈接。而目前,若是用戶的設備發生了網絡切換,好比從Wi-Fi切換到4G,則全部鏈接都要斷掉再重連。
若是你詳細研究過HTTP/2,應當知道它的header壓縮採用的HPACK,由於gzip作header壓縮有安全性隱患。HTTP/3一樣提供了header壓縮,但不能直接沿用HPACK。緣由在於,HPACK粗略來講就是一張動態表(dynamic table),由request-response共同維護它,後續header中不會完整重複以前的條目,而是引用以前的條目,TCP的有序性保證了它必定是先修改再讀取,也就是先編碼再解碼。
然而若是使用HPACK,多個流的順序是沒法保證的,這樣會致使解析錯誤。QUIC的解決方案是QPACK,其原理很簡單:全部的header必須經過同一數據流來傳輸,並且必須嚴格有序。可是這樣一來,從HTTP 1.1開始就困擾HTTP已久的隊頭阻塞又出現了。所以,QUIC的長期目標之一就是解決header的隊頭阻塞問題。
作過在線升級的朋友都知道,在線升級中的一個必須成分是提供降級方案,以保證「退化」兼容。不管HTTP/2仍是HTTP/3,都不能逃避這部分的工做量。HTTP/2雖然能夠經過upgrade這個header來升級,但也有更簡單的辦法,就是在TLS握手時協商HTTP的版本,好比Nginx就有NPN(Nginx Protocol Negotiation)擴展,自動協商協議,並已經被IETF採納,成爲ALPN(Application Layer Protocol Negotiation)。
若是web server有這樣的特性,應用服務代碼就沒必要爲兼容HTTP 1.1和HTTP/2作太多工做。可是,若是應用程序中使用了Push等新特性,仍是免不了要作不少事情。在業界,Google、Youtube、Wikipedia等大廠早已經提供了完整服務,HTTP/2和HTTP 1.1無縫切換,客戶端徹底無感知,它們的經驗值得參考。
與HTTP/2不一樣的是,HTTP/3中新定義了一個header,能夠用來指示客戶端「在另外一個端口提供了專用的HTTP/3服務」。
Alt-Svc: h3=":20003"
這個header說明,在本主機的20003端口開啓了HTTP/3的服務。因此,客戶端以後能夠嘗試和這個端口創建純粹的HTTP/3鏈接。
聊了這麼多QUIC的好處以後,再談談它的問題,有些觀點來自我我的,未必足夠準確客觀,歡迎討論。
雖然QUIC有這麼多好處,但能夠看到,相比HTTP/2,它的改動至關大,因此問題也不會少。
基於目前的應用狀況,許多網絡設備對TCP和UDP的策略至關固定,TCP限制在經常使用端口,而UDP大概只開放了53端口(DNS)。因此若是HTTP/3使用UDP,兼容性方面可能會有很多問題須要解決。
不過若是這個問題能夠解決,將來大概不會再出現這種問題,由於HTTP/3的設計思想中已經爲將來作了考慮,應用層和傳輸層職責嚴格隔離,避免再出現「傳輸層一看端口就知道應用層在幹什麼」的狀況。
TCP雖然也是很老的協議,但應用普遍,操做系統內核中有對應的處理代碼,BBR之類的新特性也能夠大幅提高TCP的性能。可是QUIC放棄了TCP,據Google的文檔,偏偏是由於TCP太穩定了,內核裏的代碼更新特別麻煩。此外,由於Linux內核設計之初並無考慮多核的擴展問題,在多核(core)狀況下反而會產生反覆的陷核,形成進程阻塞,嚴重影響性能。
針對上面的問題,很多新的方案都把網絡協議棧放到用戶態處理,QUIC也順應了這種大潮流。惟一的問題是,UDP的協議棧彷佛尚未現成的讓人滿意的方案,或許咱們還得再等待一段時間,才能用上可靠高效的方案。
雖然不少人很想要這個特性,並且HTTP/2也確實加入了它,但關於它的應用仍然存在許多爭議。簡單說,HTTP/2的推送打破了HTTP傳統的「一問一答」的通信模式,在客戶端沒有請求的時候,服務端就能夠給客戶端發送數據,這不免被濫用(想一想隨處可見的那些最喜歡「在商言商」,最不喜歡談「道德」的留言吧),儘管Chrome的開發人員說它們會檢查推送並阻止惡意內容,那也是要在收到推送數據以後進行,這個方案並不完善。
同時,服務端也可能不顧客戶端的緩存,執意重複推送,形成帶寬浪費。HTTP/3保留了推送,但機制有所不一樣。客戶端須要先贊成,服務端才能夠推送。並且,客戶端能夠設置服務端推送上限,超過上限的推送會出錯。儘管如此,推送如何能妥善利用,目前尚未公認明確的答案。
任何技術要想大規模工程應用,靠「標準實現」單打一確定是不行的,由於沒法切片,沒法細粒度調試。在經典的HTTP技術棧中,各層都有對應的工具,好比IP層有ping和traceroute,傳輸層有telnet,應用層有curl,正是有這些工具簇擁着,開發人員才能夠很方便地定位問題所處的層次和細節。HTTP/2雖然有改動,但調試工具也很多,curl能夠支持,還有nghttp二、h2c等工具,初步造成了完整的體系。HTTP/3的改動很大,若是沒有對應的調試支持工具,能夠想象部署和遷移都不會容易。