Om het makkelijker te maken te begrijpen waar dit artikel over gaat, laten we beginnen met de verandering in de gemiddelde dagelijkse statistiek van Stack Overflow. De volgende cijfers zijn afkomstig uit de statistieken per 12 november 2013:
- De load balancer accepteerde 148.084.833 HTTP-verzoeken
- Hiervan waren 36.095.312 pagina's geladen
- 833.992.982.627 bytes (776 GB) HTTP-verkeer worden gebruikt om te verzenden
- In totaal werden 286.574.644.032 bytes (267 GB) aan data ontvangen
- In totaal werd 1.125.992.557.312 bytes (1.048 GB) aan data verzonden
- 334.572.103 SQL-queries (inclusief alleen van HTTP-verzoeken)
- 412.865.051 Redis-verzoeken
- 3.603.418 tagmotorverzoeken
- Het kostte 558.224.585 ms (155 uur) aan SQL-queries
- Het kostte 99.346.916 ms (27 uur) op Redis-verzoeken
- Besteed 132.384.059 ms (36 uur) aan het tagmotorverzoek
- Het duurde 2.728.177.045 ms (757 uur) aan ASP.Net procesverwerking
De volgende gegevens tonen de veranderingen in de statistieken per 9 februari 2016, zodat u kunt vergelijken:
- HTTP-verzoeken ontvangen door load balancer: 209.420.973 (+61.336.090)
- 66.294.789 (+30.199.477) waarvan de pagina laadt
- HTTP-gegevens verzonden: 1.240.266.346.053 (+406.273.363.426) bytes (1,24 TB)
- Totale hoeveelheid ontvangen gegevens: 569.449.470.023 bytes (+282.874.825.991) bytes (569 GB)
- Totale hoeveelheid verzonden gegevens: 3.084.303.599.266 (+1.958.311.041.954) bytes (3,08 TB)
- SQL-queries (alleen van HTTP-verzoeken): 504.816.843 (+170.244.740)
- Redis Cache Hits: 5.831.683.114 (+5.418.818.063)
- Elastische zoekopdrachten: 17.158.874 (niet bijgehouden in 2013)
- Tagmotorverzoeken: 3.661.134 (+57.716)
- Cumulatieve tijd verbruikt om SQL-queries uit te voeren: 607.073.066 (+48.848.481) ms (168 uur)
- Redis-cache-hittijd verbruikt: 10.396.073 (-88.950.843) ms (2,8 uur)
- Tijd verbruikt door tagengine-verzoeken: 147.018.571 (+14.634.512) ms (40,8 uur)
- Tijd verbruikt in ASP.Net verwerking: 1.609.944.301 (-1.118.232.744) ms (447 uur)
- 22,71 (-5,29) ms: 49.180.275 issuepagina's gemiddelde rendertijd (waarvan 19,12 ms in ASP.Net)
- 11,80 (-53,2) ms 6.370.076 eerste pagina's gemiddelde rendertijd (waarvan 8,81 ms in ASP.Net)
Je vraagt je misschien af waarom ASP.Net 61 miljoen meer verzoeken per dag verwerkt, maar de verwerkingstijd met 757 uur vermindert (vergeleken met 2013). Dit komt vooral door de upgrades die we begin 2015 aan onze servers hebben doorgevoerd, evenals veel werk aan in-app prestatieoptimalisatie. Vergeet niet: prestaties blijven een verkooppunt. Als je meer nieuwsgierig bent naar de specifieke hardwaredetails, maak je geen zorgen, ik geef de specifieke hardwaredetails van de servers die deze sites draaien in de vorm van een bijlage in het volgende artikel (ik zal deze link bijwerken als het zover is).
Wat is er in de afgelopen twee jaar veranderd? Niet veel, alleen wat servers en netwerkapparatuur vervangen. Hier is een overzicht van de servers die tegenwoordig worden gebruikt om je website te draaien (let op hoe ze sinds 2013 zijn veranderd)
- 4 Microsoft SQL Server-servers (waarvan 2 nieuwe hardware gebruiken)
- 11 IIS-webservers (nieuwe hardware)
- 2 Redis-servers (nieuwe hardware)
- 3 Tag Engine-servers (waarvan er 2 nieuwe hardware gebruiken)
- 3 Elasticsearch-servers (hetzelfde als hierboven)
- 4 HAProxy load balancing-servers (2 toegevoegd ter ondersteuning van CloudFlare)
- 2 netwerkapparaten (Nexus 5596 core + 2232TM Fabric Extender, alle apparaten geüpgraded naar 10Gbps bandbreedte)
- 2 x Fortinet 800C Firewall (vervangt Cisco 5525-X ASA's)
- 2 Cisco ASR-1001 routers (vervangt Cisco 3945 routers)
- 2 Cisco ASR-1001-x routers (NIEUW!) )
Wat hebben we nodig om Stack Overflow te laten werken? Er is sinds 2013 niet veel veranderd, maar dankzij de optimalisaties en de hierboven genoemde nieuwe hardware hebben we nu slechts één webserver nodig. We hebben deze situatie al per ongeluk getest, met meerdere keren succes. Let op: ik zei net dat het werkt, ik zei niet dat het een goed idee is. Maar elke keer dat dit gebeurt, is het best interessant.
Nu we wat basiscijfers hebben over ideeën voor serverschaling, laten we eens kijken hoe we deze gave webpagina's hebben gemaakt. Weinig systemen bestaan volledig onafhankelijk (en de onze vormen daarop geen uitzondering), en zonder een holistische visie die deze delen integreert, wordt de betekenis van architectuurplanning sterk verminderd. Ons doel is om de algehele situatie te begrijpen. Er zullen in de toekomst veel artikelen verschijnen die diep ingaan op elk specifiek vakgebied. Dit artikel is slechts een samenvatting van de logische structuur van de sleutelhardware, en het volgende artikel zal specifieke details over deze hardware bevatten.
Als je wilt zien hoe deze hardware er vandaag uitziet, hier zijn een paar foto's die ik heb gemaakt van Cabinet A (Cabinet B is precies hetzelfde als die) toen ik de server in februari 2015 heb geüpgraded:
Laten we nu duiken in de architectuurindeling. Hieronder volgt een samenvatting van de logische architectuur van de belangrijkste bestaande systemen:
Basisprincipes
Hier zijn enkele veelvoorkomende principes die niet achter elkaar hoeven te worden geïntroduceerd:
- Alles heeft redundante back-ups.
- Alle servers en netwerkapparaten hebben minstens twee bandbreedteverbindingen van 10 Gbps.
- Alle servers hebben twee stroombronnen die stroom leveren via twee UPS-units, twee generatoren erachter, en twee netspanningsfeedforwards.
- Alle servers hebben een redundante back-up die zich bevindt in Rack A en Rack B.
- Alle servers en diensten hebben dubbele redundante back-ups in een apart datacenter (in Colorado), hoewel ik vooral New York dek.
- Alles heeft redundante back-ups.
internet
Eerst moet je onze website vinden, die is een DNS-ding. Websites vinden is snel, dus geven we het nu aan CloudFlare omdat zij DNS-servers in elke uithoek van de wereld hebben. Wij werken DNS-records bij via API's, en zij zijn verantwoordelijk voor het "beheren" van DNS. Toch hebben we in onze schurkachtige gedachten nog steeds onze eigen DNS-servers vanwege de diepgewortelde vertrouwensproblemen. Wanneer de apocalyps apocalyptisch is – misschien door GPL, Punyon of cachingproblemen – en mensen nog steeds willen programmeren om hun aandacht af te leiden, schakelen we over op onze eigen DNS-servers.
Zodra je browser onze schuilplaats vindt, komt HTTP-verkeer van onze vier ISP's (Level 3, Zayo, Cogent en Lightower in New York) binnen in een van onze vier geavanceerde routers. We gebruiken het Border Gateway Protocol (BGP, een zeer standaard protocol) om peer-to-peer verkeer van netwerkproviders te beheren en de meest efficiënte manier te bieden om toegang te krijgen tot onze diensten. De ASR-1001 en ASR-1001-X routers zijn verdeeld in twee groepen, die elk active/active mode moeten gebruiken om verkeer van beide netwerkproviders af te handelen – hier zijn er redundante back-ups. Hoewel ze allemaal dezelfde fysieke bandbreedte van 10Gbps hebben, is het verkeer van buitenaf nog steeds onafhankelijk van het verkeer van het externe VLAN en is het apart verbonden met de load balance. Nadat het verkeer door de router is gegaan, kom je bij de load balancer.
Ik denk dat het tijd is om te vermelden dat we MPLS hebben met 10Gbps bandbreedte tussen de twee datacenters, hoewel dit niet direct gerelateerd is aan websiteservices. We gebruiken deze technologie om off-site replicatie uit te voeren en snel data te herstellen om bepaalde noodgevallen aan te pakken. "Maar Nick, er is geen redundantie in dit!" Nou, vanuit technisch oogpunt heb je gelijk (in positieve zin), het is op dit niveau echt een enkel punt van falen. Maar wacht! Via de netwerkprovider hebben we ook twee extra OSPF-failoverroutes (MPLS is de eerste keuze, en dit zijn de tweede en derde keuze om kostenredenen). Elk van de eerder genoemde sets apparaten zal worden verbonden met het datacenter van Colorado om het netwerkverkeer in het geval van failover te kunnen balanceren. Natuurlijk hadden we deze twee sets apparaten met elkaar kunnen verbinden, zodat er vier sets paden zijn, maar vergeet het maar, laten we verder gaan.
Load balancing (HAProxy)
Load balancing is geïmplementeerd met HAProxy 1.5.15, draaiend op CentOS 7 (onze favoriete Linux-versie). En voeg TLS (SSL) secure transmission protocol toe op HAProxy. We houden ook HAProxy 1.7 in de gaten, dat direct ondersteuning zal bieden voor het HTTP/2-protocol.
In tegenstelling tot andere servers met dubbele 10Gbps LACP-netwerkverbindingen heeft elke load balancer twee 10Gbps-verbindingen: één voor het externe netwerk en één voor de DMZ. Deze servers hebben 64GB of meer geheugen om de SSL-protocollaag efficiënter te beheren. Wanneer we meer TLS-sessies in het geheugen kunnen cachen en hergebruiken, verbruiken we minder rekenkracht wanneer we verbinding maken met dezelfde client. Dit betekent dat we sessies sneller en goedkoper kunnen herstellen. Geheugen is zo goedkoop dat het een makkelijke keuze is.
Load balancing zelf is eenvoudig op te zetten. We luisteren naar verschillende websites met meerdere IP's (voornamelijk voor certificaat- en DNS-beheerdoeleinden) en routeren vervolgens verkeer naar verschillende backends (voornamelijk gebaseerd op hostheaders). Het enige wat we hier doen is de snelheid beperken en wat headerinformatie (van de weblaag) scrapen om in te loggen op de systeemlogberichten van HAProxy, op deze manier kunnen we prestatie-indicatoren voor elk verzoek vastleggen. We zullen dit later in detail bespreken.
Weblaag (IIS 8.5, ASP.Net MVC 5.2.3 en .Net 4.6.1)
Load balancing verdeelt het verkeer tussen 9 van wat wij de primaire webserver noemen (01-09) en 2 ontwikkelwebservers (10-11, onze testomgeving). De hoofdserver draait Stack Overflow, Careers en alle Stack Exchange-sites, terwijl meta.stackoverflow.com en meta.stackexchange.com op twee andere servers draaien. De hoofd Q&A-app zelf is multi-tenant, wat betekent dat één app alle verzoeken van de Q&A-site afhandelt. Met andere woorden, we kunnen de hele Q&A-app draaien op één applicatiepool op één server. Andere apps zoals Careers, API v2, Mobile API, enzovoort, zijn onafhankelijk. Dit zie je in IIS voor de master- en devservers:
Hier is de verdeling van de weblaag van Stack Overflow zoals te zien is in Opserver (ons interne monitoringdashboard):
En hier is het resourceverbruik van deze webservers:
Ik zal in een volgend artikel dieper ingaan op waarom we zoveel middelen overmatig aanbieden, met de nadruk op doorlopende bouw, speelruimte en redundantie.
Servicelaag (IIS, ASP.Net MVC 5.2.3, . NET 4.6.1 en HTTP. SYS)
Naast de weblaag ligt de servicelaag. Ze draaien ook bovenop IIS 2012 in Windows 8.5R2. Deze laag draait enkele interne services die de weblaag en andere interne systemen van de productieomgeving ondersteunen. De twee belangrijkste diensten zijn: "Stack Server", die een tag-engine draait en gebaseerd is op http.sys (niet IIS); Providence API (gebaseerd op IIS). Een interessant feit: ik moest de twee processen correleren om verbinding te maken met verschillende sockets, omdat de Stack Server de L2- en L3-caches zeer vaak benaderde bij het verversen van de lijst met problemen om de twee minuten.
De machines die deze services draaien zijn cruciaal voor de tagengine en backend-API's, dus ze moeten redundant zijn, maar niet 9x redundant. We laden bijvoorbeeld alle artikelen en hun tags elke n minuten (momenteel 2 minuten) uit de database, wat niet laag is. We willen deze laadoperatie niet 9 keer op de weblaag herhalen, 3 keer is veilig genoeg voor ons. We gebruiken ook verschillende hardwareconfiguraties voor deze servers om beter te optimaliseren voor de rekenkundige en databelastingskenmerken van de tagengine en elastic index-jobs (ook draaiend in deze laag). De "tag engine" zelf is een relatief complex onderwerp dat in een speciaal artikel behandeld zal worden. Het basisprincipe is dat wanneer je het adres /questions/taged/java opent, je de tagging-engine bezoekt om de vragen te vinden die erbij horen. De engine verzorgt alle tagmatching behalve /search, dus overal, inclusief de nieuwe navigatie, ontvangt data via deze service.
Caching & Publiceren/Abonneren (Redis)
We hebben Redis op sommige plekken gebruikt, en het heeft een rotsvaste stabiliteit. Hoewel er tot wel 160 miljard bewerkingen per maand zijn, komt de CPU per instantie niet boven de 2%, wat meestal lager is:
We gebruiken Redis voor Caching-systemen op L1/L2-niveau. Het "L1"-niveau is de HTTP-cache die werkt in een webserver of een vergelijkbare applicatie. Het "L2"-niveau is bedoeld om gegevens via Redis te verkrijgen nadat de cache van het vorige niveau is mislukt. Onze data wordt opgeslagen in het Protobuf-formaat, geïmplementeerd via protobuf-dot-net geschreven door Marc Gravel. Voor de Redis-client gebruikten we de StackExchange.Redis-bibliotheek, een open-source bibliotheek die intern is ontwikkeld. Als een webserver niet raakt in zowel de L1- als L2-caches, haalt hij data op uit zijn databronnen (databasequeries, API-aanroepen, enzovoort) en slaat de resultaten op in de lokale cache en Redis. De volgende server kan ontbreken in de L1-cache bij het ophalen van dezelfde data, maar hij zal de data in L2/Restis ophalen, waardoor databasequeries of API-aanroepen overbodig zijn.
We draaien ook veel Q&A-sites, elk met zijn eigen L1/L2-cache: key als prefix in de L1-cache en database-ID in de L2/Redis-cache. We zullen dit onderwerp in toekomstige artikelen behandelen.
Naast de twee hoofd Redis-servers (één master en één slave) die alle site-instanties draaien, hebben we ook een instantie opgezet voor machine learning (voornamelijk om geheugenredenen) met twee andere dedicated slave-servers. Deze groep servers wordt gebruikt om diensten te bieden zoals het aanbevelen van vragen op de homepage en het verbeteren van jobmatching. Dit platform heet Providence, en Kevin Montrose schreef erover.
De hoofdserver van Redis heeft 256GB RAM (ongeveer 90GB gebruikt), en de server in Providence heeft 384GB geheugen (ongeveer 125GB gebruikt).
Redis is niet alleen bedoeld voor caching, het heeft ook een publicatie- en abonnementsmechanisme waarbij één server een bericht kan publiceren en andere abonnees het bericht kunnen ontvangen (inclusief Redis van downstream-clients op de server). We gebruiken dit mechanisme om de L1-cache op andere diensten te wissen en zo de consistentie van de cache op de webserver te behouden. Maar het heeft een andere belangrijke functie: websockets.
Websockets (NetGain)
We gebruiken websockets om realtime updates naar gebruikers te sturen, zoals meldingen in de bovenste balk, stemmen, nieuwe navigatie, nieuwe antwoorden, opmerkingen en meer.
De socketserver zelf draait op de weblaag en gebruikt native sockets. Dit is een zeer kleine applicatie gebaseerd op onze open-source bibliotheekimplementatie: StackExchange.NetGain. In piekuren hadden we ongeveer 500.000 gelijktijdige websocket-verbindingen, wat veel browsers zijn. Leuk weetje: sommige van deze browsers zijn al meer dan 18 maanden open, en je zult iemand moeten vinden om te zien of die ontwikkelaars nog bestaan. De volgende grafiek toont het patroon van websocket-gelijktijdigheid deze week:
Waarom websockets gebruiken? Op onze schaal is het veel efficiënter dan peilingen. Op deze manier kunnen we simpelweg meer data pushen met minder middelen en meer realtime beschikbaar zijn voor gebruikers. Deze aanpak is echter niet zonder problemen: tijdelijke poorten, uitgeputte bestandshandvatten op load balancers zijn zeer interessante problemen, waar we het later over hebben.
Zoeken (Elasticsearch)
Spoiler: Er valt hier niet veel om enthousiast over te worden. De weblaag gebruikt Elasticsearch 1.4 en implementeert een ultralichte, high-performance StackExchange.Elastic-client. In tegenstelling tot de meeste dingen zijn we niet van plan dit deel open source te maken, simpelweg omdat het een zeer kleine subset van de API's die we moeten gebruiken blootlegt. Ik weet zeker dat het openbaar maken van het verlies opweegt tegen het verlies en ontwikkelaars alleen maar zal verwarren. We gebruiken elastic:/search op deze plekken om gerelateerde vragen te berekenen en suggesties te geven bij het stellen van vragen.
Elke elastische cluster (één voor elk datacenter) bevat 3 nodes, elk met een eigen index. De Careers-site bevat ook enkele extra indexen. Een iets minder standaard onderdeel van onze configuratie in elastische cirkels is dat onze cluster van 3 servers iets krachtiger is dan de gebruikelijke configuratie: elke server gebruikt SSD-opslag, 192GB geheugen, dubbele netwerken van 10Gbps bandbreedte.
Hetzelfde applicatiedomein van Stack Server (ja, we werden hier door .Net Core rondgegooid) host ook een tag-engine, die ook Elasticsearch gebruikt voor continue indexering. Hier gebruiken we een kleine truc, zoals het gebruik van ROWVERSION in SQL Server (de databron) om te vergelijken met het "laatste plaats"-document in Elastic. Omdat het blijkbaar sequentieel is, is het voor ons gemakkelijk om content te crawlen en te indexeren als deze na het laatste bezoek wordt aangepast.
De belangrijkste reden dat we Elasticsearch gebruiken in plaats van technologieën zoals SQL full-text search is de schaalbaarheid en kosteneffectiviteit. SQL is relatief duur op CPU's, terwijl Elastic veel goedkoper is en de laatste tijd veel nieuwe functies heeft. Waarom gebruik je Solr niet? We moeten over het hele netwerk zoeken (met meerdere indexen tegelijk), en Solr ondersteunt dit scenario niet op het moment van onze beslissingen. De reden dat we 2.x nog niet hebben gebruikt, is omdat de types in 2.x sterk zijn veranderd, wat betekent dat we alles opnieuw moeten indexeren als we willen upgraden. Ik heb gewoon niet genoeg tijd om te plannen voor wijzigingen in de vereisten en migraties.
Database (SQL Server)
We gebruiken SQL Server als één bron van waarheid. Alle data in Elastic en Redis komt van SQL Server. We hebben twee SQL Server-clusters en zijn geconfigureerd met AlwaysOn-beschikbaarheidsgroepen. Elke cluster heeft een primaire server in New York (die bijna alle belasting opvangt) en een replica-server, naast een replicaserver in Colorado (ons disaster recovery datacenter). Alle kopieerbewerkingen zijn asynchroon.
De eerste cluster bestaat uit een set Dell R720xd-servers, elk met 384GB geheugen, een PCIe SSD met 4TB ruimte en twee 12-core CPU's. Het omvat Stack Overflow, Sites (dat is een slechte naam, dat leg ik later uit), PRIZM en de database van Mobile.
De tweede cluster bestaat uit een set Dell R730xd-servers, elk met 768GB geheugen, een PCIe SSD met 6TB ruimte en twee 8-core CPU's. Deze cluster bevat alle andere databases, waaronder Careers, Open ID, Chat, uitzonderingslogboeken en andere Q&A-sites (bijv. Super User, Server Fault, enz.).
Op de databaselaag willen we het CPU-gebruik op een zeer laag niveau houden, hoewel het CPU-gebruik in de praktijk iets hoger zal zijn wanneer er geplande cachingproblemen optreden (die we aan het oplossen zijn). Op dit moment zijn NY-SQL02 en 04 de primaire servers en 01 en 03 de replica-servers, en we hebben ze vandaag opnieuw opgestart vanwege de SSD-upgrade. Zo hebben ze het in de afgelopen 24 uur gedaan:
Ons gebruik van SQL is heel eenvoudig. Simpel betekent snel. Hoewel sommige query-statements verdraaid kunnen zijn, verloopt onze interactie met SQL zelf op een vrij native manier. We hebben wat legacy Linq2SQL, maar al onze nieuwe ontwikkelingen gebruiken Dapper, ons open-source micro-ORM-framework dat POCO gebruikt. Laat me het anders uitleggen: Stack Overflow heeft slechts één opgeslagen procedure in zijn database, en ik ga deze laatste opgeslagen procedure stoppen en vervangen door code.
Bibliotheek
Laten we van gedachten veranderen, hier zijn dingen die je directer kunnen helpen. Ik heb er al een paar genoemd, maar ik geef je een lijst van de vele open-source .Net-bibliotheken die we beheren en die iedereen gebruikt. We open source ze omdat ze geen kernwaarde voor het bedrijf hebben, maar ze kunnen ontwikkelaars over de hele wereld helpen. Ik hoop dat je ze nu kunt gebruiken:
- Dapper (.Net Core) – Een high-performance micro-ORM framework voor ADO.Net
- StackExchange.Redis – Een high-performance Redis-client
- MiniProfiler – een lichtgewicht profiler die we op elke pagina gebruiken (ondersteunt ook Ruby, Go en Node)
- Uitzonderlijk – Voor foutlogging in SQL, JSON, MySQL, enzovoort
- Jil – High-performance JSON serialisatie en deserializer
- Sigil – .Net CIL Generatie Helper (gebruikt wanneer C# niet snel genoeg is)
- NetGain – High-performance websocketserver
- Opserver – Monitoringsdashboard dat de meeste systemen direct pollt en informatie kan ophalen van Orion, Bosun of WMI
- Bosun – Monitoringsysteem op de achtergrond, geschreven in Go
|