For å gjøre det lettere å forstå hva denne artikkelen handler om, la oss starte med endringen i Stack Overflows gjennomsnittlige daglige statistikk. Følgende tall er fra statistikken per 12. november 2013:
- Lastbalansereren aksepterte 148 084 833 HTTP-forespørsler
- Av disse var 36 095 312 sideinnlasting
- 833 992 982 627 byte (776 GB) HTTP-trafikk brukes til å sende
- Totalt ble 286 574 644 032 byte (267 GB) data mottatt
- Totalt ble 1 125 992 557 312 byte (1 048 GB) sendt data
- 334 572 103 SQL-spørringer (inkludert kun fra HTTP-forespørsler)
- 412 865 051 Redis-forespørsler
- 3 603 418 tag engine-forespørsler
- Det tok 558 224 585 ms (155 timer) på SQL-spørringer
- Det tok 99 346 916 ms (27 timer) på Redis-forespørsler
- Brukte 132 384 059 ms (36 timer) på tag engine-forespørselen
- Det tok 2 728 177 045 ms (757 timer) på ASP.Net prosessprosessering
Følgende data viser endringene i statistikken per 9. februar 2016, slik at du kan sammenligne:
- HTTP-forespørsler mottatt av lastbalanserer: 209 420 973 (+61 336 090)
- 66 294 789 (+30 199 477) hvorav siden lastes
- HTTP-data sendt: 1 240 266 346 053 (+406 273 363 426) bytes (1,24 TB)
- Total mengde mottatte data: 569 449 470 023 (+282 874 825 991) byte (569 GB)
- Total mengde sendt data: 3 084 303 599 266 (+1 958 311 041 954) byte (3,08 TB)
- SQL-spørringer (kun fra HTTP-forespørsler): 504 816 843 (+170 244 740)
- Redis-cachetreff: 5 831 683 114 (+5 418 818 063)
- Elastiske søk: 17 158 874 (ikke sporet i 2013)
- Tag Engine-forespørsler: 3 661 134 (+57 716)
- Kumulativ tid brukt på å kjøre SQL-spørringer: 607 073 066 (+48 848 481) ms (168 timer)
- Redis cache-trefftid brukt: 10 396 073 (-88 950 843) ms (2,8 timer)
- Tid brukt av tag engine-forespørsler: 147 018 571 (+14 634 512) ms (40,8 timer)
- Tid brukt i ASP.Net behandling: 1 609 944 301 (-1 118 232 744) ms (447 timer)
- 22,71 (-5,29) ms: 49 180 275 utgavesider, gjennomsnittlig gjengivelsestid (hvorav 19,12 ms brukes i ASP.Net)
- 11,80 (-53,2) ms 6 370 076 første sider gjennomsnittlig gjengivelsestid (hvorav 8,81 ms brukes i ASP.Net)
Du lurer kanskje på hvorfor ASP.Net behandler 61 millioner flere forespørsler per dag, men reduserer behandlingstiden med 757 timer (sammenlignet med 2013). Dette skyldes hovedsakelig oppgraderingene vi gjorde på serverne våre tidlig i 2015, samt mye arbeid med ytelsesoptimalisering i appen. Ikke glem: ytelse er fortsatt et salgsargument. Hvis du er mer nysgjerrig på de spesifikke maskinvaredetaljene, ikke bekymre deg, jeg vil gi de spesifikke maskinvaredetaljene til serverne som brukes til å kjøre disse sidene i form av et vedlegg i neste artikkel (jeg oppdaterer denne lenken når tiden er inne).
Så hva har endret seg de siste to årene? Ikke mye, bare å bytte ut noen servere og nettverksutstyr. Her er en oversikt over serverne som brukes til å kjøre nettsiden din i dag (merk hvordan de har endret seg siden 2013)
- 4 Microsoft SQL Server-servere (2 av dem bruker ny maskinvare)
- 11 IIS-webservere (ny maskinvare)
- 2 Redis-servere (ny maskinvare)
- 3 Tag Engine-servere (hvorav 2 bruker ny maskinvare)
- 3 Elasticsearch-servere (samme som ovenfor)
- 4 HAProxy lastbalanseringsservere (2 lagt til for å støtte CloudFlare)
- 2 nettverksenheter (Nexus 5596 kjerne + 2232TM Fabric Extender, alle enheter oppgradert til 10 Gbps båndbredde)
- 2 x Fortinet 800C brannmur (erstatter Cisco 5525-X ASA)
- 2 Cisco ASR-1001 rutere (erstatter Cisco 3945 rutere)
- 2 Cisco ASR-1001-x rutere (NYE!) )
Hva trenger vi for at Stack Overflow skal fungere? Ikke mye har endret seg siden 2013, men takket være optimaliseringene og den nye maskinvaren nevnt ovenfor, trenger vi nå bare én webserver. Vi har allerede testet denne situasjonen utilsiktet, med suksess flere ganger. Vennligst merk: Jeg sa nettopp at det fungerer, jeg sa ikke at det er en god idé. Men hver gang dette skjer, er det ganske interessant.
Nå som vi har noen grunnleggende tall for server-skaleringsideer, la oss se hvordan vi lagde disse kule nettsidene. Få systemer eksisterer helt uavhengig (og våre er intet unntak), og uten et helhetlig syn som integrerer disse delene, reduseres betydningen av arkitekturplanlegging sterkt. Målet vårt er å forstå den overordnede situasjonen. Det vil komme mange artikler som går i dybden på hvert spesifikt felt i fremtiden. Denne artikkelen er bare et sammendrag av den logiske strukturen til nøkkelmaskinvaren, og neste artikkel vil inneholde spesifikke detaljer om denne maskinvaren.
Hvis du vil se hvordan denne maskinvaren ser ut i dag, her er noen bilder jeg tok av skap A (skap B er akkurat det samme) da jeg oppgraderte serveren i februar 2015:
La oss nå dykke ned i arkitekturoppsettet. Følgende er en oppsummering av den logiske arkitekturen til de viktigste eksisterende systemene:
Grunnleggende prinsipper
Her er noen felles prinsipper som ikke trenger å introduseres på én gang:
- Alt har redundante sikkerhetskopier.
- Alle servere og nettverksenheter har minst to båndbreddeforbindelser på 10 Gbps.
- Alle servere har to strømkilder som leverer strøm gjennom to UPS-enheter, to generatorer bak dem, og to nettspenningsmating.
- Alle servere har en redundant backup plassert i Rack A og Rack B.
- Alle servere og tjenester har doble redundante sikkerhetskopier i et separat datasenter (i Colorado), selv om jeg hovedsakelig dekker New York.
- Alt har redundante sikkerhetskopier.
Internett
Først må du finne nettsiden vår, som er en DNS-greie. Å finne nettsider går raskt, så nå gir vi det til CloudFlare fordi de har DNS-servere overalt i verden. Vi oppdaterer DNS-poster gjennom API-er, og de er ansvarlige for å "administrere" DNS. Men i våre skurkaktige sinn har vi fortsatt våre egne DNS-servere på grunn av dypt rotfestede tillitsproblemer. Når apokalypsen er apokalyptisk – kanskje på grunn av GPL, Punyon eller caching-problemer – og folk fortsatt vil programmere for å avlede oppmerksomheten sin, bytter vi til våre egne DNS-servere.
Når nettleseren din finner vårt gjemmested, kommer HTTP-trafikk fra våre fire internettleverandører (Level 3, Zayo, Cogent og Lightower i New York) inn i en av våre fire avanserte rutere. Vi bruker Border Gateway Protocol (BGP, en svært standard protokoll) for peer-to-peer-trafikk fra nettverksleverandører for å kontrollere den og gi den mest effektive måten å få tilgang til tjenestene våre på. ASR-1001 og ASR-1001-X rutere er delt inn i to grupper, som hver skal bruke aktiv/aktiv modus for å håndtere trafikk fra begge nettverksleverandørene – her finnes det redundante sikkerhetskopier. Selv om de alle har samme fysiske båndbredde på 10 Gbps, er trafikken fra utsiden fortsatt uavhengig av trafikken fra det eksterne VLAN-et og er koblet til lastbalanseringen separat. Etter at trafikken har passert gjennom ruteren, kommer du til lastbalansereren.
Jeg tror det er på tide å nevne at vi har MPLS med 10 Gbps båndbredde mellom de to datasentrene, selv om dette egentlig ikke er direkte relatert til nettsidetjenester. Vi bruker denne teknologien til å utføre ekstern replikasjon og rask gjenoppretting av data for å håndtere visse nødsituasjoner. "Men Nick, det er ingen redundans i dette!" Vel, fra et teknisk ståsted har du rett (i en positiv forstand), det er egentlig et enkelt sviktpunkt på dette nivået. Men vent! Gjennom nettverksleverandøren har vi også to ekstra OSPF failover-ruter (MPLS er førstevalg, og dette er andre og tredje valg av kostnadsgrunner). Hver av de tidligere nevnte enhetsgruppene vil bli koblet til Colorados datasenter for å balansere nettverkstrafikken i tilfelle failover. Selvfølgelig kunne vi ha koblet disse to settene med enheter til hverandre, slik at det finnes fire sett med signalveier, men glem det, la oss gå videre.
Lastbalansering (HAProxy)
Lastbalansering er implementert med HAProxy 1.5.15, som kjører på CentOS 7 (vår favorittversjon av Linux). Og legg til TLS (SSL) sikker overføringsprotokoll på HAProxy. Vi følger også med på HAProxy 1.7, som umiddelbart vil gi støtte for HTTP/2-protokollen.
I motsetning til andre servere med doble 10Gbps LACP-nettverkstilkoblinger, har hver lastbalanserer to 10Gbps-tilkoblinger: én for det eksterne nettverket og én for DMZ. Disse serverne har 64 GB eller mer minne for å håndtere SSL-protokolllaget mer effektivt. Når vi kan cache og gjenbruke flere TLS-økter i minnet, bruker vi mindre beregningsressurser når vi kobler til samme klient. Dette betyr at vi kan gjenopprette økter på en raskere og billigere måte. Hukommelse er så billig at det er et enkelt valg.
Lastfordelingen i seg selv er enkel å sette opp. Vi lytter til ulike nettsteder på flere forskjellige IP-adresser (hovedsakelig for sertifikat- og DNS-administrasjon), og ruter deretter trafikk til forskjellige backends (hovedsakelig basert på vertsheaders). Det eneste vi gjør her er å begrense hastigheten og skrape ut noe header-informasjon (fra weblaget) for å logge inn i HAProxys systemloggmeldinger, på denne måten kan vi registrere ytelsesmålinger for hver forespørsel. Vi skal nevne dette i detalj senere.
Weblag (IIS 8.5, ASP.Net MVC 5.2.3 og .Net 4.6.1)
Lastbalansering fordeler trafikken mellom 9 av det vi kaller den primære webserveren (01-09) og 2 utviklingswebservere (10-11, vårt testmiljø). Hovedserveren kjører Stack Overflow, Careers og alle Stack Exchange-nettstedene, mens meta.stackoverflow.com og meta.stackexchange.com kjører på to andre servere. Selve Q&A-appen er multi-tenant, noe som betyr at én app håndterer alle forespørsler fra Q&A-nettstedet. Med andre ord kan vi kjøre hele Q&A-appen på én applikasjonspool på én server. Andre apper som Careers, API v2, Mobile API, osv., er uavhengige. Her er hva du ser i IIS for master- og dev-serverne:
Her er fordelingen av Stack Overflows webtier slik den vises i Opserver (vårt interne overvåkingsdashboard):
Og her er ressursforbruket til disse webserverne:
Jeg vil gå mer i detalj i en senere artikkel om hvorfor vi tilbyr så mange ressurser, med fokus på rullende bygging, spillerom og redundans.
Tjenestelaget (IIS, ASP.Net MVC 5.2.3, . NET 4.6.1 og HTTP. SYS)
Ved siden av weblaget ligger tjenestelaget. De kjører også oppå IIS 2012 i Windows 8.5R2. Dette laget kjører noen interne tjenester som støtter weblaget og andre interne systemer i produksjonsmiljøet. De to hovedtjenestene er: "Stack Server", som kjører en tag-motor og er basert på http.sys (ikke IIS); Providence API (basert på IIS). En interessant fakta: Jeg måtte korrelere de to prosessene for å koble til forskjellige sockets, fordi Stack Server fikk tilgang til L2- og L3-cachene veldig ofte når den oppdaterte listen over problemer med to minutters mellomrom.
Maskinene som kjører disse tjenestene er kritiske for tagmotoren og backend-API-ene, så de må være redundante, men ikke 9 ganger redundante. For eksempel laster vi alle artikler og deres tagger fra databasen hvert n minutter (for øyeblikket 2 minutter), noe som ikke er lavt. Vi ønsker ikke å gjenta denne lastoperasjonen 9 ganger på weblaget, 3 ganger er trygt nok for oss. Vi bruker også ulike maskinvarekonfigurasjoner for disse serverne for å optimalisere bedre for de beregningsmessige og datalasteegenskapene til tagmotoren og elastiske indeksjobber (som også kjører i dette laget). "Tag-motoren" i seg selv er et relativt komplekst tema som vil bli dekket i en dedikert artikkel. Det grunnleggende prinsippet er at når du får tilgang til adressen /questions/tagged/java, besøker du tagging-motoren for å finne spørsmålene som matcher den. Motoren håndterer all taggmatching unntatt /search, så overalt, inkludert den nye navigasjonen, får data gjennom denne tjenesten.
Caching og publisering/abonnement (Redis)
Vi brukte Redis noen steder, og den har steinsolid stabilitet. Selv om det er så mange som 160 milliarder operasjoner per måned, overstiger ikke CPU-en per instans 2 %, som vanligvis er lavere:
Vi bruker Redis for Caching-systemer på nivå 1/L2. "L1"-nivået er HTTP-cachen som fungerer i en webserver eller en lignende applikasjon. "L2"-nivået er for å hente data gjennom Redis etter at forrige nivå-cache feiler. Våre data lagres i Protobuf-format, implementert gjennom protobuf-dot-net skrevet av Marc Gravel. For Redis-klienten brukte vi StackExchange.Redis-biblioteket, som er et åpen kildekode-bibliotek utviklet internt. Hvis en webserver ikke treffer både L1- og L2-cachen, henter den data fra sine datakilder (databasespørringer, API-kall osv.) og lagrer resultatene i den lokale cachen og Redis. Neste server kan mangle i L1-cachen når den henter de samme dataene, men den henter dataene i L2/Redis, noe som eliminerer behovet for databasespørringer eller API-kall.
Vi kjører også mange Q&A-steder, hver med sin egen L1/L2-cache: nøkkel som prefiks i L1-cachen og database-ID i L2/Redis-cachen. Vi vil gå nærmere inn på dette temaet i fremtidige artikler.
I tillegg til de to hovedserverne i Redis (en master og en slave) som kjører alle site-instanser, satte vi også opp en instans for maskinlæring (hovedsakelig av minnegrunner) ved å bruke to andre dedikerte slaveservere. Denne gruppen servere brukes til å tilby tjenester som å anbefale spørsmål på hjemmesiden og lage bedre jobbmatching. Denne plattformen heter Providence, og Kevin Montrose skrev om den.
Hovedserveren for Redis har 256 GB RAM (omtrent 90 GB brukt), og Providence-serveren har 384 GB minne (omtrent 125 GB brukt).
Redis er ikke bare for caching, det har også en publiserings- og abonnementsmekanisme hvor én server kan publisere en melding og andre abonnenter kan motta meldingen (inkludert Redis fra nedstrømsklienter på serveren). Vi bruker denne mekanismen for å tømme L1-cachen på andre tjenester for å opprettholde cachekonsistens på webserveren. Men det har en annen viktig bruk: websockets.
Websockets (NetGain)
Vi bruker websockets for å sende sanntidsoppdateringer til brukerne, som varsler i toppfeltet, stemmer, ny navigasjon, nye svar, kommentarer og mer.
Selve socket-serveren kjører på web-laget, med native sockets. Dette er en veldig liten applikasjon basert på vår åpne kildekode-biblioteksimplementering: StackExchange.NetGain. I rushtiden hadde vi omtrent 500 000 samtidige websocket-tilkoblinger, som er mange nettlesere. Morsomt faktum: noen av disse nettleserne har vært åpne i over 18 måneder, og du må finne noen for å se om disse utviklerne fortsatt er i live. Følgende diagram viser mønsteret for websocket-samtidighet denne uken:
Hvorfor bruke websockets? På vår skala er det mye mer effektivt enn meningsmålinger. På denne måten kan vi enkelt sende mer data med færre ressurser og være mer sanntidsorientert for brukerne. Denne tilnærmingen er imidlertid ikke uten problemer: midlertidige porter, uttømte filhåndteringer på lastbalanserere, er svært interessante problemer, og vi skal snakke om dem senere.
Søk (Elasticsearch)
Spoiler: Det er ikke mye å glede seg over her. Weblaget bruker Elasticsearch 1.4 og implementerer en ultralett, høyytelses StackExchange.Elastic-klient. I motsetning til det meste, planlegger vi ikke å åpne denne delen, rett og slett fordi den eksponerer et veldig lite utvalg av API-ene vi trenger å bruke. Jeg er sikker på at det å gjøre det offentlig veier opp for tapet og bare vil forvirre utviklerne. Vi bruker elastic:/search på disse stedene for å beregne relaterte spørsmål og gi forslag når vi stiller spørsmål.
Hver Elastic-klynge (én for hvert datasenter) inneholder 3 noder, hver med sin egen indeks. Karrieresiden har også noen ekstra indekser. En litt mindre standard del av konfigurasjonen vår i elastiske kretser er at klyngen vår på 3 servere er litt kraftigere enn den vanlige konfigurasjonen: hver server bruker SSD-lagring, 192 GB minne, dobbelt nettverk med 10 Gbps båndbredde.
Det samme applikasjonsdomenet Stack Server (ja, vi ble kastet rundt av .Net Core her) har også en tag-motor, som også bruker Elasticsearch for kontinuerlig indeksering. Her bruker vi et lite triks, som å bruke ROWVERSION i SQL Server (datakilden) for å sammenligne med "siste plass"-dokumentet i Elastic. Fordi det tilsynelatende er sekvensielt, er det lett for oss å crawle og indeksere innhold hvis det endres etter siste besøk.
Hovedgrunnen til at vi bruker Elasticsearch i stedet for teknologier som SQL fulltekstsøk, er dets skalerbarhet og kostnadseffektivitet. SQL er relativt dyrt på CPU-er, mens Elastic er mye billigere og har mange nye funksjoner i det siste. Hvorfor ikke bruke Solr? Vi må søke på tvers av nettverket (med flere indekser samtidig), og Solr støtter ikke dette scenariet på tidspunktet vi avgjør. Grunnen til at vi ikke har brukt 2.x ennå, er fordi typene har endret seg mye i 2.x, noe som betyr at vi må indeksere alt på nytt hvis vi vil oppgradere. Jeg har rett og slett ikke nok tid til å planlegge kravendringer og migreringer.
Database (SQL Server)
Vi bruker SQL Server som en enkelt sannhetskilde. All data i Elastic og Redis kommer fra SQL Server. Vi har to SQL Server-klynger og er konfigurert med AlwaysOn-tilgjengelighetsgrupper. Hver klynge har en primærserver i New York (som tar nesten all belastningen) og en replikaserver, i tillegg til en replikaserver i Colorado (vårt katastrofegjenopprettingsdatasenter). Alle kopioperasjoner er asynkrone.
Den første klyngen er et sett med Dell R720xd-servere, hver med 384 GB minne, en PCIe SSD med 4 TB plass, og to 12-kjerners CPU-er. Den inkluderer Stack Overflow, Sites (det er et dårlig navn, det forklarer jeg senere), PRIZM og Mobiles database.
Den andre klyngen er et sett med Dell R730xd-servere, hver med 768 GB minne, en PCIe SSD med 6 TB plass, og to 8-kjerners CPU-er. Denne klyngen inneholder alle andre databaser, inkludert Careers, Open ID, Chat, unntakslogger og andre spørsmål-og-svar-nettsteder (f.eks. Super User, Server Fault, osv.).
På databaselaget ønsker vi å holde CPU-utnyttelsen på et veldig lavt nivå, selv om CPU-bruken i praksis vil være litt høyere når noen planlagte caching-problemer oppstår (som vi feilsøker). For øyeblikket er NY-SQL02 og 04 hovedserverne, og 01 og 03 er replikaserverne, og vi startet dem på nytt i dag på grunn av SSD-oppgraderingen. Slik presterte de de siste 24 timene:
Vår bruk av SQL er veldig enkel. Enkelt betyr raskt. Selv om noen spørringssetninger kan være forvrengte, foregår vår interaksjon med SQL på en ganske naturlig måte. Vi har noe gammel Linq2Sql, men alle våre nye utviklinger bruker Dapper, vårt åpne mikro-ORM-rammeverk som bruker POCO. La meg forklare det på en annen måte: Stack Overflow har bare én lagret prosedyre i databasen sin, og jeg kommer til å avslutte denne siste lagrede prosedyren og erstatte den med kode.
Bibliotek
Vel, la oss ombestemme oss, her er ting som kan hjelpe deg mer direkte. Jeg har nevnt noen av dem før, men jeg skal gi deg en liste over de mange åpne .Net-bibliotekene vi vedlikeholder og som alle bruker. Vi åpner dem åpen fordi de ikke har kjerneverdi involvert, men de kan hjelpe utviklere over hele verden. Jeg håper du kan bruke dem nå:
- Dapper (.Net Core) – Et høyytelses mikro-ORM-rammeverk for ADO.Net
- StackExchange.Redis – En høyytelses Redis-klient
- MiniProfiler – en lettvektsprofiler som vi bruker på hver side (støtter også Ruby, Go og Node)
- Eksepsjonelt – For feillogging i SQL, JSON, MySQL osv
- Jil – Høyytelses JSON-serialisering og deserialisator
- Sigil – .Net CIL Generation Helper (brukes når C# ikke er raskt nok)
- NetGain – Høyytelses websocket-server
- Opserver – Overvåkingsdashbord som sjekker de fleste systemer direkte og kan hente informasjon fra Orion, Bosun eller WMI
- Bosun – Overvåkingssystem i bakgrunnen, skrevet i Go
|