Po vieno iš paskutinių darbo pokalbių nustebau, kai supratau, kad įmonė, į kurią kreipiausi, vis dar naudoja „Laravel“ – PHP sistemą, kurią išbandžiau maždaug prieš dešimtmetį. Tuo metu tai buvo tinkama, bet jei technologijos ir mados srityje yra pastovi, tai nuolatinis stilių ir koncepcijų kaitos ir naujovės. Jei esate „JavaScript“ programuotojas, tikriausiai esate susipažinęs su šiuo senu pokštu
1 programuotojas: „Man nepatinka ši nauja „JavaScript“ sistema!"
2 programuotojas: „Nereikia jaudintis. Tiesiog palaukite šešis mėnesius ir jį pakeis kitas!"
Dėl smalsumo nusprendžiau tiksliai pažiūrėti, kas atsitiks, kai išbandysime senus ir naujus. Žinoma, žiniatinklis yra pilnas etalonų ir tvirtinimų, iš kurių populiariausias tikriausiai yra TechEmpower Web Framework etalonas čia . Tačiau šiandien nieko tokio sudėtingo kaip jie nedarysime. Kad šis straipsnis netaptų Karas ir taika , ir kad turėsite nedidelę tikimybę, kad nemiegosite, kai baigsite skaityti. Galioja įprasti įspėjimai: jūsų kompiuteryje tai gali neveikti taip pat, skirtingos programinės įrangos versijos gali turėti įtakos veikimui, o Schrödingerio katė iš tikrųjų tapo kate zombiu, kuri buvo pusiau gyva ir pusiau mirusi tuo pačiu metu.
Šiam bandymui naudosiu savo nešiojamąjį kompiuterį, ginkluotą niūriu „i5“, kuriame veikia „Manjaro Linux“, kaip parodyta čia.
╰─➤ uname -a
Linux jimsredmi 5.10.174-1-MANJARO #1 SMP PREEMPT Tuesday Mar 21 11:15:28 UTC 2023 x86_64 GNU/Linux
╰─➤ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 126
model name : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
stepping : 5
microcode : 0xb6
cpu MHz : 990.210
cache size : 6144 KB
Mūsų kode kiekvienai užklausai bus trys paprastos užduotys:
Kas per idiotiškas testas, galite paklausti? Na, o jei pažvelgsite į šio puslapio tinklo užklausas, pastebėsite vieną, pavadintą sessionvars.js, kuris atlieka lygiai tą patį.
Matote, šiuolaikiniai tinklalapiai yra sudėtingi tvariniai, o viena iš dažniausiai atliekamų užduočių yra sudėtingų puslapių kaupimas talpykloje, kad būtų išvengta per didelės duomenų bazės serverio apkrovos.
Jei kaskart naudotojui paprašius sudėtingą puslapį pateiksime iš naujo, galime aptarnauti tik apie 600 vartotojų per sekundę.
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 186.83ms 174.22ms 1.06s 81.16%
Req/Sec 166.11 58.84 414.00 71.89%
6213 requests in 10.02s, 49.35MB read
Requests/sec: 619.97
Transfer/sec: 4.92MB
Tačiau jei šį puslapį talpykloje saugosime kaip statinį HTML failą ir leisime „Nginx“ greitai jį išmesti vartotojui pro langą, galime aptarnauti 32 000 vartotojų per sekundę, padidindami našumą 50 kartų.
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.03ms 511.95us 6.87ms 68.10%
Req/Sec 8.20k 1.15k 28.55k 97.26%
327353 requests in 10.10s, 2.36GB read
Requests/sec: 32410.83
Transfer/sec: 238.99MB
Statinis index.en.html yra dalis, kuri atitenka visiems, o į sessionvars.js siunčiamos tik tos dalys, kurios skiriasi pagal vartotoją. Tai ne tik sumažina duomenų bazės apkrovą ir sukuria geresnę patirtį mūsų vartotojams, bet ir sumažina kvantinę tikimybę, kad mūsų serveris spontaniškai išgaruos dėl deformacijos branduolio pažeidimo, kai užpuls klingonai.
Kiekvienos sistemos grąžinamas kodas turės vieną paprastą reikalavimą: parodykite vartotojui, kiek kartų jis atnaujino puslapį, sakydamas „Skaičius yra x“. Kad viskas būtų paprasta, kol kas nesiliesime nuo „Redis“ eilių, „Kubernetes“ komponentų ar „AWS Lambdas“.
Kiekvieno vartotojo seanso duomenys bus išsaugoti PostgreSQL duomenų bazėje.
Ir ši duomenų bazės lentelė bus sutrumpinta prieš kiekvieną testą.
Paferos šūkis yra paprastas, bet veiksmingas... bet kokiu atveju už tamsiausios laiko juostos...
Gerai, dabar pagaliau galime pradėti tepti rankas. Mes praleisime Laravel sąranką, nes tai tik kompozitorių ir amatininkų krūva komandas.
Pirmiausia nustatysime duomenų bazės nustatymus .env faile
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Tada nustatysime vieną atsarginį maršrutą, kuris siunčia kiekvieną užklausą mūsų valdikliui.
Route::fallback(SessionController::class);
Ir nustatykite valdiklį, kad būtų rodomas skaičius. „Laravel“ pagal numatytuosius nustatymus saugo seansus duomenų bazėje. Ji taip pat suteikia session()
funkcija susieti su mūsų seanso duomenimis, todėl mūsų puslapiui pateikti užteko kelių kodo eilučių.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Nustačius php-fpm ir Nginx, mūsų puslapis atrodo gana gerai...
╰─➤ php -v
PHP 8.2.2 (cli) (built: Feb 1 2023 08:33:04) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.2, Copyright (c) Zend Technologies
with Xdebug v3.2.0, Copyright (c) 2002-2022, by Derick Rethans
╰─➤ sudo systemctl restart php-fpm
╰─➤ sudo systemctl restart nginx
Bent jau tol, kol pamatysime testo rezultatus...
PHP/Laravel
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.08s 546.33ms 1.96s 65.71%
Req/Sec 12.37 7.28 40.00 56.64%
211 requests in 10.03s, 177.21KB read
Socket errors: connect 0, read 0, write 0, timeout 176
Requests/sec: 21.04
Transfer/sec: 17.67KB
Ne, tai nėra rašybos klaida. Mūsų bandomoji mašina pakeitė nuo 600 užklausų per sekundę, pateikiant sudėtingą puslapį... iki 21 užklausos per sekundę, pateikiant "Skaičius yra 1".
Taigi, kas nutiko? Ar kažkas negerai įdiegus PHP? Ar Nginx kažkaip sulėtėja, kai sąveikauja su php-fpm?
Pakartokime šį puslapį naudodami gryną PHP kodą.
<?php
// ====================================================================
function uuid4()
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
// ====================================================================
function Query($db, $query, $params = [])
{
$s = $db->prepare($query);
$s->setFetchMode(PDO::FETCH_ASSOC);
$s->execute(array_values($params));
return $s;
}
// ********************************************************************
session_start();
$sessionid = 0;
if (isset($_SESSION['sessionid']))
{
$sessionid = $_SESSION['sessionid'];
}
if (!$sessionid)
{
$sessionid = uuid4();
$_SESSION['sessionid'] = $sessionid;
}
$db = new PDO('pgsql:host=127.0.0.1 dbname=sessiontest user=sessiontest password=sessiontest');
$data = 0;
try
{
$result = Query(
$db,
'SELECT data FROM usersessions WHERE uid = ?',
[$sessionid]
)->fetchAll();
if ($result)
{
$data = json_decode($result[0]['data'], 1);
}
} catch (Exception $e)
{
echo $e;
Query(
$db,
'CREATE TABLE usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)'
);
}
if (!$data)
{
$data = ['count' => 0];
}
$data['count']++;
if ($data['count'] == 1)
{
Query(
$db,
'INSERT INTO usersessions(uid, data)
VALUES(?, ?)',
[$sessionid, json_encode($data)]
);
} else
{
Query(
$db,
'UPDATE usersessions
SET data = ?
WHERE uid = ?',
[json_encode($data), $sessionid]
);
}
echo 'Count is ' . $data['count'];
Dabar panaudojome 98 kodo eilutes, kad padarytume tai, ką padarė keturios kodo eilutės (ir daugybė konfigūravimo darbų) Laravel. (Žinoma, jei tinkamai apdorotume klaidas ir vartotojui skirtus pranešimus, tai būtų maždaug dvigubai daugiau eilučių.) Galbūt galime pasiekti 30 užklausų per sekundę?
PHP/Pure PHP
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 140.79ms 27.88ms 332.31ms 90.75%
Req/Sec 178.63 58.34 252.00 61.01%
7074 requests in 10.04s, 3.62MB read
Requests/sec: 704.46
Transfer/sec: 369.43KB
Oho! Atrodo, kad mūsų PHP diegime nėra nieko blogo. Gryna PHP versija atlieka 700 užklausų per sekundę.
Jei PHP nėra nieko blogo, galbūt neteisingai sukonfigūravome Laravel?
Ištyrus žiniatinklį dėl konfigūracijos problemų ir našumo patarimų, du iš populiariausių metodų buvo konfigūracijos ir maršruto duomenų kaupimas talpykloje, kad būtų išvengta jų apdorojimo kiekvienai užklausai. Todėl pasinaudosime jų patarimais ir išbandysime šiuos patarimus.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
Komandinėje eilutėje viskas atrodo gerai. Pakartokime etaloną.
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.13s 543.50ms 1.98s 61.90%
Req/Sec 25.45 13.39 50.00 55.77%
289 requests in 10.04s, 242.15KB read
Socket errors: connect 0, read 0, write 0, timeout 247
Requests/sec: 28.80
Transfer/sec: 24.13KB
Na, dabar padidinome našumą nuo 21,04 iki 28,80 užklausos per sekundę, o tai yra dramatiškas padidėjimas beveik 37 %! Tai būtų gana įspūdinga bet kuriam programinės įrangos paketui... išskyrus tai, kad vis dar atliekame tik 1/24 grynos PHP versijos užklausų skaičiaus.
Jei manote, kad kažkas negerai atliekant šį testą, turėtumėte pasikalbėti su Lucinda PHP sistemos autoriumi. Savo bandymų rezultatuose jis turi Lucinda sumušė Laravelį 36x HTML užklausoms ir 90x JSON užklausoms.
Išbandęs savo mašiną su Apache ir Nginx, neturiu jokios priežasties juo abejoti. Laravel yra tikrai teisingas kad lėtai! PHP pats savaime nėra toks blogas, bet pridėjus visą papildomą apdorojimą, kurį Laravel prideda prie kiekvienos užklausos, man labai sunku rekomenduoti Laravel kaip pasirinkimą 2023 m.
PHP/Wordpress paskyros apie 40 % visų žiniatinklyje esančių svetainių , todėl ji yra pati dominuojanti sistema. Asmeniškai aš manau, kad populiarumas nebūtinai virsta kokybe, taip pat man kyla staigus nevaldomas potraukis to nepaprasto gurmaniško maisto populiariausias restoranas pasaulyje ... McDonald's. Kadangi jau išbandėme gryną PHP kodą, neketiname testuoti paties Wordpress, nes viskas, kas susiję su Wordpress, neabejotinai būtų mažesnė nei 700 užklausų per sekundę, kurią stebėjome naudodami gryną PHP.
„Django“ yra dar viena populiari sistema, kuri egzistuoja ilgą laiką. Jei jį naudojote anksčiau, tikriausiai su malonumu prisimenate įspūdingą duomenų bazės administravimo sąsają ir tai, kaip erzino viską konfigūruoti taip, kaip norėjote. Pažiūrėkime, kaip gerai „Django“ veikia 2023 m., ypač su nauja ASGI sąsaja, kuri buvo pridėta nuo 4.0 versijos.
„Django“ nustatymas yra nepaprastai panašus į „Laravel“ nustatymą, nes jie abu buvo iš to amžiaus, kai MVC architektūros buvo stilingos ir teisingos. Praleisime nuobodžią konfigūraciją ir pereisime tiesiai prie vaizdo nustatymo.
from django.shortcuts import render
from django.http import HttpResponse
# =====================================================================
def index(request):
count = request.session.get('count', 0)
count += 1
request.session['count'] = count
return HttpResponse(f"Count is {count}")
Keturios kodo eilutės yra tokios pat kaip ir Laravel versijoje. Pažiūrėkime, kaip jis veikia.
╰─➤ python --version
Python 3.10.9
Python/Django
╰─➤ gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 djangotest.asgi
[2023-03-21 15:20:38 +0800] [2886633] [INFO] Starting gunicorn 20.1.0
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000/sessiontest/
Running 10s test @ http://127.0.0.1:8000/sessiontest/
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 277.71ms 142.84ms 835.12ms 69.93%
Req/Sec 91.21 57.57 230.00 61.04%
3577 requests in 10.06s, 1.46MB read
Requests/sec: 355.44
Transfer/sec: 148.56KB
Visai neblogai esant 355 užklausoms per sekundę. Tai tik pusė grynos PHP versijos našumo, bet taip pat 12 kartų didesnis nei Laravel versijos. Atrodo, kad „Django“ ir „Laravel“ nėra varžybos.
Be didesnių visko, įskaitant virtuvės kriauklę, rėmų, yra ir mažesnių karkasų, kurie atlieka tam tikrą pagrindinę sąranką, o visa kita leidžia jums tvarkyti. Vienas iš geriausių naudojamų yra „Flask“ ir jo ASGI atitikmuo „Quart“. Mano paties PaferaPy Framework yra pastatytas ant „Flask“, todėl gerai žinau, kaip lengva atlikti darbus išlaikant našumą.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test
import json
import psycopg
import uuid
from flask import Flask, session, redirect, url_for, request, current_app, g, abort, send_from_directory
from flask.sessions import SecureCookieSessionInterface
app = Flask('pafera')
app.secret_key = b'secretkey'
dbconn = 0
# =====================================================================
@app.route('/', defaults={'path': ''}, methods = ['GET', 'POST'])
@app.route('/<path:path>', methods = ['GET', 'POST'])
def index(path):
"""Handles all requests for the server.
We route all requests through here to handle the database and session
logic in one place.
"""
global dbconn
if not dbconn:
dbconn = psycopg.connect('dbname=sessiontest user=sessiontest password=sessiontest')
cursor = dbconn.execute('''
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)
''')
cursor.close()
dbconn.commit()
sessionid = session.get('sessionid', 0)
if not sessionid:
sessionid = uuid.uuid4().hex
session['sessionid'] = sessionid
cursor = dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
row = cursor.fetchone()
count = json.loads(row[0])['count'] if row else 0
count += 1
newdata = json.dumps({'count': count})
if count == 1:
cursor.execute("""
INSERT INTO usersessions(uid, data)
VALUES(%s, %s)
""",
[sessionid, newdata]
)
else:
cursor.execute("""
UPDATE usersessions
SET data = %s
WHERE uid = %s
""",
[newdata, sessionid]
)
cursor.close()
dbconn.commit()
return f'Count is {count}'
Kaip matote, Flask scenarijus yra trumpesnis nei grynas PHP scenarijus. Manau, kad iš visų kalbų, kurias naudoju, „Python“ yra turbūt pati išraiškingiausia kalba, kai kalbama apie įvestus klavišų paspaudimus. Dėl skliaustų ir skliaustų trūkumo, sąrašo ir diktavimo supratimo bei blokavimo, paremto įtrauka, o ne kabliataškiais, „Python“ yra gana paprastas, tačiau galingas.
Deja, „Python“ taip pat yra lėčiausia bendrosios paskirties kalba, nepaisant to, kiek programinės įrangos joje parašyta. „Python“ bibliotekų skaičius yra maždaug keturis kartus didesnis nei panašių kalbų ir apima daugybę domenų, tačiau niekas nepasakytų, kad „Python“ yra greitas ar našus už nišų, tokių kaip „NumPy“, ribų.
Pažiūrėkime, kaip mūsų „Flask“ versija palyginama su ankstesnėmis sistemomis.
Python/Flask
╰─➤ gunicorn --access-logfile - -w 4 flasksite:app
[2023-03-21 15:32:49 +0800] [2856296] [INFO] Starting gunicorn 20.1.0
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 91.84ms 11.97ms 149.63ms 86.18%
Req/Sec 272.04 39.05 380.00 74.50%
10842 requests in 10.04s, 3.27MB read
Requests/sec: 1080.28
Transfer/sec: 333.37KB
Mūsų Flask scenarijus iš tikrųjų yra greitesnis nei mūsų gryna PHP versija!
Jei jus tai nustebina, turėtumėte suprasti, kad mūsų programa „Flask“ atlieka visą inicijavimą ir konfigūraciją, kai paleidžiame „Gunicorn“ serverį, o PHP iš naujo vykdo scenarijų kiekvieną kartą, kai gaunama nauja užklausa. Tai prilygsta Flaskui, kuris yra jaunas, nekantrus taksi vairuotojas, kuris jau užvedė automobilį ir laukia šalia kelio, o PHP yra senas vairuotojas, kuris lieka savo namuose ir laukia skambučio ir tik tada važiuoja atvykti tavęs pasiimti. Kadangi esate senos mokyklos vaikinas ir atėjo iš tų laikų, kai PHP buvo nuostabus paprastų HTML ir SHTML failų pakeitimas, šiek tiek liūdna suvokti, kiek laiko praėjo, tačiau dėl dizaino skirtumų PHP tikrai sunku konkuruoja su Python, Java ir Node.js serveriais, kurie tiesiog lieka atmintyje ir apdoroja užklausas taip, kaip žonglieriai.
Kol kas „Flask“ gali būti greičiausia sistema, bet iš tikrųjų tai gana sena programinė įranga. Python bendruomenė prieš porą metų perėjo prie naujesnių asimetrinių ASGI serverių, ir, žinoma, aš pats perėjau su jais.
Naujausia Pafera Framework versija, PaferaPyAsync , paremtas Starlette. Nors yra ASGI „Flask“ versija, pavadinta „Quart“, „Quart“ ir „Starlette“ veikimo skirtumų pakako, kad galėčiau pakeisti kodą „Starlette“.
Asichroninis programavimas gali gąsdinti daugelį žmonių, tačiau iš tikrųjų tai nėra sudėtinga koncepcija, nes Node.js vaikinai išpopuliarino šią koncepciją daugiau nei prieš dešimtmetį.
Mes kovojome su lygiagrečiais su kelių gijų, daugiasluoksniu apdorojimu, paskirstytu skaičiavimu, pažadų grandine ir visais tais linksmais laikais, kurie per anksti senstelėjo ir išdžiovino daugelį programuotojų veteranų. Dabar mes tiesiog rašome async
prieš mūsų funkcijas ir await
prieš bet kokį kodą, kurio vykdymas gali užtrukti. Jis iš tiesų yra daug žodesnis nei įprastas kodas, tačiau jį naudoti mažiau erzina, nei susidoroti su sinchronizavimo primityvais, pranešimų perdavimu ir pažadų įgyvendinimu.
Mūsų „Starlette“ failas atrodo taip:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test
import json
import uuid
import psycopg
from starlette.applications import Starlette
from starlette.responses import Response, PlainTextResponse, JSONResponse, RedirectResponse, HTMLResponse
from starlette.routing import Route, Mount, WebSocketRoute
from starlette_session import SessionMiddleware
dbconn = 0
# =====================================================================
async def index(R):
global dbconn
if not dbconn:
dbconn = await psycopg.AsyncConnection.connect('dbname=sessiontest user=sessiontest password=sessiontest')
cursor = await dbconn.execute('''
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)
''')
await cursor.close()
await dbconn.commit()
sessionid = R.session.get('sessionid', 0)
if not sessionid:
sessionid = uuid.uuid4().hex
R.session['sessionid'] = sessionid
cursor = await dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
row = await cursor.fetchone()
count = json.loads(row[0])['count'] if row else 0
count += 1
newdata = json.dumps({'count': count})
if count == 1:
await cursor.execute("""
INSERT INTO usersessions(uid, data)
VALUES(%s, %s)
""",
[sessionid, newdata]
)
else:
await cursor.execute("""
UPDATE usersessions
SET data = %s
WHERE uid = %s
""",
[newdata, sessionid]
)
await cursor.close()
await dbconn.commit()
return PlainTextResponse(f'Count is {count}')
# *********************************************************************
app = Starlette(
debug = True,
routes = [
Route('/{path:path}', index, methods = ['GET', 'POST']),
],
)
app.add_middleware(
SessionMiddleware,
secret_key = 'testsecretkey',
cookie_name = "pafera",
)
Kaip matote, jis beveik nukopijuotas ir įklijuotas iš mūsų „Flask“ scenarijaus, atlikus tik kelis maršruto pakeitimus ir async/await
raktinius žodžius.
Kiek patobulinimų iš tikrųjų gali mums suteikti nukopijuotas ir įklijuotas kodas?
Python/Starlette
╰─➤ gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 starlettesite:app 130 ↵
[2023-03-21 15:42:34 +0800] [2856220] [INFO] Starting gunicorn 20.1.0
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 21.85ms 10.45ms 67.29ms 55.18%
Req/Sec 1.15k 170.11 1.52k 66.00%
45809 requests in 10.04s, 13.85MB read
Requests/sec: 4562.82
Transfer/sec: 1.38MB
Turime naują čempioną, ponios ir ponai! Ankstesnis mūsų rekordas buvo mūsų grynoji PHP versija 704 užklausų per sekundę greičiu, kurią vėliau aplenkė mūsų Flask versija (1080 užklausų per sekundę). Mūsų „Starlette“ scenarijus sutriuškina visus ankstesnius varžovus 4562 užklausų per sekundę greičiu, o tai reiškia 6 kartus patobulinimą, palyginti su grynu PHP, ir 4 kartus patobulinimu, palyginti su „Flask“.
Jei dar nepakeitėte WSGI Python kodo į ASGI, dabar gali būti tinkamas laikas pradėti.
Iki šiol apėmėme tik PHP ir Python sistemas. Tačiau didelė dalis pasaulio iš tikrųjų savo svetainėse naudoja Java, DotNet, Node.js, Ruby on Rails ir kitas tokias technologijas. Tai jokiu būdu nėra išsami visų pasaulio ekosistemų ir biomų apžvalga, todėl norėdami išvengti organinės chemijos programavimo atitikmens, pasirinksime tik tas sistemas, kurioms lengviausia įvesti kodą. iš kurių Java tikrai nėra.
Nebent pasislėpėte po savo K&R C arba Knuth's kopija Kompiuterių programavimo menas pastaruosius penkiolika metų tikriausiai girdėjote apie Node.js. Tie iš mūsų, kurie dirbame nuo „JavaScript“ pradžios, yra neįtikėtinai išsigandę, nustebę arba abu dėl šiuolaikinio JavaScript būsenos, tačiau negalima paneigti, kad „JavaScript“ tapo jėga, su kuria reikia atsižvelgti ir serveriuose. kaip naršyklės. Juk dabar kalboje turime net 64 bitų sveikuosius skaičius! Tai kur kas geriau, nei viskas, kas saugoma 64 bitų plūdėse!
„ExpressJS“ yra turbūt lengviausiai naudojamas Node.js serveris, todėl mes sukursime greitą ir nešvarią „Node.js/ExpressJS“ programėlę, kuri aptarnautų mūsų skaitiklį.
/**********************************************************************
* Simple session test using ExpressJS.
**********************************************************************/
var L = console.log;
var uuid = require('uuid4');
var express = require('express');
var session = require('express-session');
var MemoryStore = require('memorystore')(session);
var { Client } = require('pg')
var db = 0;
var app = express();
const PORT = 8000;
//session middleware
app.use(
session({
secret: "secretkey",
saveUninitialized: true,
resave: false,
store: new MemoryStore({
checkPeriod: 1000 * 60 * 60 * 24 // prune expired entries every 24h
})
})
);
app.get('/',
async function(req,res)
{
if (!db)
{
db = new Client({
user: 'sessiontest',
host: '127.0.0.1',
database: 'sessiontest',
password: 'sessiontest'
});
await db.connect();
await db.query(`
CREATE TABLE IF NOT EXISTS usersessions(
uid TEXT PRIMARY KEY,
data TEXT
)`,
[]
);
};
var session = req.session;
if (!session.sessionid)
{
session.sessionid = uuid();
}
var row = 0;
let queryresult = await db.query(`
SELECT data::TEXT
FROM usersessions
WHERE uid = $1`,
[session.sessionid]
);
if (queryresult && queryresult.rows.length)
{
row = queryresult.rows[0].data;
}
var count = 0;
if (row)
{
var data = JSON.parse(row);
data.count += 1;
count = data.count;
await db.query(`
UPDATE usersessions
SET data = $1
WHERE uid = $2
`,
[JSON.stringify(data), session.sessionid]
);
} else
{
await db.query(`
INSERT INTO usersessions(uid, data)
VALUES($1, $2)`,
[session.sessionid, JSON.stringify({count: 1})]
);
count = 1;
}
res.send(`Count is ${count}`);
}
);
app.listen(PORT, () => console.log(`Server Running at port ${PORT}`));
Šį kodą iš tikrųjų buvo lengviau parašyti nei Python versijas, nors vietinis „JavaScript“ tampa gana sudėtingas, kai programos tampa didesnės, ir visi bandymai tai ištaisyti, pvz., „TypeScript“, greitai tampa išsamesni nei „Python“.
Pažiūrėkime, kaip tai veikia!
Node.js/ExpressJS
╰─➤ node --version v19.6.0
╰─➤ NODE_ENV=production node nodejsapp.js 130 ↵
Server Running at port 8000
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 90.41ms 7.20ms 188.29ms 85.16%
Req/Sec 277.15 37.21 393.00 81.66%
11018 requests in 10.02s, 3.82MB read
Requests/sec: 1100.12
Transfer/sec: 390.68KB
Galbūt esate girdėję senovinių (vis dėlto senovinių pagal interneto standartus...) liaudies pasakų apie Node.js' greičiu, o šios istorijos dažniausiai yra tikros dėl įspūdingo darbo, kurį „Google“ atliko su V8 JavaScript varikliu. Tačiau šiuo atveju, nors mūsų greitoji programa pranoksta „Flask“ scenarijų, jos vienos gijos pobūdį nugali keturi asinchroniniai procesai, kuriuos valdo Starlette Knight, kuris sako „Ni!“.
Sulaukime daugiau pagalbos!
╰─➤ pm2 start nodejsapp.js -i 4
[PM2] Spawning PM2 daemon with pm2_home=/home/jim/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/jim/projects/paferarust/nodejsapp.js in cluster_mode (4 instances)
[PM2] Done.
┌────┬──────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ nodejsapp │ default │ N/A │ cluster │ 37141 │ 0s │ 0 │ online │ 0% │ 64.6mb │ jim │ disabled │
│ 1 │ nodejsapp │ default │ N/A │ cluster │ 37148 │ 0s │ 0 │ online │ 0% │ 64.5mb │ jim │ disabled │
│ 2 │ nodejsapp │ default │ N/A │ cluster │ 37159 │ 0s │ 0 │ online │ 0% │ 56.0mb │ jim │ disabled │
│ 3 │ nodejsapp │ default │ N/A │ cluster │ 37171 │ 0s │ 0 │ online │ 0% │ 45.3mb │ jim │ disabled │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Gerai! Dabar mūšis keturi prieš keturis! Atlikime etaloną!
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 45.09ms 19.89ms 176.14ms 60.22%
Req/Sec 558.93 97.50 770.00 66.17%
22234 requests in 10.02s, 7.71MB read
Requests/sec: 2218.69
Transfer/sec: 787.89KB
Vis dar ne visai Starlette lygiu, bet tai nėra blogai greitam penkių minučių „JavaScript“ įsilaužimui. Iš mano paties testavimo matyti, kad šis scenarijus iš tikrųjų šiek tiek sulaikomas duomenų bazės sąsajos lygmeniu, nes node-postgres nėra toks efektyvus kaip psycopg Python. Perjungus į sqlite kaip duomenų bazės tvarkyklę, per sekundę gaunama daugiau nei 3000 užklausų dėl to paties ExpressJS kodo.
Svarbiausia pažymėti, kad nepaisant lėto Python vykdymo greičio, ASGI sistemos iš tikrųjų gali konkuruoti su Node.js sprendimais tam tikriems darbo krūviams.
Taigi dabar artėjame prie kalno viršūnės, o kalbėdamas apie kalną turiu omenyje aukščiausius pelių ir vyrų užregistruotus etaloninius balus.
Jei pažvelgsite į daugumą žiniatinklyje pasiekiamų sistemos etalonų, pastebėsite, kad yra dvi kalbos, kurios dažniausiai dominuoja: C++ ir Rust. Su C++ dirbau nuo 90-ųjų ir netgi turėjau savo Win32 C++ sistemą, kai dar nebuvo MFC/ATL, todėl turiu daug patirties dirbant su kalba. Nelabai smagu dirbti su kažkuo, kai tai jau žinai, todėl vietoj to sukursime Rust versiją. ;)
„Rust“ yra palyginti nauja programavimo kalbų prasme, tačiau ji man tapo smalsumo objektu, kai Linusas Torvaldsas paskelbė, kad priims Rust kaip „Linux“ branduolio programavimo kalbą. Mums, vyresniems programuotojams, tai yra maždaug tas pats, kas pasakyti, kad šis naujas išgalvotas naujojo amžiaus hipių dalykas bus nauja JAV Konstitucijos pataisa.
Dabar, kai esate patyręs programuotojas, esate linkęs ne taip greitai, kaip tai daro jaunesni žmonės, nes kitaip galite nudegti dėl greitų kalbos ar bibliotekų pasikeitimų. (Kiekvienas, naudojęs pirmąją AngularJS versiją, žinos, apie ką aš kalbu.) Rust vis dar yra eksperimentinio kūrimo stadijoje, ir man juokinga, kad tiek daug kodo pavyzdžių internete net nėra daugiau kompiliuoti su dabartinėmis paketų versijomis.
Tačiau negalima paneigti „Rust“ programų rodomo našumo. Jei niekada nebandėte ripgrep arba fd-rasti Naudodami didelius šaltinio kodo medžius, tikrai turėtumėte juos išbandyti. Jie netgi pasiekiami daugeliui „Linux“ platinimų tiesiog iš paketų tvarkyklės. Su Rust žodiškumą keičiate į pasirodymą... a daug daugiažodiškumas a daug atlikimo.
Visas Rust kodas yra šiek tiek didelis, todėl mes tiesiog pažvelgsime į atitinkamus tvarkykles čia:
// =====================================================================
pub async fn RunQuery(
db: &web::Data<Pool>,
query: &str,
args: &[&(dyn ToSql + Sync)]
) -> Result<Vec<tokio_postgres::row::Row>, tokio_postgres::Error>
{
let client = db.get().await.unwrap();
let statement = client.prepare_cached(query).await.unwrap();
client.query(&statement, args).await
}
// =====================================================================
pub async fn index(
req: HttpRequest,
session: Session,
db: web::Data<Pool>,
) -> Result<HttpResponse, Error>
{
let mut count = 1;
if let Some(sessionid) = session.get::<String>("sessionid")?
{
let rows = RunQuery(
&db,
"SELECT data
FROM usersessions
WHERE uid = $1",
&[&sessionid]
).await.unwrap();
if rows.is_empty()
{
let jsondata = serde_json::json!({
"count": 1,
}).to_string();
RunQuery(
&db,
"INSERT INTO usersessions(uid, data)
VALUES($1, $2)",
&[&sessionid, &jsondata]
).await
.expect("Insert failed!");
} else
{
let jsonstring:&str = rows[0].get(0);
let countdata: CountData = serde_json::from_str(jsonstring)?;
count = countdata.count;
count += 1;
let jsondata = serde_json::json!({
"count": count,
}).to_string();
RunQuery(
&db,
"UPDATE usersessions
SET data = $1
WHERE uid = $2
",
&[&jsondata, &sessionid]
).await
.expect("Update failed!");
}
} else
{
let sessionid = Uuid::new_v4().to_string();
let jsondata = serde_json::json!({
"count": 1,
}).to_string();
RunQuery(
&db,
"INSERT INTO usersessions(uid, data)
VALUES($1, $2)",
&[&sessionid, &jsondata]
).await
.expect("Insert failed!");
session.insert("sessionid", sessionid)?;
}
Ok(HttpResponse::Ok().body(format!(
"Count is {:?}",
count
)))
}
Tai daug sudėtingiau nei Python/Node.js versijos...
Rust/Actix
╰─➤ cargo run --release
[2023-03-21T23:37:25Z INFO actix_server::builder] starting 4 workers
Server running at http://127.0.0.1:8888/
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1:8888
Running 10s test @ http://127.0.0.1:8888
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 9.93ms 3.90ms 77.18ms 94.87%
Req/Sec 2.59k 226.41 2.83k 89.25%
102951 requests in 10.03s, 24.59MB read
Requests/sec: 10267.39
Transfer/sec: 2.45MB
Ir daug našesnis!
Mūsų „Rust“ serveris, kuriame naudojamas „Actix/deadpool_postgres“, pranoksta ankstesnį čempioną „Starlette“ +125%, „ExpressJS“ +362%, o gryną PHP +1366%. (Paliksiu našumo deltą su Laravel versija kaip pratimą skaitytojui.)
Pastebėjau, kad išmokti Rust kalbą buvo sunkiau nei kitų kalbų, nes joje yra daug daugiau trūkumų nei bet kuriame kitame, kurį mačiau už 6502 asamblėjos ribų, bet jei jūsų Rust serveris gali priimti 14 kartų daugiau vartotojus kaip savo PHP serverį, tada galbūt visgi yra ko gauti perjungus technologijas. Štai kodėl kita „Pafera Framework“ versija bus pagrįsta „Rust“. Mokymosi kreivė yra daug aukštesnė nei scenarijų kalbų, tačiau našumas bus to vertas. Jei negalite skirti laiko išmokti Rust, savo technologijų paketą pagrįsti Starlette arba Node.js taip pat nėra blogas sprendimas.
Per pastaruosius dvidešimt metų nuo pigių statinių prieglobos svetainių perėjome prie bendro prieglobos su LAMP paketais ir nuomojame VPS į AWS, Azure ir kitas debesies paslaugas. Šiais laikais daugelis kompanijų yra patenkintos priimdamos sprendimus dėl projektavimo, remdamosi tuo, kas yra prieinama arba pigiausia, nes atsiradus patogioms debesijos paslaugoms tapo lengva išmesti daugiau aparatinės įrangos lėtiems serveriams ir programoms. Tai suteikė jiems didelį trumpalaikį pelną ilgalaikės techninės skolos kaina.
Prieš 70 metų tarp Sovietų Sąjungos ir JAV vyko didžiulės kosminės lenktynės. Sovietai laimėjo daugumą ankstyvųjų etapų. Jie turėjo pirmąjį palydovą Sputnik, pirmąjį šunį kosmose Laikoje, pirmąjį mėnulio erdvėlaivį Luna 2, pirmąjį vyrą ir moterį kosmose Jurijus Gagarinas ir Valentina Tereškova ir t.t.
Tačiau jie pamažu kaupė technines skolas.
Nors sovietai buvo pirmieji kiekvienam iš šių pasiekimų, jų inžineriniai procesai ir tikslai paskatino juos sutelkti dėmesį į trumpalaikius iššūkius, o ne į ilgalaikį įgyvendinamumą. Jie laimėdavo kiekvieną kartą šuoliuodami, tačiau vis labiau pavargdavo ir lėtesni, o varžovai toliau nuosekliai žengdavo į finišo liniją.
Kai Neilas Armstrongas žengė istorinius žingsnius Mėnulyje per tiesioginę televizijos transliaciją, amerikiečiai perėmė lyderio poziciją, o paskui liko ten, nes sovietų programa šlubavo. Tai niekuo nesiskiria nuo šiandieninių kompanijų, kurios sutelkė dėmesį į kitą didelį dalyką, kitą didelį pelną ar kitą didelę technologiją, o nesugebėjo sukurti tinkamų įpročių ir strategijų ilgam laikui.
Būti pirmam rinkoje nereiškia, kad tapsite dominuojančiu tos rinkos žaidėju. Arba, skirdami laiko viską daryti teisingai, negarantuojate sėkmės, bet tikrai padidinate ilgalaikių laimėjimų tikimybę. Jei esate savo įmonės technologijų lyderis, pasirinkite tinkamą darbo krūvio kryptį ir įrankius. Neleiskite, kad populiarumas pakeistų našumą ir efektyvumą.
Norite atsisiųsti 7z failą, kuriame yra Rust, ExpressJS, Flask, Starlette ir Pure PHP scenarijai?
Apie Autorius |
|
![]() |
Jimas programuoja nuo tada, kai 90-aisiais gavo IBM PS/2. Iki šiol jis vis dar renkasi HTML ir SQL rašymą ranka, o savo darbe daugiausia dėmesio skiria efektyvumui ir teisingumui. |