Pentru a face mai ușor de înțeles despre ce este acest articol, să începem cu schimbarea statisticii medii zilnice a Stack Overflow. Următoarele cifre provin din statisticile de la 12 noiembrie 2013:
- Load balancer-ul a acceptat 148.084.833 de cereri HTTP
- Dintre acestea, 36.095.312 erau încărcate de pagini
- 833.992.982.627 octeți (776 GB) de trafic HTTP sunt folosiți pentru a trimite
- Au fost primite în total 286.574.644.032 de octeți (267 GB) de date
- Au fost trimise în total 1.125.992.557.312 octeți (1.048 GB) de date
- 334.572.103 interogări SQL (inclusiv doar din cereri HTTP)
- 412.865.051 cereri Redis
- 3.603.418 cereri Tag Engine
- A durat 558.224.585 ms (155 ore) pentru interogări SQL
- A durat 99.346.916 ms (27 de ore) pentru cererile Redis
- Am petrecut 132.384.059 ms (36 ore) pentru cererea de motor de etichete
- A durat 2.728.177.045 ms (757 ore) pentru procesarea procesului ASP.Net
Următoarele date arată schimbările statisticilor la data de 9 februarie 2016, astfel încât să puteți compara:
- Cereri HTTP primite de la load balancer: 209.420.973 (+61.336.090)
- 66.294.789 (+30.199.477) dintre care pagini se încarcă
- Date HTTP trimise: 1.240.266.346.053 (+406.273.363.426) octeți (1,24 TB)
- Cantitatea totală de date primite: 569.449.470.023 (+282.874.825.991) octeți (569 GB)
- Cantitatea totală de date trimise: 3.084.303.599.266 (+1.958.311.041.954) octeți (3,08 TB)
- Interogări SQL (doar din cereri HTTP): 504.816.843 (+170.244.740)
- Vizualizări Redis Cache: 5.831.683.114 (+5.418.818.063)
- Căutări elastice: 17.158.874 (neînregistrate în 2013)
- Cereri de motoare de etichete: 3.661.134 (+57.716)
- Timp cumulativ consumat pentru rularea interogărilor SQL: 607.073.066 (+48.848.481) ms (168 ore)
- Timp consumat de lovitură a cache-ului Reis: 10.396.073 (-88.950.843) ms (2,8 ore)
- Timp consumat de cererile motorului de etichete: 147.018.571 (+14.634.512) ms (40,8 ore)
- Timp consumat în procesarea ASP.Net: 1.609.944.301 (-1.118.232.744) ms (447 ore)
- 22,71 (-5,29) ms 49.180.275 pagini de număr timp mediu de randare (din care 19,12 ms consumate în ASP.Net)
- 11,80 (-53,2) ms 6.370.076 prime pagini Timpul mediu de randare (din care 8,81 ms sunt consumate în ASP.Net)
Poate te întrebi de ce ASP.Net procesează cu 61 de milioane de cereri în plus pe zi, dar reduce timpul de procesare cu 757 de ore (comparativ cu 2013). Acest lucru se datorează în principal actualizărilor pe care le-am făcut serverelor noastre la începutul lui 2015, precum și multor lucrări de optimizare a performanței în aplicație. Nu uita: performanța rămâne un punct forte. Dacă ești mai curios despre detaliile specifice hardware-ului, nu-ți face griji, voi oferi detaliile hardware specifice ale serverelor folosite pentru rularea acestor site-uri sub forma unui anexă în următorul articol (voi actualiza acest link când va veni momentul).
Deci, ce s-a schimbat în ultimii doi ani? Nu prea mult, doar înlocuim niște servere și echipamente de rețea. Iată o prezentare generală a serverelor folosite astăzi pentru a rula site-ul tău (observă cum s-au schimbat din 2013)
- 4 servere Microsoft SQL Server (dintre care 2 folosesc hardware nou)
- 11 servere web IIS (hardware nou)
- 2 servere Redis (hardware nou)
- 3 servere Tag Engine (dintre care 2 folosesc hardware nou)
- 3 Servere Elasticsearch (la fel ca mai sus)
- 4 servere de echilibrare a încărcării HAProxy (2 adăugate pentru a suporta CloudFlare)
- 2 dispozitive de rețea (Nexus 5596 nucleu + 2232TM Fabric Extender, toate dispozitivele actualizate la lățime de bandă de 10Gbps)
- 2 x Fortinet 800C Firewall (înlocuiește Cisco 5525-X ASA)
- 2 routere Cisco ASR-1001 (înlocuiește routerele Cisco 3945)
- 2 routere Cisco ASR-1001-x (NOI!) )
Ce avem nevoie pentru ca Stack Overflow să funcționeze? Nu s-a schimbat prea mult din 2013, dar datorită optimizărilor și noului hardware menționat mai sus, acum avem nevoie doar de un singur server web. Am testat deja această situație fără să vrem, cu succes de mai multe ori. Vă rog să rețineți: tocmai am spus că funcționează, nu că e o idee bună. Dar de fiecare dată când se întâmplă asta, este destul de interesant.
Acum că avem câteva cifre de bază pentru idei de scalare a serverelor, să vedem cum am creat aceste pagini web interesante. Puține sisteme există complet independent (iar ale noastre nu fac excepție), iar fără o viziune holistică care să integreze aceste părți, sensul planificării arhitecturale este mult redus. Scopul nostru este să înțelegem situația generală. Vor exista multe articole care vor aborda în profunzime fiecare domeniu specific în viitor. Acest articol este doar un rezumat al structurii logice a hardware-ului cheie, iar următorul articol va conține detalii specifice despre aceste hardware-uri.
Dacă vrei să vezi cum arată acest hardware astăzi, iată câteva fotografii pe care le-am făcut cu Dulapul A (Dulapul B este exact la fel) când am făcut upgrade la server în februarie 2015:
Acum, să trecem la structura arhitecturală. Următorul este un rezumat al arhitecturii logice a sistemelor principale existente:
Principii de bază
Iată câteva principii comune care nu trebuie introduse la rândul lor:
- Totul are backup-uri redundante.
- Toate serverele și dispozitivele de rețea au cel puțin două conexiuni cu lățime de bandă de 10Gbps.
- Toate serverele au două surse de alimentare care furnizează energie prin două seturi UPS, două generatoare în spate și două alimente de tensiune în rețea.
- Toate serverele au un backup redundant situat în Rack A și Rack B.
- Toate serverele și serviciile au backup-uri duble redundante într-un centru de date separat (în Colorado), deși eu acopăr în principal New York-ul.
- Totul are backup-uri redundante.
Internet
Mai întâi trebuie să găsești site-ul nostru, care este o chestiune de DNS. Găsirea site-urilor este rapidă, așa că acum o dăm CloudFlare pentru că au servere DNS în fiecare colț al lumii. Actualizăm înregistrările DNS prin API-uri, iar ei sunt responsabili de "gestionarea" DNS-ului. Totuși, în mințile noastre răufăcătoare, încă avem propriile servere DNS din cauza problemelor adânc înrădăcinate de încredere. Când apocalipsa este apocaliptică – poate din cauza GPL-ului, Punyon sau a problemelor de cache – și oamenii încă vor să programeze pentru a-și distrage atenția, trecem la propriile noastre servere DNS.
Odată ce browserul tău găsește locul nostru de ascunzătoare, traficul HTTP de la cei patru furnizori ai noștri (Level 3, Zayo, Cogent și Lightower în New York) intră într-unul dintre cele patru routere avansate. Folosim Border Gateway Protocol (BGP, un protocol foarte standard) pentru a conecta traficul peer-to-peer de la furnizorii de rețea pentru a-l controla și a oferi cea mai eficientă modalitate de a accesa serviciile noastre. Routerele ASR-1001 și ASR-1001-X sunt împărțite în două grupuri, fiecare dintre ele trebuind să folosească modul activ/activ pentru a gestiona traficul ambilor furnizori de rețea – aici există backup-uri redundante. Deși toate au aceeași lățime de bandă fizică de 10Gbps, traficul din exterior este totuși independent de traficul din VLAN-ul extern și este conectat separat la echilibrarea încărcării. După ce traficul trece prin router, vei ajunge la load balancer.
Cred că ar fi momentul să menționăm că avem MPLS cu o lățime de bandă de 10Gbps între cele două centre de date, deși acest lucru nu are legătură directă cu serviciile site-ului. Folosim această tehnologie pentru a efectua replicare off-site și recuperare rapidă a datelor pentru a face față anumitor situații de urgență. "Dar Nick, nu e nicio redundanță în asta!" Ei bine, din punct de vedere tehnic ai dreptate (într-un sens pozitiv), este cu adevărat un singur punct de eșec la acest nivel. Dar stai! Prin intermediul furnizorului de rețea, avem și două rute suplimentare de failover OSPF (MPLS este prima opțiune, iar acestea sunt a doua și a treia opțiune din motive de cost). Fiecare dintre seturile de dispozitive menționate anterior va fi conectat la centrul de date din Colorado în consecință pentru a echilibra traficul de rețea în cazul unui failover. Desigur, am fi putut conecta aceste două seturi de dispozitive între ele, astfel încât să existe patru seturi de căi, dar uită, să trecem mai departe.
Echilibrarea încărcării (HAProxy)
Echilibrarea încărcării este implementată cu HAProxy 1.5.15, rulând pe CentOS 7 (versiunea noastră preferată de Linux). Și să adaug protocolul de transmisie securizată TLS (SSL) pe HAProxy. De asemenea, urmărim HAProxy 1.7, care va oferi suport imediat pentru protocolul HTTP/2.
Spre deosebire de alte servere cu două conexiuni de rețea LACP de 10Gbps, fiecare echilibrator are două conexiuni de 10Gbps: una pentru rețeaua externă și cealaltă pentru DMZ. Aceste servere au 64GB sau mai mult de memorie pentru a gestiona mai eficient stratul protocolului SSL. Când putem stoca și reutiliza mai multe sesiuni TLS în memorie, consumăm mai puține resurse de calcul atunci când ne conectăm la același client. Aceasta înseamnă că putem restabili sesiunile într-un mod mai rapid și mai ieftin. Memoria este atât de ieftină încât e o alegere ușoară.
Echilibrarea încărcării în sine este ușor de configurat. Ascultăm diferite site-uri pe mai multe IP-uri diferite (în principal din motive de gestionare a certificatelor și DNS) și apoi direcționăm traficul către backend-uri diferite (în principal pe baza anteturilor gazdei). Singurul lucru pe care îl facem aici este să limităm rata și să extragem unele informații de antet (din stratul web) pentru a se conecta la mesajele de jurnal de sistem ale HAProxy, astfel încât putem înregistra metrici de performanță pentru fiecare cerere. Vom menționa asta în detaliu mai târziu.
Stratul web (IIS 8.5, ASP.Net MVC 5.2.3 și .Net 4.6.1)
Echilibrarea încărcării distribuie traficul între 9 dintre ceea ce numim serverul web principal (01-09) și 2 servere web de dezvoltare (10-11, mediul nostru de testare). Serverul principal rulează Stack Overflow, Careers și toate site-urile Stack Exchange, în timp ce meta.stackoverflow.com și meta.stackexchange.com rulează pe alte două servere. Aplicația principală de întrebări și răspunsuri este multi-tenant, ceea ce înseamnă că o singură aplicație gestionează toate cererile de pe site-ul de întrebări și răspunsuri. Cu alte cuvinte, putem rula întreaga aplicație de întrebări și răspunsuri pe un singur pool de aplicații pe un singur server. Alte aplicații precum Careers, API v2, Mobile API etc. sunt independente. Iată ce vezi în IIS pentru serverele master și dev:
Iată distribuția nivelului web al Stack Overflow, așa cum se vede în Opserver (panoul nostru intern de monitorizare):
Și iată consumul de resurse al acestor servere web:
Voi intra în mai multe detalii într-un articol ulterior despre motivul pentru care oferim atât de multe resurse, concentrându-ne pe construcția în rol, marjă de manevră și redundanță.
Stratul de servicii (IIS, ASP.Net MVC 5.2.3, . NET 4.6.1 și HTTP. SYS)
Lângă stratul web se află stratul de servicii. De asemenea, rulează peste IIS 2012 în Windows 8.5R2. Acest strat rulează unele servicii interne care suportă stratul web și alte sisteme interne ale mediului de producție. Cele două servicii principale sunt: "Stack Server", care rulează un motor de etichete și se bazează pe http.sys (nu pe IIS); API-ul Providence (bazat pe IIS). Un fapt interesant: a trebuit să corelez cele două procese pentru a se conecta la socket-uri diferite, deoarece Stack Server accesa foarte frecvent cache-urile L2 și L3 când reîmprospăta lista problemelor la intervale de două minute.
Mașinile care rulează aceste servicii sunt critice pentru motorul de etichete și API-urile backend, deci trebuie să fie redundante, dar nu de 9x redundante. De exemplu, încărcăm toate articolele și etichetele lor din baza de date la fiecare n minute (momentan 2 minute), ceea ce nu este scăzut. Nu vrem să repetăm această operațiune de încărcare de 9 ori la nivelul web, 3 ori este suficient de sigur pentru noi. De asemenea, folosim diferite configurații hardware pentru aceste servere pentru a optimiza mai bine caracteristicile computaționale și de încărcare a datelor ale motorului de etichete și ale joburilor elastice de index (care rulează și în acest strat). "Motorul de etichete" în sine este un subiect relativ complex care va fi abordat într-un articol dedicat. Principiul de bază este că atunci când accesezi adresa /questions/tagged/java, vizitezi motorul de etichetare pentru a obține întrebările care corespund acesteia. Motorul gestionează toate potrivirile etichetelor, cu excepția /search, așa că peste tot, inclusiv noul navigator, primesc date prin acest serviciu.
Cache & Publicare/Abonare (Redis)
Am folosit Redis în unele locuri și are o stabilitate foarte solidă. Deși există până la 160 de miliarde de operațiuni pe lună, CPU-ul pe instanță nu depășește 2%, ceea ce este de obicei mai mic:
Folosim Redis pentru sistemele de caching la nivel L1/L2. Nivelul "L1" este cache-ul HTTP care funcționează într-un server web sau în orice aplicație similară. Nivelul "L2" este pentru a obține date prin Redis după ce cache-ul de nivel anterior cedează. Datele noastre sunt stocate în formatul Protobuf, implementat prin protobuf-dot-net scris de Marc Gravel. Pentru clientul Redis, am folosit biblioteca StackExchange.Redis, care este o bibliotecă open-source dezvoltată intern. Dacă un server web nu lovește nici în cache-urile L1 cât și L2, preia date din sursele sale (interogări în baza de date, apeluri API și așa mai departe) și salvează rezultatele în cache-ul local și în Redis. Următorul server poate lipsi din cache-ul L1 când recuperează aceleași date, dar va recupera datele în L2/Redis, eliminând necesitatea interogărilor bazei de date sau apelurilor API.
De asemenea, rulăm multe site-uri de întrebări și răspunsuri, fiecare cu propriul cache L1/L2: cheia ca prefix în cache-ul L1 și ID-ul bazei de date în cache-ul L2/Redis. Vom aborda acest subiect în articolele viitoare.
Pe lângă cele două servere principale Redis (unul master și unul slave) care rulează toate instanțele site-ului, am configurat și o instanță pentru machine learning (în principal din motive de memorie) folosind alte două servere slave dedicate. Acest grup de servere este folosit pentru a oferi servicii precum recomandarea întrebărilor pe pagina principală și îmbunătățirea potrivirii joburilor. Această platformă se numește Providence, iar Kevin Montrose a scris despre ea.
Serverul principal Redis are 256GB RAM (aproximativ 90GB folosiți), iar serverul Providence are 384GB memorie (aproximativ 125GB folosiți).
Redis nu este doar pentru cache, ci are și un mecanism de publicare și abonare în care un server poate publica un mesaj, iar alți abonați pot primi mesajul (inclusiv Redis de la clienții din aval de pe server). Folosim acest mecanism pentru a șterge cache-ul L1 pe alte servicii pentru a menține consistența cache-ului pe serverul web. Dar are o altă utilitate importantă: websocket-urile.
Websockets (NetGain)
Folosim websocket-uri pentru a trimite actualizări în timp real utilizatorilor, cum ar fi notificări în bara de sus, voturi, navigare nouă, răspunsuri noi, comentarii și altele.
Socket serverul în sine rulează pe stratul web, folosind socket-uri native. Aceasta este o aplicație foarte mică, bazată pe implementarea noastră open-source a bibliotecii: StackExchange.NetGain. În perioadele de vârf, aveam aproximativ 500.000 de conexiuni websocket simultane, ceea ce reprezintă multe browsere. Un fapt interesant: unele dintre aceste browsere sunt deschise de peste 18 luni și va trebui să găsești pe cineva care să vadă dacă acei dezvoltatori mai sunt în viață. Graficul următor arată tiparul concurenței websocket din această săptămână:
De ce să folosești websocket-uri? La scara noastră, este mult mai eficient decât sondajele. Astfel, putem pur și simplu să împingem mai multe date cu mai puține resurse și să fim mai în timp real pentru utilizatori. Totuși, această abordare nu este lipsită de probleme: porturile temporare, epuizarea handle-urilor de fișiere pe load balancere, sunt probleme foarte interesante și vom discuta despre ele mai târziu.
Căutare (Elasticsearch)
Spoiler: Nu prea ai de ce să te entuziasmezi aici. Stratul web folosește Elasticsearch 1.4 și implementează un client StackExchange.Elastic ultra-ușor și performant. Spre deosebire de majoritatea lucrurilor, nu plănuim să deschidem această parte ca open source, pur și simplu pentru că expune un subset foarte mic din API-urile pe care trebuie să le folosim. Sunt sigur că publicarea lui depășește pierderea și va deruta doar dezvoltatorii. Folosim elastic:/search în aceste locuri pentru a calcula întrebări conexe și pentru a oferi sugestii când punem întrebări.
Fiecare cluster Elastic (câte unul pentru fiecare centru de date) conține 3 noduri, fiecare cu propriul său index. Site-ul Careers are, de asemenea, câțiva indexuri suplimentare. O parte puțin mai puțin standard a configurației noastre în cercuri elastice este că clusterul nostru de 3 servere este puțin mai puternic decât configurația obișnuită: fiecare server folosește stocare SSD, 192GB memorie, rețea dublă de 10Gbps lățime de bandă.
Același domeniu de aplicație Stack Server (da, am fost aruncați de .Net Core aici) găzduiește și un motor de tag-uri, care folosește Elasticsearch pentru indexare continuă. Aici folosim un mic truc, cum ar fi utilizarea ROWVERSION în SQL Server (sursa de date) pentru a compara cu documentul "ultimul loc" din Elastic. Deoarece pare să fie secvențial, ne este ușor să căutăm și să indexăm conținutul dacă este modificat după ultima vizită.
Principalul motiv pentru care folosim Elasticsearch în locul tehnologiilor precum SQL full-text search este scalabilitatea și eficiența costurilor. SQL este relativ scump pe procesoare, în timp ce Elastic este mult mai ieftin și are multe funcții noi în ultima vreme. De ce să nu folosești Solr? Trebuie să căutăm în rețea (cu mai mulți indexuri în același timp), iar Solr nu suportă acest scenariu la momentul deciziilor noastre. Motivul pentru care nu am folosit încă 2.x este că tipurile s-au schimbat mult în 2.x, ceea ce înseamnă că trebuie să reindexăm totul dacă vrem să facem upgrade. Pur și simplu nu am suficient timp să planific schimbările de cerințe și migrările.
Bază de date (SQL Server)
Folosim SQL Server ca sursă unică de adevăr. Toate datele din Elastic și Redis provin din SQL Server. Avem două clustere SQL Server și suntem configurați cu grupuri de disponibilitate AlwaysOn. Fiecare cluster are un server principal în New York (care preia aproape toată sarcina) și un server replică, pe lângă un server replică în Colorado (centrul nostru de date de recuperare în caz de dezastru). Toate operațiunile de copiere sunt asincrone.
Primul cluster este un set de servere Dell R720xd, fiecare cu 384GB memorie, un SSD PCIe cu 4TB spațiu și două procesoare cu 12 nuclee. Include Stack Overflow, Sites (e un nume urât, o să explic mai târziu), PRIZM și baza de date a Mobile.
Al doilea cluster este un set de servere Dell R730xd, fiecare cu 768GB memorie, un SSD PCIe cu 6TB spațiu și două procesoare cu 8 nuclee. Acest cluster conține toate celelalte baze de date, inclusiv Careers, Open ID, Chat, jurnale de excepții și alte site-uri de întrebări și răspunsuri (de exemplu, Super User, Server Fault etc.).
La nivelul bazei de date, vrem să menținem utilizarea CPU-ului la un nivel foarte scăzut, deși în practică utilizarea CPU-ului va fi ușor mai mare atunci când apar unele probleme planificate de cache (pe care le depanăm). În prezent, NY-SQL02 și 04 sunt serverele principale, iar 01 și 03 sunt serverele replică, iar astăzi le-am repornit din cauza upgrade-ului la SSD. Iată cum s-au comportat în ultimele 24 de ore:
Utilizarea noastră a SQL-ului este foarte simplă. Simplu înseamnă rapid. Deși unele instrucțiuni de interogare pot fi pervertite, interacțiunea noastră cu SQL în sine se face într-un mod destul de nativ. Avem ceva Linq2SQL vechi, dar toate noile noastre dezvoltări folosesc Dapper, cadrul nostru open-source micro-ORM care folosește POCO. Permiteți-mi să explic altfel: Stack Overflow are o singură procedură stocată în baza sa de date, iar eu voi opri această ultimă procedură stocată rămasă și o voi înlocui cu cod.
Bibliotecă
Ei bine, hai să ne schimbăm părerea, iată lucruri care te pot ajuta mai direct. Am menționat câteva dintre ele înainte, dar vă voi oferi o listă cu numeroasele biblioteci open-source .Net pe care le întreținem și pe care le folosește toată lumea. Le folosim open source pentru că nu implică valoare de business de bază, dar pot ajuta dezvoltatorii din întreaga lume. Sper să le poți folosi acum:
- Dapper (.Net Core) – Un cadru micro-ORM de înaltă performanță pentru ADO.Net
- StackExchange.Redis – Un client Redis de înaltă performanță
- MiniProfiler – un profiler ușor pe care îl folosim pe fiecare pagină (suportă și Ruby, Go și Node)
- Excepțional – Pentru înregistrarea erorilor în SQL, JSON, MySQL etc.
- Jil – Serializare JSON de înaltă performanță și deserializator
- Sigil – .Net CIL Generation Helper (folosit când C# nu este suficient de rapid)
- NetGain – Server websocket de înaltă performanță
- Opserver – Tablou de control de monitorizare care interoghează majoritatea sistemelor direct și poate prelua informații de la Orion, Bosun sau WMI
- Bosun – Sistem de monitorizare în fundal, scris în Go
|