Laravel er mange ting. Men hurtig er ikke en af dem. Lad os lære nogle tricks fra handelen for at få den til at gå hurtigere!
Ingen PHP-udvikler er uberørt af Laravel disse dage. De er enten en junior- eller mellemudvikler, der elsker den hurtige udvikling, Laravel tilbyder, eller de er en seniorudvikler, der bliver tvunget til at lære Laravel på grund af markedspresset.
Uanset hvad er der ingen at benægte, at Laravel har genoplivet PHP-økosystemet (jeg ville helt sikkert forlade PHP-verdenen for længe siden, hvis Laravel ikke var der).
Et uddrag af (lidt berettiget) selvpris fra Laravel
Da Laravel bøjer sig bagud for at gøre tingene lette for dig, betyder det, at under det arbejder tonsvis af tonsvis af arbejde for at sikre dig, at du har et behageligt liv som udvikler. Alle de “magiske” funktioner i Laravel, der bare ser ud til at fungere, har lag på lag med kode, der skal piskes op, hver gang en funktion kører. Selv en simpel undtagelse sporer, hvor dybt kaninhullet er (læg mærke til, hvor fejlen starter, helt ned til hovedkernen):
For hvad der ser ud til at være en kompilationsfejl i en af visningerne, er der 18 funktionskald til at spore. Jeg er personligt kommet på 40, og der kunne nemt være mere, hvis du bruger andre biblioteker og plugins.
Pointen er, at dette lag som standard på lag med kode gør Laravel langsom.
Hvor langsom er Laravel?
Helt ærligt er det helt umuligt at besvare dette spørgsmål af flere grunde.
Først, der er ingen accepteret, objektiv og fornuftig standard til måling af hastigheden på webapps. Hurtigere eller langsommere i forhold til hvad? Under hvilke betingelser?
Anden, en webapp afhænger af så mange ting (database, filsystem, netværk, cache osv.), at det er almindeligt fjollet at tale om hastighed. En meget hurtig webapp med en meget langsom database er en meget langsom webapp.
Men denne usikkerhed er netop grunden til, at benchmarks er populære. Selvom de ikke betyder noget (se det her og det her), de giver nogle referencerammer og hjælper os med at blive gal. Lad os derfor få en forkert, grov idé om hastighed blandt PHP-rammer, med flere knap salt klar.
At gå efter denne temmelig respektable GitHub kilde, her er, hvordan PHP-rammerne er i linje, når de sammenlignes:
Du vil måske ikke engang bemærke Laravel her (selvom du sprænger rigtigt hårdt), medmindre du kaster din sag lige til slutningen af halen. Ja, kære venner, Laravel kommer sidst! Nu, indrømmet, er de fleste af disse “rammer” ikke meget praktiske eller endda nyttige, men det fortæller os, hvor langsom Laravel er i sammenligning med andre mere populære.
Normalt har denne “langsomhed” ikke funktion i applikationer, fordi vores almindelige webapps sjældent rammer høje tal. Men når de først har gjort det (siger op mod 200-500 samtidighed), begynder serverne at kvæle og dø. Det er den tid, hvor selv at kaste mere hardware på problemet ikke skærer det, og infrastrukturregninger klatrer så hurtigt, at dine høje idealer for cloud computing kommer ned.
Men hej, cheer up! Denne artikel handler ikke om, hvad der ikke kan gøres, men om, hvad der kan gøres.
Gode nyheder er, at du kan gøre meget for at få din Laravel-app til at gå hurtigere. Flere gange hurtigt. Ja, ikke noget. Du kan få den samme kodebase til at gå ballistisk og spare flere hundrede dollars på infrastruktur / hosting-regninger hver måned. Hvordan? Lad os komme til det.
Fire typer optimeringer
Efter min mening kan optimering udføres på fire forskellige niveauer (når det kommer til PHP-applikationer, det vil sige):
- Sprog-niveau: Dette betyder, at du bruger en hurtigere version af sproget og undgår specifikke funktioner / typografier for kodning på det sprog, der gør din kode langsom.
- Framework-niveau: Dette er de ting, vi vil dække i denne artikel.
- Infrastruktur-niveau: Indstilling af din PHP-procesadministrator, webserver, database osv.
- Hardware-niveau: Gå videre til en bedre, hurtigere og mere kraftfuld hardwareudbyder.
Alle disse typer optimeringer har deres plads (for eksempel er php-fpm optimering temmelig kritisk og kraftfuld). Men fokus for denne artikel vil være optimeringer rent af type 2: dem, der er relateret til rammen.
Der er forresten ingen begrundelse bag nummereringen, og det er ikke en accepteret standard. Jeg har lige lavet disse. Vær venlig ikke at citere mig nogensinde og sige, “Vi har brug for type-3-optimering på vores server,” eller din teamleder dræber dig, finder mig og dræber mig også.
Og nu, endelig, ankommer vi det lovede land.
Contents
- 1 Vær opmærksom på n + 1 databaseforespørgsler
- 2 Cache konfigurationen!
- 3 Reducer autoladede tjenester
- 4 Vær klog med middleware-stakke
- 5 Undgå ORM (til tider)
- 6 Brug cache så meget som muligt
- 7 Foretrækker cache i hukommelsen
- 8 Cache ruterne
- 9 Billedoptimering og CDN
- 10 Autoloader-optimering
- 11 Bliv venner med køer
- 12 Aktiveringsoptimering (Laravel Mix)
Vær opmærksom på n + 1 databaseforespørgsler
Problemet med n + 1-forespørgsel er et almindeligt, når der bruges ORM’er. Laravel har sin magtfulde ORM kaldet Eloquent, som er så smuk, så praktisk, at vi ofte glemmer at se på, hvad der foregår.
Overvej et meget almindeligt scenario: visning af listen over alle ordrer placeret af en given liste over kunder. Dette er temmelig almindeligt i e-handelssystemer og eventuelle rapporteringsgrænseflader generelt, hvor vi har brug for at vise alle enheder, der er relateret til nogle enheder.
I Laravel kan vi forestille os en controller-funktion, der gør jobbet sådan:
klasse OrdersController udvider Controller
{
// …
offentlig funktion getAllByCustomers (Anmod om $ anmodning, array $ ids) {
$ kunder = kunde :: findMany ($ ids);
$ ordrer = samle (); // ny kollektion
foreach ($ kunder som $ kunde) {
$ ordrer = $ ordrer->merge ($ kunde->Ordre:% s);
}
returvisning (‘admin.reports.orders’, [‘orders’ => $ ordrer]);
}
}
Sød! Og endnu vigtigere, elegant, smukt.
Desværre er det en katastrofal måde at skrive kode i Laravel.
Her er hvorfor.
Når vi beder ORM om at lede efter de givne kunder, genereres en SQL-forespørgsel som denne:
VÆLG * FRA kunder, HVOR ID I ER (22, 45, 34,…);
Hvilket er nøjagtigt som forventet. Som et resultat gemmes alle de returnerede rækker i samling $ $ -kunder i controller-funktionen.
Nu slæber vi over hver kunde en efter en og får deres ordrer. Dette udfører følgende forespørgsel . . .
VÆLG * FRA ordrer HVOR customer_id = 22;
. . . så mange gange som der er kunder.
Med andre ord, hvis vi har brug for at få ordredata for 1000 kunder, vil det samlede antal udførte databaseforespørgsler være 1 (til hentning af alle kunders data) + 1000 (til hentning af ordredata for hver kunde) = 1001. Dette er hvor navnet n + 1 kommer fra.
Kan vi gøre det bedre? Sikkert! Ved at bruge det, der kaldes ivrig indlæsning, kan vi tvinge ORM til at udføre en JOIN og returnere alle de nødvendige data i en enkelt forespørgsel! Sådan her:
$ ordrer = kunde :: findMany ($ ids)->med (ordrer “)->få();
Den resulterende datastruktur er bestemt en indlejret struktur, men ordredataene kan let udvindes. Den resulterende enkelt forespørgsel, i dette tilfælde, er noget lignende:
VÆLG * FRA kunder INNER JOIN ordrer PÅ customer.id = orders.customer_id HVOR kunder.id IN (22, 45,…);
En enkelt forespørgsel er naturligvis bedre end tusind ekstra forespørgsler. Forestil dig hvad der ville ske, hvis der var 10.000 kunder, der skulle behandle! Eller gud forbyde, hvis vi også ønskede at vise varerne indeholdt i enhver rækkefølge! Husk, at navnet på teknikken er ivrig med indlæsning, og det er næsten altid en god ide.
Cache konfigurationen!
En af grundene til Laravels fleksibilitet er de mange konfigurationsfiler, der er en del af rammen. Vil du ændre, hvordan / hvor billederne gemmes?
Du skal bare ændre filen config / filesystems.php (mindst i skrivende stund). Vil du arbejde med flere køedrivere? Du er velkommen til at beskrive dem i config / queue.php. Jeg tællede lige og fandt, at der er 13 konfigurationsfiler til forskellige aspekter af rammen, hvilket sikrer, at du ikke bliver skuffet, uanset hvad du vil ændre.
I betragtning af arten af PHP vågner Laravel op, starter alt op og analyserer alle disse konfigurationsfiler for at finde ud af, hvordan man gør ting anderledes denne gang. Bortset fra at det er dumt, hvis intet har ændret sig i de sidste par dage! Genopbygning af konfigurationen på enhver anmodning er et spild, der kan (faktisk skal undgås), og vejen ud er en simpel kommando, som Laravel tilbyder:
php håndværkskonfigur: cache
Hvad dette gør er at kombinere alle de tilgængelige konfigurationsfiler i en enkelt og cache er et sted til hurtig hentning. Næste gang der er en internetanmodning, vil Laravel blot læse denne enkelt fil og komme i gang.
Når det er sagt, er cache-konfiguration en ekstremt delikat operation, der kan sprænge i dit ansigt. Den største gotcha er, at når du har udstedt denne kommando, ringer env () -funktionen fra overalt undtagen konfigurationsfilerne vil returnere nul!
Det giver mening, når du tænker over det. Hvis du bruger konfigurationscache, fortæller du rammen, “Ved du hvad, jeg tror, jeg har konfigureret tingene pænt, og jeg er 100% sikker på, at jeg ikke vil have dem til at ændre.” Med andre ord forventer du, at miljøet forbliver statisk, hvilket er, hvad .env-filer er til.
Med det sagt er her nogle jernbelagte, hellige, uknuselige regler for konfigurationscache:
- Gør det kun på et produktionssystem.
- Gør det kun, hvis du virkelig er, virkelig sikker på, at du vil fryse konfigurationen.
- I tilfælde af at noget går galt, fortryd indstillingen med php håndværkscache: rydd
- Bed om, at skaderne på virksomheden ikke var betydelige!
Reducer autoladede tjenester
For at være nyttigt indlæser Laravel et væld af tjenester, når det vågner op. Disse er tilgængelige i filen config / app.php som en del af array-nøglen ‘udbydere’. Lad os se på hvad jeg har i min sag:
/ *
|————————————————————————–
| Autoloaded serviceudbydere
|————————————————————————–
|
| De her udførte tjenesteudbydere indlæses automatisk på
| anmodning til din ansøgning. Du er velkommen til at tilføje dine egne tjenester til
| denne matrix for at give dine applikationer udvidet funktionalitet.
|
* /
‘udbydere’ => [
/ *
* Laravel Framework Service Providers…
* /
Illuminate \ Auth \ AuthServiceProvider :: klasse,
Illuminate \ Broadcasting \ BroadcastServiceProvider :: klasse,
Illuminate \ Bus \ BusServiceProvider :: klasse,
Illuminate \ Cache \ CacheServiceProvider :: klasse,
Illuminate \ Foundation \ Providers \ ConsoleSupportServiceProvider :: klasse,
Illuminate \ Cookie \ CookieServiceProvider :: klasse,
Illuminate \ Database \ DatabaseServiceProvider :: klasse,
Illuminate \ Kryptering \ EncryptionServiceProvider :: klasse,
Illuminate \ filsystem \ FilesystemServiceProvider :: klasse,
Illuminate \ Foundation \ Providers \ FoundationServiceProvider :: klasse,
Illuminate \ Hashing \ HashServiceProvider :: klasse,
Illuminate \ Mail \ MailServiceProvider :: klasse,
Illuminate \ Bemærkninger \ NotificationServiceProvider :: klasse,
Illuminate \ Pagination \ PaginationServiceProvider :: klasse,
Illuminate \ Pipeline \ PipelineServiceProvider :: klasse,
Illuminate \ Kø \ QueueServiceProvider :: klasse,
Illuminate \ Redis \ RedisServiceProvider :: klasse,
Illuminate \ Auth \ Passwords \ PasswordResetServiceProvider :: klasse,
Illuminate \ Session \ SessionServiceProvider :: klasse,
Illuminate \ Oversættelse \ TranslationServiceProvider :: klasse,
Illuminate \ Validering \ ValidationServiceProvider :: klasse,
Illuminate \ View \ ViewServiceProvider :: klasse,
/ *
* Pakkeudbydere…
* /
/ *
* Udbydere af applikationsservices…
* /
App \ Providers \ AppServiceProvider :: klasse,
App \ Providers \ AuthServiceProvider :: klasse,
// App \ Providers \ BroadcastServiceProvider :: klasse,
App \ Providers \ EventServiceProvider :: klasse,
App \ Providers \ RouteServiceProvider :: klasse,
],
Endnu en gang tællede jeg, og der er 27 tjenester på listen! Nu har du muligvis brug for dem alle, men det er usandsynligt.
For eksempel bygger jeg tilfældigvis et REST API i øjeblikket, hvilket betyder, at jeg ikke har brug for Session Service Provider, View Service Provider osv. Og da jeg laver et par ting på min måde og ikke følger rammeforholdene , Jeg kan også deaktivere Auth Service Provider, Pagination Service Provider, Translator Service Provider osv. Alt i alt er næsten halvdelen af disse unødvendige til min brugssag.
Se et langt, hårdt kig på din ansøgning. Har det brug for alle disse tjenesteudbydere? Men for Guds skyld skal du ikke kommentere disse tjenester blindt og skub til produktion! Kør alle testene, kontroller ting manuelt på dev- og iscenesættelsesmaskiner, og vær meget paranoid, før du trækker i udløseren.
Vær klog med middleware-stakke
Når du har brug for en vis tilpasset behandling af den indkommende webanmodning, er det at oprette en ny mellemvare. Nu er det fristende at åbne app / Http / Kernel.php og sætte middleware i nettet eller api-stakken; på den måde bliver den tilgængelig på tværs af appen, og hvis den ikke gør noget påtrængende (som f.eks. at logge eller underrette).
Efterhånden som appen vokser, kan denne samling af global middleware imidlertid blive en stille byrde på appen, hvis alle (eller flertallet) af disse er til stede i enhver anmodning, selvom der ikke er nogen forretningsgrund til det.
Med andre ord skal du være forsigtig med hvor du tilføjer / anvender en ny mellemvare. Det kan være mere praktisk at tilføje noget globalt, men præstationsstraffen er meget høj i det lange løb. Jeg kender den smerte, du skulle være nødt til at gennemgå, hvis du selektivt skulle anvende middleware, hver gang der er en ny ændring, men det er en smerte, jeg vil gerne tage og anbefale!
Undgå ORM (til tider)
Mens Eloquent gør mange aspekter af DB-interaktion behagelige, kommer det til prisen for hastigheden. Som en kortlægning, skal ORM ikke kun hente poster fra databasen, men også instantisere modelobjekter og hydratere (udfyld dem) dem med kolonnedata.
Så hvis du udfører en simpel $ brugere = Bruger :: alle () og der er, for eksempel, 10.000 brugere, henter rammen 10.000 rækker fra databasen og internt foretager 10.000 nye bruger () og udfylder deres egenskaber med de relevante data . Dette er enorme mængder arbejde, der udføres bag kulisserne, og hvis databasen er det sted, du er i applikationen, bliver en flaskehals, er det til tider at omgå ORM.
Dette gælder især for komplekse SQL-forespørgsler, hvor du skulle hoppe en masse bøjler og skrive lukninger ved lukninger og stadig ende med en effektiv forespørgsel. I sådanne tilfælde foretrækkes at gøre en DB :: rå () og skrive forespørgslen manuelt.
Gå forbi det her præstationsundersøgelse, selv for enkle indsatser er Eloquent meget langsommere, når antallet af poster stiger:
Brug cache så meget som muligt
En af de bedst bevarede hemmeligheder ved optimering af webapplikationer er cache.
For uinitierede betyder cache-beregning og opbevaring af dyre resultater (dyre med hensyn til CPU- og hukommelsesforbrug) og simpelthen returnere dem, når den samme forespørgsel gentages.
For eksempel kan det i en e-handelsbutik komme på tværs af det af de 2 millioner produkter, for det meste er folk interesseret i dem, der er frisk på lager, inden for et bestemt prisklasse og for en bestemt aldersgruppe. Spørgsmål til databasen til disse oplysninger er spildende – da forespørgslen ikke ændres ofte, er det bedre at gemme disse resultater et sted, hvor vi hurtigt kan få adgang til.
Laravel har indbygget support til flere typer caching. Ud over at bruge en cachedriver og opbygge cachesystemet fra bunden, ønsker du måske at bruge nogle Laravel-pakker, der letter model caching, forespørgsel cache, etc.
Men vær opmærksom på, at ud over en bestemt forenklet brugssag kan forudbyggede cache-pakker medføre flere problemer, end de løser.
Foretrækker cache i hukommelsen
Når du cacheer noget i Laravel, har du flere muligheder for, hvor du kan gemme den resulterende beregning, der skal cache. Disse indstillinger er også kendt som cachedrivere. Så selvom det er muligt og helt rimeligt at bruge filsystemet til lagring af cache-resultater, er det ikke rigtig, hvad cache er beregnet til at være.
Ideelt set ønsker du at bruge en in-memory (bor helt i RAM) cache som Redis, Memcached, MongoDB osv., Så caching under højere belastning tjener en vigtig anvendelse snarere end at blive en flaskehals i sig selv.
Nu kan du tro, at det at have en SSD-disk er næsten det samme som at bruge en RAM-stick, men det er ikke engang tæt. Selv uformel benchmarks vis at RAM overgår SSD 10-20 gange, når det kommer til hastighed.
Mit yndlingssystem når det kommer til cache er Redis. Det er latterligt hurtigt (100.000 læseoperationer pr. Sekund er almindelige), og for meget store cache-systemer kan de udvikles til en klynge let.
Cache ruterne
Ligesom programkonfigurationen ændrer ruterne sig ikke meget over tid og er en ideel kandidat til cache. Dette gælder især, hvis du ikke kan stå store filer som mig og ender med at opdele din web.php og api.php over flere filer. En enkelt Laravel-kommando pakker alle de tilgængelige ruter og holder dem praktisk til fremtidig adgang:
php håndværkerute: cache
Og når du ender med at tilføje eller ændre ruter, skal du blot gøre:
php håndværkerute: klar
Billedoptimering og CDN
Billeder er hjertet og sjælen i de fleste webapplikationer. Tilfældigvis er de også de største forbrugere af båndbredde og en af de største årsager til langsomme apps / websteder. Hvis du blot gemmer de uploadede billeder naivt på serveren og sender dem tilbage i HTTP-svar, lader du en massiv optimeringsmulighed glide forbi.
Min første anbefaling er ikke at gemme billeder lokalt – der er problemet med datatab at håndtere, og afhængigt af hvilken geografisk region din kunde befinder sig i, kan dataoverførsel være smerteligt langsom.
Gå i stedet for en løsning som Cloudinary der automatisk ændrer størrelsen på og optimerer billederne mens du flyver.
Hvis det ikke er muligt, skal du bruge noget som Cloudflare til at cache og servere billeder, mens de er gemt på din server.
Og hvis det ikke er muligt, gør en stor forskel, at finjustere din webserversoftware lidt for at komprimere aktiver og lede den besøgende browser til at cache ting. Sådan ser et uddrag af Nginx-konfiguration ud:
server {
# fil trunkeret
# gzip-komprimeringsindstillinger
gzip på;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied enhver;
gzip_vary på;
# browser-cache-kontrol
placering ~ * \. (ico | css | js | gif | jpeg | jpg | png | woff | ttf | otf | svg | woff2 | eot) $ {
udløber 1d;
adgang_log slukket;
add_header Pragma public;
add_header Cache-Control "offentlig, max-alder = 86400";
}
}
Jeg er klar over, at billedoptimering ikke har noget at gøre med Laravel, men det er et så simpelt og kraftfuldt trick (og så ofte overses), at det ikke kunne hjælpe mig selv.
Autoloader-optimering
Autoloading er en pæn, ikke så gammel funktion i PHP, der uden tvivl reddede sproget fra undergang. Når det er sagt, tager processen med at finde og indlæse den relevante klasse ved at dechiffrere en given navneområde streng og kan undgås i produktionsinstallationer, hvor høj ydeevne er ønskelig. Igen har Laravel en enkelt-kommandoløsning på dette:
komponistinstallation –optimize-autoloader –no-dev
Bliv venner med køer
Køer er, hvordan du behandler ting, når der er mange af dem, og hver af dem tager et par millisekunder at gennemføre. Et godt eksempel er at sende e-mails – en udbredt sag i webapps er at skyde et par meddelelses-e-mails ud, når en bruger udfører nogle handlinger.
I et nyligt lanceret produkt kan du f.eks. Ønske, at virksomhedsledelsen (ca. 6-7 e-mail-adresser) skal underrettes, når nogen lægger en ordre over en bestemt værdi. Hvis vi antager, at din e-mail-gateway kan svare på din SMTP-anmodning på 500 ms, taler vi om en god 3-4 sekunders ventetid på brugeren, før ordrebekræftelsen starter. Et rigtig dårligt stykke UX, jeg er sikker på, at du enig.
Løsningen er at gemme job, når de kommer ind, fortælle brugeren, at alt gik godt og behandle dem (et par sekunder) senere. Hvis der er en fejl, kan de job, der står i kø, forsøges igen et par gange, før de erklæres for at have mislykket.
Kreditter: Microsoft.com
Mens et køsystem komplicerer opsætningen lidt (og tilføjer en del overvågningsomkostninger), er det uundværligt i en moderne webapplikation.
Aktiveringsoptimering (Laravel Mix)
For ethvert frontend-aktiv i din Laravel-applikation skal du sørge for, at der er en pipeline, der samler og minimerer alle aktivfiler. De, der har det godt med et bundlersystem som Webpack, Gulp, Pakke osv., Behøver ikke at bryde sig, men hvis du ikke gør det allerede, Laravel Mix er en solid anbefaling.
Mix er en let (og dejlig, i al ærlighed!) Indpakning omkring Webpack, der tager sig af alle dine CSS, SASS, JS osv., Filer til produktion. En typisk .mix.js-fil kan være så lille som denne og stadig udføre vidundere:
const mix = kræver (‘laravel-mix’);
mix.js (‘ressourcer / js / app.js’, ‘public / js’)
.sass (‘ressourcer / sass / app.scss’, ‘public / css’);
Dette tager automatisk sig af import, minificering, optimering og hele shebang, når du er klar til produktion og kører npm run-produktion. Mix tager sig af ikke kun traditionelle JS- og CSS-filer, men også Vue- og React-komponenter, som du muligvis har i din applikations workflow.
Mere info her!
Konklusion
Performanceoptimering er mere kunst end videnskab – det er vigtigt at vide, hvordan og hvor meget man skal gøre, end hvad man skal gøre. Når det er sagt, er der ingen ende på hvor meget og hvad du kan optimere i en Laravel-applikation.
Men uanset hvad du gør, vil jeg gerne give dig nogle råd om afsked – optimering bør ske, når der er en solid grund, og ikke fordi det lyder godt, eller fordi du er paranoid over appens ydeevne for 100.000+ brugere, mens du er i virkeligheden der er kun 10.
Hvis du ikke er sikker på, om du har brug for at optimere din app eller ej, behøver du ikke at sparke det sproglige hornets rede. En fungerende app, der føles kedelig, men gør nøjagtigt hvad den skal, er ti gange mere ønskværdig end en app, der er optimeret til en mutant hybrid-supermaskine, men falder fladt nu og da.
Og for at newbiew skal blive Laravel-master, så tjek dette online kursus.
Må dine apps køre meget, meget hurtigere!