HTTP/1.1, HTTP/2 և HTTP/3
Multiplexing, header-ների սեղմում, QUIC, և ինչ են դրանք նշանակում արագության համար։
30 վայրկյանում
HTTP-ի իմաստը տասնամյակներ շարունակ չի փոխվել, բայց փոխվել է հաղորդագրությունների փոխանցման եղանակը։ HTTP/1.1-ը մեկ կապով միաժամանակ մեկ հարցում է ուղարկում, HTTP/2-ը մեկ TCP կապով շատ հարցումներ է ուղարկում զուգահեռ, իսկ HTTP/3-ը նույնն անում է QUIC-ով UDP-ի վրա, որպեսզի խուսափի կանգերից, երբ փաթեթներ են կորչում։ Նոր տարբերակները միացնելը սովորաբար կոնֆիգի մի քանի տող է և էջերն ավելի արագ է բեռնում, հատկապես մոբայլում։
Ինչու եմ օգտագործում
- Իմ աշխատեցրած կայքերում միացնում եմ HTTP/2-ը և HTTP/3-ը, որովհետև դա էժան արագություն է յուրաքանչյուր այցելուի համար։
- Յուրաքանչյուր տարբերակի աշխատանքն իմանալը ցույց է տալիս, թե performance-ի որ հին հնարքներից հրաժարվել, օրինակ՝ domain sharding-ից և հսկայական bundle-ներից։
- Անկայուն ցանցերում գտնվող մոբայլ օգտատերերն ամենաշատն են շահում HTTP/3-ից, իսկ իմ պրոդուկտներից շատերն օգտագործվում են հեռախոսներից։
- Դա օգնում է հասկանալ real-time ֆունկցիաներն ու երկարակյաց կապերը nginx-ի և CDN-ի հետևում։
Ինչպես է աշխատում
01HTTP/1.1՝ մեկ հարցում միաժամանակ
HTTP/1.1-ը (1997, ճշգրտված RFC 9112-ում) տեքստային է։ Keep-alive-ը թույլ է տալիս կապը վերաօգտագործել, բայց յուրաքանչյուր կապում հարցումներին պատասխանվում է խիստ մեկը մյուսի հետևից․ եթե մեկ պատասխանը դանդաղ է, դրա հետևում ամեն ինչ սպասում է։ Սա head-of-line blocking է HTTP մակարդակում։ Բրաուզերները դա շրջանցում են՝ յուրաքանչյուր host-ի համար բացելով մոտ վեց կապ, իսկ ծրագրավորողներն օգտագործում էին հնարքներ՝ domain sharding մի քանի hostname-ների միջև, image sprite-ներ և ամբողջ JavaScript-ի միավորում մեկ ֆայլում։ Header-ներն ուղարկվում են պարզ տեքստով յուրաքանչյուր հարցման հետ, ուստի cookie-ներն ու user agent-ները հազարավոր անգամներ են կրկնվում։ Անալոգիա՝ մեկ դրամարկղ, որտեղ յուրաքանչյուր գնորդ պետք է ավարտի, մինչև հաջորդը սկսի, ուստի խանութները բացում են վեց դրամարկղ։
02HTTP/2՝ multiplexing և HPACK
HTTP/2-ը (2015, այժմ RFC 9113) պահպանում է նույն մեթոդները, header-ները և status code-երը, բայց wire format-ը փոխում է բինար frame-երի։ Նրա գլխավոր հնարավորությունը multiplexing-ն է․ շատ հարցումներ և պատասխաններ միաժամանակ անցնում են մեկ TCP կապով՝ որպես անկախ stream-ներ, frame առ frame խառնված։ Մեկ դանդաղ պատասխանն այլևս չի արգելափակում մյուսներին HTTP մակարդակում։ HPACK-ը սեղմում է header-ները՝ օգտագործելով նախկինում ուղարկված արժեքների աղյուսակ, այնպես որ կրկնվող cookie-ներն ու header-ները արժեն մի քանի բայթ։ Prioritization-ը թույլ է տալիս բրաուզերին ազդանշել, թե ինչն է ամենակարևորը։ Server push-ը գոյություն ուներ, բայց հեռացվեց բրաուզերներից, որովհետև հազվադեպ էր օգնում․ 103 Early Hints-ը preload-ի հետ փոխարինում է այն։ Գործնականում բրաուզերները HTTP/2 օգտագործում են միայն TLS-ի վրա, որը համաձայնեցվում է ALPN-ի միջոցով handshake-ի ժամանակ։
- Մեկ կապ յուրաքանչյուր origin-ի համար՝ վեցի փոխարեն․ ավելի քիչ handshake-եր։
- Domain sharding-ն այժմ վնասում է․ այն ստիպում է լրացուցիչ կապեր բացել։
- Շատ փոքր ֆայլերը նորմալ են․ bundle-ները բաժանեք ըստ route-ի՝ մեկ հսկա ֆայլի փոխարեն։
03Մնացած խնդիրը՝ TCP head-of-line blocking
HTTP/2-ը շտկեց արգելափակումը HTTP շերտում, բայց ոչ դրա տակ։ TCP-ն երաշխավորում է, որ բայթերը հասնում են հերթականությամբ։ Եթե մեկ փաթեթ կորչում է, TCP-ն դրանից հետո եկած ամեն ինչ պահում է, մինչև կորած փաթեթը վերաուղարկվի, նույնիսկ այլ stream-երին պատկանող տվյալները։ Մաքուր գրասենյակային ցանցում դա հազվադեպ է։ 1–2% փաթեթների կորուստ ունեցող մոբայլ ցանցերում մեկ կորած փաթեթը կանգնեցնում է այդ կապի բոլոր պատկերները, սկրիպտները և API կանչերը։ Վատ պայմաններում մեկ կապով HTTP/2-ը կարող է նույնիսկ ավելի դանդաղ լինել, քան վեց կապով HTTP/1.1-ը։ TCP-ն նաև ապրում է օպերացիոն համակարգի kernel-ում, ուստի այն բարելավելը տարիներ է պահանջում OS-ի թարմացումների համար։ Ահա թե ինչու էր պետք նոր տրանսպորտ։
04HTTP/3՝ QUIC UDP-ի վրա
HTTP/3-ն (RFC 9114, 2022) աշխատում է QUIC-ի (RFC 9000) վրա՝ UDP-ի վրա կառուցված տրանսպորտ։ QUIC-ն ինքն է իրականացնում հուսալիությունը, congestion control-ը և stream-երը, և յուրաքանչյուր stream անկախ է․ կորած փաթեթը հետաձգում է միայն իր stream-ը։ TLS 1.3-ը ներկառուցված է QUIC-ի մեջ, ուստի նոր կապին պետք է մեկ round trip՝ TCP-ի և TLS-ի երկու կամ երեքի փոխարեն, իսկ վերսկսված կապերը կարող են տվյալներ ուղարկել անմիջապես (0-RTT, որը պետք է սահմանափակել safe, idempotent հարցումներով, որովհետև այն կարող է replay արվել)։ Կապերը նույնականացվում են connection ID-ով, ոչ թե IP-ով և պորտով, այնպես որ Wi-Fi-ից մոբայլ ինտերնետի անցնող հեռախոսը պահպանում է իր կապը։ Header-ներն օգտագործում են QPACK՝ HPACK-ի տարբերակ։ Բրաուզերները HTTP/3-ը հայտնաբերում են Alt-Svc header-ի կամ HTTPS DNS գրառման միջոցով և վերադառնում HTTP/2-ի, եթե UDP 443-ը արգելափակված է։
05Միացում nginx-ում և Cloudflare-ում
nginx 1.25.1 կամ ավելի նոր տարբերակում HTTP/2-ը միացվում է http2 դիրեկտիվով (հին listen ... http2 պարամետրը deprecated է), իսկ HTTP/3-ը՝ նույն պորտի վրա quic listener-ով, որը կառուցված է QUIC մոդուլով, ինչը ներառված է ընթացիկ պաշտոնական փաթեթներում։ HTTP/3-ը հայտարարեք Alt-Svc header-ով և firewall-ում բացեք UDP 443-ը։ Cloudflare-ում HTTP/2-ն ու HTTP/3-ը դեպի այցելուներ լռելյայն միացված են․ դրանք կարող եք ստուգել Speed կամ Network կարգավորումներում։ Cloudflare-ից ձեր origin կապն առանձին է․ այն օգտագործում է HTTP/1.1 կամ HTTP/2 TCP-ի վրա, ուստի Cloudflare-ի հետևում գտնվող origin-ին HTTP/3 պետք չէ։ Ստուգեք curl-ով կամ բրաուզերի Network tab-ի Protocol սյունակով։
server {
listen 443 ssl;
listen 443 quic reuseport; # reuseport only once per port
http2 on;
http3 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
# sudo ufw allow 443/udp
# curl -sI --http2 https://example.com/ | head -1
06Ինչ է դա նշանակում performance-ի աշխատանքի համար
HTTP/2-ը կամ HTTP/3-ը միացնելուց հետո կենտրոնացեք այն բանի վրա, ինչը դեռ ժամանակ է խլում։ Round trip-երը դեռ կարևոր են, ուստի critical path-ը պահեք կարճ՝ preload արեք hero պատկերն ու հիմնական ֆոնտը, խուսափեք redirect-ների երկար շղթաներից և մատուցեք օգտատերերին մոտ CDN-ից։ Բայթերը դեռ կարևոր են, ուստի սեղմեք և ուղարկեք ավելի քիչ JavaScript։ Միավորումը (concatenation) ավելի քիչ կարևոր է, բայց հազարավոր փոքրիկ մոդուլները դեռ overhead ունեն․ bundler-ների route-level splitting-ն օպտիմալ միջինն է։ Խուսափեք domain sharding-ից և ավելորդ third-party origin-ներից, քանի որ յուրաքանչյուր նոր origin պահանջում է DNS, կապի և TLS կարգավորում․ նրանց համար, որոնք պետք է պահեք, օգտագործեք preconnect։ Չափեք իրական օգտատերերին․ HTTP/3-ի բարելավումները հիմնականում երևում են դանդաղ և փաթեթներ կորցնող ցանցերում, ոչ թե ձեր արագ desktop-ում։
Օգտագործեք, երբ
- HTTP/2՝ այսօր յուրաքանչյուր HTTPS կայքում և API-ում․ չմիացնելու իրական պատճառ չկա։
- HTTP/3՝ հանրային կայքեր՝ շատ մոբայլ օգտատերերով կամ հեռավոր, փաթեթներ կորցնող ցանցերում գտնվող օգտատերերով։
- CDN-ի հետևում՝ թող CDN-ը այցելուների հետ խոսի HTTP/3-ով, իսկ origin-ի հետ պահեք HTTP/2 կամ HTTP/1.1։
Մի՛ օգտագործեք, երբ
- Մի հետապնդեք HTTP/3-ը մեկ data center-ում գտնվող ներքին ծառայությունների համար․ շահը չնչին է, իսկ UDP-ի debug-ն ավելի բարդ է։
- Կորպորատիվ ցանցերը երբեմն արգելափակում են UDP 443-ը․ HTTP/3-ը միշտ պետք է ունենա HTTP/2 fallback։
Այլընտրանքներ
- WebSockets
Երկարակյաց երկկողմանի ալիք․ աշխատում է HTTP/1.1 upgrade-ի կամ HTTP/2 extended CONNECT-ի վրա։
- gRPC
Օգտագործում է HTTP/2 stream-եր՝ ծառայությունների միջև տիպավորված RPC-ի համար։
- WebTransport
Ավելի նոր API HTTP/3-ի վրա՝ ցածր latency-ով stream-երի և datagram-ների համար․ դեռ հասունանում է։
Լավագույն պրակտիկաներ
- Միացրեք HTTP/2 ամենուր և HTTP/3 հանրային edge-երում՝ միացված TLS 1.3-ով։
- Ձեր սեփական սերվերում HTTP/3 միացնելիս firewall-ում բացեք UDP 443-ը։
- Դադարեցրեք domain sharding-ը և image sprite-ները․ փոխարենը bundle-ները բաժանեք ըստ route-ի։
- Անհրաժեշտ third-party origin-ների համար օգտագործեք preconnect և հեռացրեք ավելորդները։
- Հեռացված server push-ի փոխարեն օգտագործեք 103 Early Hints կամ preload։
- Կոնֆիգի յուրաքանչյուր փոփոխությունից հետո DevTools-ում կամ curl-ով ստուգեք համաձայնեցված պրոտոկոլը։
Տարածված սխալներ
- Միացնել quic listener-ը, բայց մոռանալ բացել UDP 443-ը, այնպես որ բրաուզերները լուռ մնում են HTTP/2-ի վրա։
- reuseport ավելացնել նույն պորտի համար մի քանի server block-ում, ինչի պատճառով nginx-ը չի գործարկվում։
- Պահել HTTP/1.1-ի ժամանակաշրջանի հնարքները, օրինակ՝ sharding-ը, որոնք այժմ դանդաղեցնում են HTTP/2-ը։
- Թույլ տալ 0-RTT տվյալներ փոխող հարցումների համար՝ դրանք ենթարկելով replay-ի ռիսկին։
Խորը վերլուծություն
07Prioritization. dependency tree-երից մինչև Extensible Priorities
Multiplexing-ը նոր հարց է առաջացնում. երբ քսան պատասխան կիսում են մեկ կապ, որ բայթերն են գնում առաջինը։ HTTP/2-ն ի սկզբանե թույլ էր տալիս client-ներին կառուցել dependency tree՝ կշիռներով, բայց դա բարդ էր, բրաուզերներն այն օգտագործում էին անհետևողական, իսկ շատ սերվերներ անտեսում էին, ուստի RFC 9113-ը այն deprecated դարձրեց։ Փոխարինողը Extensible Priorities-ն է (RFC 9218), որն օգտագործում են HTTP/2-ը և HTTP/3-ը։ Client-ը ուղարկում է պարզ Priority header կամ frame. urgency u՝ 0-ից (ամենաբարձր) մինչև 7, և incremental դրոշակ i, որը նշանակում է, որ պատասխանն օգտակար է նաև կտորներով, ինչպես progressive նկարը։ Սերվերները և CDN-ները համապատասխանաբար են պլանավորում տվյալները, ուստի CSS-ը և LCP նկարը կարող են առաջ անցնել մեծ analytics script-ից։ Էջում fetchpriority-ն և preload-ը այն միջոցներն են, որոնցով ազդում եք, թե ինչ է ուղարկում բրաուզերը։ Եթե ճանապարհին ինչ-որ տեղ prioritization-ը կոտրված է, multiplexing-ը կարող է իրականում ուշացնել կրիտիկական ֆայլերը մեծերի հետևում։
GET /css/main.css HTTP/2
priority: u=0
GET /img/hero.avif HTTP/2
priority: u=1, i
GET /js/analytics.js HTTP/2
priority: u=5
08Flow control և HTTP/2-ին հատուկ հարձակումներ
HTTP/2-ը և QUIC-ը ունեն flow control ըստ stream-ի և ըստ կապի. յուրաքանչյուր կողմ հայտարարում է բայթերի պատուհան, որը պատրաստ է ստանալ, և ուղարկողը պետք է սպասի WINDOW_UPDATE-ին՝ ավելին ուղարկելուց առաջ։ Փոքր լռելյայն պատուհանները կարող են սահմանափակել throughput-ը արագ, երկար կապուղիներում, ահա թե ինչու սերվերները դրանք կարգավորում են։ Multiplexing-ը ստեղծեց նաև denial-of-service-ի նոր pattern-ներ։ 2023-ին Rapid Reset հարձակումը (CVE-2023-44487) հսկայական քանակությամբ բացում և անմիջապես չեղարկում էր stream-ներ, ուստի սերվերներն աշխատանք էին կատարում հարցումների համար, որոնք երբեք չէին հաշվվում concurrency limit-ների մեջ. դա ռեկորդային հեղեղներ առաջացրեց։ HPACK-ը և QPACK-ը header table-ի չափի սահմանափակումներ են պահանջում՝ հիշողության սպառումը կանխելու համար։ Պաշտպանությունը հիմնականում software update-ներն են և ողջամիտ limit-ները. nginx-ը և proxy-ները պահեք patch արված, սահմանափակեք միաժամանակյա stream-ները (http2_max_concurrent_streams, nginx-ում լռելյայն 128), սահմանափակեք header-ների չափը և թողեք, որ CDN-ը կլանի ծավալային հարձակումները։
09QUIC-ը ներսից. handshake-ներ, loss recovery, migration
QUIC փաթեթները գաղտնագրված են, ներառյալ header-ի մեծ մասը, ուստի միջանկյալ սարքերը չեն կարող ստուգել կամ փոփոխել transport-ի մանրամասները, ինչը պրոտոկոլը զարգացման համար բաց է պահում։ Packet համարները երբեք չեն կրկնվում, նույնիսկ retransmission-ների դեպքում, ինչը վերացնում է TCP-ի անորոշությունը, թե որ պատճենին է վերաբերում acknowledgement-ը, և round trip-ի գնահատումները դարձնում է ավելի ճշգրիտ։ Մինչև client-ի հասցեի validation-ը սերվերը կարող է ուղարկել ստացածից առավելագույնը երեք անգամ ավելի բայթ, ինչը թույլ չի տալիս QUIC-ն օգտագործել amplification հարձակումների համար. բեռի տակ token-ով Retry փաթեթը կարող է նախ հաստատել հասցեն։ Connection ID-ները թույլ են տալիս հեռախոսին փոխել ցանցը առանց նոր handshake-ի, և endpoint-ը ստուգում է նոր ճանապարհը՝ նախքան դրան վստահելը։ Գինը. QUIC-ն աշխատում է user space-ում, գաղտնագրումը կատարվում է ամեն փաթեթի համար, իսկ UDP-ի send և receive ճանապարհները պակաս օպտիմալացված են, քան TCP-ինը, ուստի CPU-ի ծախսը մեկ բայթի համար ավելի բարձր է։ Kernel-ի ֆունկցիաները, ինչպես UDP GSO-ն և ավելի մեծ socket buffer-ները, նվազեցնում են տարբերությունը։
10Discovery, fallback և իրական աշխարհի խառնուրդը
Բրաուզերը նախապես չի կարող իմանալ, որ սերվերը խոսում է HTTP/3-ով։ Սովորական ճանապարհը. առաջին այցն օգտագործում է HTTP/2 TCP-ով, պատասխանը ներառում է Alt-Svc: h3=":443", և հաջորդ կապերը QUIC-ը և TCP-ն մրցեցնում են՝ օգտագործելով այն, որն աշխատում է։ HTTPS DNS record-ը կարող է h3-ը հայտարարել դեռ առաջին կապից առաջ՝ բաց թողնելով այդ քայլը։ Եթե UDP 443-ը արգելափակված է կամ rate-limit է արված firewall-ի կողմից, բրաուզերը լուռ վերադառնում է TCP-ին և կարող է որոշ ժամանակ հիշել ձախողումը, ուստի սխալ կարգավորված սերվերը հազվադեպ է կոտրված թվում. այն պարզապես երբեք HTTP/3 տրաֆիկ չի ստանում։ Սերվերում ստուգեք, որ firewall-ը և ցանկացած cloud security group թույլ են տալիս UDP 443, որ երկու listener-ներում էլ մատուցվում է նույն սերտիֆիկատը, և որ load balancer-ները փոխանցում են UDP։ Log-երում գրանցեք պրոտոկոլը յուրաքանչյուր հարցման համար (nginx-ում $server_protocol)՝ յուրաքանչյուր տարբերակի իրական բաժինը տեսնելու համար։
# Ask for HTTP/3 directly (curl built with HTTP/3 support)
curl -sI --http3-only https://example.com/ | head -1
# Is h3 advertised?
curl -sI https://example.com/ | grep -i alt-svc
dig example.com HTTPS +short
# nginx log format: add the protocol per request
# log_format main '$remote_addr "$request" $status $server_protocol';
Իրական իրավիճակներ
HTTP/3-ը միացված է, բայց բրաուզերները երբեք չեն օգտագործում
Խնդիր
nginx կոնֆիգում կա quic listener և http3 on, և nginx-ը նորմալ է գործարկվում։ DevTools-ը դեռ ցույց է տալիս h2 յուրաքանչյուր հարցման համար, նույնիսկ մի քանի reload-ից հետո։
Ինչպես լուծել
Անցեք discovery-ի ճանապարհով։ curl -sI-ով ստուգեք, որ պատասխաններն ունեն Alt-Svc. location block-ի ներսում add_header-ը կարող է վերագրել server մակարդակի header-ները, ուստի համոզվեք, որ այն երևում է իրական էջերում։ Հաստատեք, որ UDP 443-ը բաց է ufw-ում և cloud provider-ի firewall-ում, և դրսից թեստավորեք curl --http3-only-ով։ Ստուգեք, որ nginx -V-ն ցույց է տալիս QUIC մոդուլը, և որ OpenSSL build-ը աջակցում է QUIC։ Բրաուզերները որոշ ժամանակ քեշավորում են HTTP/3-ի բացակայությունը, ուստի թեստավորեք նոր պրոֆիլում։ Եթե առջևում CDN կա, HTTP/3-ը CDN-ի կարգավորումն է, ոչ թե nginx-ինը։
Թույլ ցանցերի մոբայլ օգտատերերը բողոքում են դանդաղ բեռնումից
Խնդիր
Analytics-ը ցույց է տալիս, որ գյուղական վայրերում մոբայլ ցանցերի օգտատերերը շատ ավելի վատ բեռնման ժամանակ ունեն, քան Wi-Fi օգտատերերը, նկարներով հագեցած էջերում հաճախակի կանգառներով։
Ինչպես լուծել
Այս ցանցերում փաթեթների կորուստը ծանր է հարվածում TCP-ի վրա հիմնված HTTP/2-ին, քանի որ մեկ կորած փաթեթը կանգնեցնում է բոլոր stream-ները։ Այցելուներին մատուցեք HTTP/3՝ կամ CDN-ի միջոցով, կամ միացնելով այն nginx-ում՝ UDP 443-ը բաց։ Հետո կրճատեք յուրաքանչյուր էջի կարիքները. նկարները սեղմեք AVIF կամ WebP՝ responsive չափերով, էջի ներքևի մասը lazy-load արեք և առաջնահերթություն տվեք LCP նկարին։ Քիչ origin-ներ նշանակում են քիչ handshake-ներ։ Համեմատեք իրական օգտատերերի մետրիկաները ըստ պրոտոկոլի և կապի տեսակի՝ մինչև և հետո, ոչ թե լաբորատոր թեստեր արագ Wi-Fi-ում։
HTTP/3 ավելացնելուց հետո nginx-ը չի գործարկվում
Խնդիր
Յուրաքանչյուր virtual host-ին listen 443 quic reuseport ավելացնելուց հետո nginx -t-ն հաղորդում է duplicate listen option, և reload-ը ձախողվում է։
Ինչպես լուծել
reuseport-ը կարող է հայտնվել միայն մեկ անգամ յուրաքանչյուր հասցեի և port-ի համար ամբողջ կոնֆիգուրացիայում։ listen 443 quic reuseport-ը պահեք մեկ server block-ում, հաճախ default server-ում, իսկ մյուսներում օգտագործեք պարզ listen 443 quic։ Յուրաքանչյուր reload-ից առաջ աշխատեցրեք nginx -t։ Միաժամանակ համոզվեք, որ յուրաքանչյուր server block, որը պետք է HTTP/3 առաջարկի, ունի նաև http3 on և ուղարկում է Alt-Svc, և որ ssl_protocols-ը ներառում է TLSv1.3, որը QUIC-ը պահանջում է։
Արագ ուղեցույց
- Ո՞ր պրոտոկոլն է օգտագործում կայքը
curl -sI -o /dev/null -w "%{http_version}\n" https://example.com/- Ստիպել HTTP/2
curl -sI --http2 https://example.com/ | head -1- Ստիպել միայն HTTP/3
curl -sI --http3-only https://example.com/ | head -1- Ստուգել ALPN negotiation-ը
openssl s_client -connect example.com:443 -servername example.com -alpn h2 </dev/null 2>/dev/null | grep -i alpn- Միացնել HTTP/2 nginx 1.25.1+-ում
http2 on;- HTTP/3 listener nginx-ում
listen 443 quic reuseport; http3 on;- Հայտարարել HTTP/3-ի մասին
add_header Alt-Svc 'h3=":443"; ma=86400' always;- Բացել UDP 443
sudo ufw allow 443/udp- Ստուգել, որ nginx-ն ունի QUIC աջակցություն
nginx -V 2>&1 | tr ' ' '\n' | grep -i v3
Իմ իրական նախագծերում
- Իմ կառավարած production սերվերներում nginx-ն աշխատեցնում եմ որպես վեբ սերվեր և reverse proxy, որտեղ HTTP/2-ը TLS-ի վրա ստանդարտ կարգավորման մասն է։
- Իմ կայքերի առջև կանգնած Cloudflare-ն այցելուներին մատուցում է HTTP/2 և HTTP/3, մինչդեռ origin-ը մնում է ստանդարտ HTTPS-ի վրա։
- Իմ պրոդուկտների realtime մասերը (socket.io, Pusher) աշխատում են երկարակյաց կապերով՝ նույն nginx շերտի միջով։
Հարցեր և պատասխաններ
Q1Որո՞նք են HTTP/1.1-ի, HTTP/2-ի և HTTP/3-ի հիմնական տարբերությունները։
HTTP/1.1-ը տեքստային է և մեկ կապով միաժամանակ մշակում է մեկ հարցում, ուստի բրաուզերները բացում են մի քանի կապ, և դանդաղ պատասխանները արգելափակում են մյուսներին։ HTTP/2-ն օգտագործում է բինար frame-եր և մեկ TCP կապով multiplex է անում շատ stream-եր՝ HPACK header-ների սեղմմամբ, վերացնելով HTTP մակարդակի head-of-line blocking-ը։ HTTP/3-ը պահպանում է նույն գաղափարը, բայց աշխատում է QUIC-ի վրա UDP-ով․ stream-երն անկախ են տրանսպորտային մակարդակում, TLS 1.3-ը ներկառուցված է, ուստի կարգավորումը պահանջում է ավելի քիչ round trip, և կապերը դիմանում են ցանցի փոփոխություններին։ Սեմանտիկան՝ այսինքն մեթոդները, status code-երը և header-ները, երեքում էլ նույնն է։
Q2Ի՞նչ է head-of-line blocking-ը։
Դա այն է, երբ մեկ ուշացած տարրը պահում է իր հետևում հերթագրված ամեն ինչ։ HTTP/1.1-ում դա տեղի է ունենում հավելվածի մակարդակում․ կապի վրա պատասխանները գալիս են հերթականությամբ, ուստի դանդաղը արգելափակում է մնացածին։ HTTP/2-ը դա շտկում է multiplexed stream-երով, բայց TCP-ն դեռ բայթերը հասցնում է խիստ հերթականությամբ, այնպես որ մեկ կորած փաթեթը կանգնեցնում է բոլոր stream-երը, մինչև այն վերաուղարկվի։ Սա տրանսպորտային մակարդակի head-of-line blocking է, և այն վնասում է փաթեթներ կորցնող մոբայլ ցանցերում։ HTTP/3-ը դա լուծում է QUIC-ով, որտեղ յուրաքանչյուր stream առաքվում է անկախ, ուստի կորուստը հետաձգում է միայն այն stream-ը, որին պատկանում է։
Q3Ինչո՞ւ է HTTP/3-ը կառուցված UDP-ի, և ոչ թե TCP-ի վրա։
TCP-ն իրականացված է օպերացիոն համակարգերի kernel-ներում և ամենուր ստուգվում է middlebox-երի կողմից, ուստի նրա վարքագիծը փոխելը շատ տարիներ է պահանջում։ UDP-ն բարակ շերտ է, որն անցնում է ցանցերի մեծ մասով, ուստի QUIC-ը կարող է հուսալիությունը, congestion control-ը, stream-երը և գաղտնագրումն իրականացնել user space-ում և արագ զարգանալ։ UDP-ի վրա կառուցելը հնարավոր է դարձնում նաև անկախ stream-եր՝ խուսափելով TCP-ի հերթական առաքման խնդրից, և թույլ է տալիս TLS 1.3-ը միավորել տրանսպորտային handshake-ի հետ։ Connection ID-ները փոխարինում են IP-ի և պորտի զույգին, ուստի կապերը դիմանում են ցանցի փոփոխություններին։ Գինը CPU-ի ավելի մեծ օգտագործումն է և fallback-ի անհրաժեշտությունը, երբ UDP-ն արգելափակված է։
Q4Եթե օգտագործում ես Cloudflare, origin-ում HTTP/3 պե՞տք է։
Ոչ։ Cloudflare-ն այցելուի կապն ավարտում է իր edge-ում, որտեղ բրաուզերի հետ խոսում է HTTP/3-ով կամ HTTP/2-ով։ Cloudflare-ից ձեր origin կապն առանձին hop է TCP-ի վրա՝ HTTP/1.1-ով կամ HTTP/2-ով, սովորաբար լավ միացված ցանցերում տաք, վերաօգտագործվող կապերով, որտեղ HTTP/3-ի առավելությունները փոքր են։ Այնպես որ origin-ում համոզվում եմ, որ TLS-ը ճիշտ է Full (strict)-ի համար, և HTTP/2-ը միացված է, իսկ HTTP/3-ը թողնում եմ edge-ին։ Ձեր սեփական nginx-ում HTTP/3-ը կարևոր է, երբ այցելուները միանում են ուղղակիորեն՝ առանց CDN-ի։
Q5Performance-ի ո՞ր հին օպտիմալացումներից պետք է հրաժարվել HTTP/2-ի հետ։
Domain sharding-ը՝ ռեսուրսները մի քանի hostname-ների միջև բաշխելը ավելի շատ զուգահեռ կապեր ստանալու համար, այժմ վնասում է, որովհետև յուրաքանչյուր hostname պահանջում է իր DNS lookup-ը, TCP և TLS handshake-ը, և այն կոտրում է multiplexing-ն ու prioritization-ը։ Image sprite-ները և ագրեսիվ միավորումը մեկ հսկայական bundle-ի մեջ ավելի քիչ օգտակար են, որովհետև շատ հարցումները էժան են, իսկ հսկա ֆայլը վնասում է քեշավորմանը․ ցանկացած փոփոխություն անվավեր է դարձնում ամեն ինչ։ Շատ ռեսուրսներ HTML-ի մեջ inline անելը քեշավորման նույն գինն ունի։ Փոխարենը կոդը բաժանում եմ ըստ route-ի, ռեսուրսներին տալիս եմ hash-ով անուններ և քեշի երկար ժամկետներ, և origin-ների քանակը պահում եմ փոքր։
Սենիոր մակարդակի հարցեր
S1Կարո՞ղ է HTTP/2-ը կայքը HTTP/1.1-ից ավելի դանդաղ դարձնել։
Այո, կոնկրետ պայմաններում։ Կորուստներով ցանցերում ամեն ինչ տանող մեկ TCP կապը տուժում է, երբ փաթեթը կորչում է, մինչդեռ վեց HTTP/1.1 կապերը բաշխում են ռիսկը և ունեն վեց congestion window։ Կոտրված prioritization-ը, օրինակ՝ proxy, որն անտեսում է առաջնահերթությունները, կարող է թույլ տալ, որ մեծ նկարը կամ script-ը զբաղեցնի կապը CSS-ից առաջ։ HTTP/1.1-ի մնացած հնարքները, ինչպես domain sharding-ը, ստիպում են լրացուցիչ կապեր և չեզոքացնում են multiplexing-ը։ Իսկ շատ փոքր սկզբնական flow-control պատուհանները կարող են սահմանափակել throughput-ը երկար, արագ կապուղիներում։ Գործնականում HTTP/2-ը ավելի արագ է տիպիկ կայքերի համար, բայց այս դեպքերը բացատրում են տարօրինակ regression-ները։ HTTP/3-ը լուծում է կորստի խնդիրը, իսկ ճիշտ առաջնահերթությունները և ավելի քիչ origin-ները լուծում են մնացածի մեծ մասը։
S2Ի՞նչ գործառնական ծախսեր է բերում HTTP/3-ը ինքնուրույն աշխատեցնելը։
Դուք ավելացնում եք UDP listener, ուստի firewall-ները, cloud security group-երը և load balancer-ները պետք է թույլատրեն և փոխանցեն UDP 443, իսկ շատ managed load balancer-ներ այն մշակում են TCP-ից տարբեր։ CPU-ի օգտագործումը մեկ բայթի համար ավելի բարձր է, քան kernel offload-ներով TCP-ինը, ինչը կարևոր է բարձր տրաֆիկի դեպքում։ Debug անելն ավելի դժվար է, քանի որ փաթեթները գաղտնագրված են. tcpdump-ը քիչ բան է ցույց տալիս, ուստի հենվում եք qlog ֆայլերի, սերվերի log-երի և բրաուզերի գործիքների վրա։ Պետք է թեստավորել fallback-ը, քանի որ որոշ ցանցեր արգելափակում են UDP։ nginx-ի աջակցությունը պահանջում է QUIC-ունակ TLS գրադարան և reuseport-ի զգույշ կոնֆիգուրացիա։ Թիմերի մեծ մասի համար պրագմատիկ ընտրությունն այն է, որ CDN-ը HTTP/3-ով խոսի օգտատերերի հետ, իսկ origin-ում մնա HTTP/2, եթե միայն տրաֆիկը ուղիղ ձեր սերվերներին չի գնում։
S3Ինչպե՞ս են gRPC-ն և WebSocket-ները կապված HTTP տարբերակների հետ։
gRPC-ն սահմանված է HTTP/2-ի վրա. յուրաքանչյուր կանչ stream է, հաղորդագրությունները length-prefix ունեցող Protocol Buffers են, իսկ status-ը փոխանցվում է trailer-ներով։ Ահա թե ինչու բրաուզերները չեն կարող այն ուղղակիորեն օգտագործել և պետք է gRPC-Web կամ proxy, և ինչու ճանապարհի յուրաքանչյուր proxy պետք է աջակցի HTTP/2 ամբողջ ճանապարհին, ներառյալ trailer-ները։ WebSocket-ները սկսվել են որպես HTTP/1.1 Upgrade handshake. RFC 8441-ը ավելացրել է WebSocket-ներ HTTP/2-ի վրա extended CONNECT-ով, իսկ RFC 9220-ը նույնն է անում HTTP/3-ի համար, թեև շատ սերվերներ և proxy-ներ դեռ օգտագործում են HTTP/1.1 upgrade-ը։ nginx-ի համար սա նշանակում է WebSocket-ները proxy անել Upgrade և Connection header-ներով և երկար timeout-ներով։ WebTransport-ը HTTP/3-ին բնիկ տարբերակն է ցածր latency-ով stream-ների և datagram-ների համար։
Բառարան
- Multiplexing
- Բազմաթիվ հարցումների և պատասխանների միաժամանակյա ուղարկում մեկ կապով՝ որպես frame-եր խառնված։
- Stream
- Մեկ request–response փոխանակում HTTP/2 կամ HTTP/3 կապի ներսում՝ իր սեփական ID-ով։
- Head-of-line blocking
- Երբ մեկ ուշացած տարրը պահում է իր հետևում հերթագրված ամեն ինչ՝ HTTP կամ transport շերտում։
- HPACK / QPACK
- Header-ների սեղմման սխեմաներ HTTP/2-ի և HTTP/3-ի համար, որոնք խուսափում են կրկնվող header-ները նորից ուղարկելուց։
- ALPN
- Application-Layer Protocol Negotiation. TLS extension, որով client-ը և սերվերը համաձայնում են h2-ի կամ http/1.1-ի շուրջ։
- QUIC
- UDP-ի վրա հիմնված transport՝ ներկառուցված TLS 1.3-ով, անկախ stream-ներով և connection migration-ով. HTTP/3-ն աշխատում է դրա վրա։
- Alt-Svc
- Պատասխանի header, որը բրաուզերին ասում է, որ նույն կայքը հասանելի է այլ պրոտոկոլով, օրինակ՝ h3։
- 0-RTT
- Տվյալների ուղարկում վերսկսված կապի հենց առաջին փաթեթում. արագ է, բայց replay-ի ենթակա, ուստի միայն ապահով հարցումների համար։
- Connection ID
- QUIC-ի կապի նույնացուցիչ՝ անկախ IP-ից և port-ից, որը թույլ է տալիս փոխել ցանցը առանց վերամիանալու։
- Flow control
- Ստացողի կողմից հայտարարված սահմանաչափ, թե որքան տվյալ կարող է ուղարկողն ուղարկել՝ նախքան սպասելը։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։