Po jednom z mojich posledných pracovných pohovorov som si s prekvapením uvedomil, že spoločnosť, do ktorej som sa prihlásil, stále používa Laravel, PHP framework, ktorý som vyskúšal asi pred desiatimi rokmi. Na tú dobu to bolo slušné, ale ak existuje jedna konštanta v technológii a móde, je to neustála zmena a pretváranie štýlov a konceptov. Ak ste programátor JavaScript, pravdepodobne poznáte tento starý vtip
Programátor 1: "Nepáči sa mi tento nový rámec JavaScriptu!"
Programátor 2: „Netreba sa báť. Len počkaj šesť mesiacov a nahradí sa iná!"
Zo zvedavosti som sa rozhodol presne vidieť, čo sa stane, keď otestujeme staré a nové. Samozrejme, web je plný benchmarkov a tvrdení, z ktorých je asi najpopulárnejší Benchmarky TechEmpower Web Framework nájdete tu . Dnes však neurobíme nič také zložité ako oni. Udržíme veci pekné a jednoduché, aby sa tento článok nezmenil na Vojna a mier , a že budete mať malú šancu, že zostanete hore, kým dočítate. Platia obvyklé upozornenia: na vašom počítači to nemusí fungovať rovnako, rôzne verzie softvéru môžu ovplyvniť výkon a Schrödingerova mačka sa v skutočnosti stala zombie mačkou, ktorá bola napoly živá a napoly mŕtva v rovnakom čase.
Na tento test budem používať svoj laptop vyzbrojený malým i5 so systémom Manjaro Linux, ako je znázornené tu.
╰─➤ 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
Náš kód bude mať pre každú požiadavku tri jednoduché úlohy:
Môžete sa opýtať, čo je to za idiotský test? Ak sa pozriete na sieťové požiadavky pre túto stránku, všimnete si stránku s názvom sessionvars.js, ktorá robí presne to isté.
Viete, moderné webové stránky sú komplikované stvorenia a jednou z najbežnejších úloh je ukladanie zložitých stránok do vyrovnávacej pamäte, aby sa predišlo nadmernému zaťaženiu databázového servera.
Ak znova vykreslíme zložitú stránku zakaždým, keď o to používateľ požiada, dokážeme obslúžiť iba približne 600 používateľov za sekundu.
╰─➤ 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
Ak však túto stránku uložíme do vyrovnávacej pamäte ako statický súbor HTML a necháme Nginx, aby ju rýchlo vyhodil používateľovi z okna, môžeme obslúžiť 32 000 používateľov za sekundu, čím sa výkon zvýši 50-násobne.
╰─➤ 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
Statický index.en.html je časť, ktorá sa dostane každému, a v sessionvars.js sa odosielajú iba časti, ktoré sa líšia podľa používateľa. To nielen znižuje zaťaženie databázy a vytvára lepší zážitok pre našich používateľov, ale tiež znižuje kvantové pravdepodobnosti, že náš server sa spontánne vyparí pri narušení warpového jadra, keď Klingoni zaútočia.
Vrátený kód pre každý rámec bude mať jednu jednoduchú požiadavku: ukázať používateľovi, koľkokrát obnovil stránku, vyjadrením „Počet je x“. Aby sme veci zjednodušili, budeme sa zatiaľ držať ďalej od frontov Redis, komponentov Kubernetes alebo AWS Lambdas.
Údaje o relácii každého používateľa budú uložené v databáze PostgreSQL.
A táto tabuľka databázy bude pred každým testom skrátená.
Jednoduché, ale účinné je motto Pafera... aj tak mimo najtemnejšej časovej osi...
Dobre, takže teraz si konečne môžeme začať špiniť ruky. Preskočíme nastavenie pre Laravel, pretože je to len skupina skladateľov a remeselníkov príkazy.
Najprv nastavíme nastavenia databázy v súbore .env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Potom nastavíme jednu záložnú trasu, ktorá odošle každú požiadavku nášmu kontrolórovi.
Route::fallback(SessionController::class);
A nastavte ovládač tak, aby zobrazoval počet. Laravel štandardne ukladá relácie do databázy. Poskytuje tiež session()
funkcia na prepojenie s našimi údajmi relácie, takže na vykreslenie našej stránky stačilo len pár riadkov kódu.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Po nastavení php-fpm a Nginx naša stránka vyzerá celkom dobre...
╰─➤ 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
Aspoň kým neuvidíme výsledky testov...
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
Nie, to nie je preklep. Náš testovací stroj prešiel zo 600 požiadaviek za sekundu pri vykresľovaní komplexnej stránky... na 21 požiadaviek za sekundu pri vykresľovaní „Počet je 1“.
Čo sa teda pokazilo? Je niečo v neporiadku s našou inštaláciou PHP? Spomaľuje sa Nginx nejako pri prepojení s php-fpm?
Prerobme túto stránku v čistom PHP kóde.
<?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'];
Teraz sme použili 98 riadkov kódu na to, čo urobili štyri riadky kódu (a celý rad konfiguračných prác) v Laravel. (Samozrejme, ak by sme spravili správne spracovanie chýb a správy zobrazované používateľom, bol by to asi dvojnásobok počtu riadkov.) Možno by sme to mohli dosiahnuť na 30 požiadaviek za sekundu?
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
Och! Zdá sa, že na našej inštalácii PHP nie je nič zlé. Čistá verzia PHP robí 700 požiadaviek za sekundu.
Ak s PHP nie je nič zlé, možno sme nesprávne nakonfigurovali Laravel?
Po prehľadaní webu o problémoch s konfiguráciou a tipoch na výkon boli dve z najpopulárnejších techník ukladať do vyrovnávacej pamäte konfiguráciu a smerovať údaje, aby sa predišlo ich spracovaniu pre každú požiadavku. Dáme preto na ich rady a vyskúšame tieto tipy.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
Na príkazovom riadku vyzerá všetko dobre. Zopakujme benchmark.
╰─➤ 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
Teraz sme zvýšili výkon z 21,04 na 28,80 požiadavky za sekundu, čo predstavuje dramatický nárast o takmer 37 %! To by bolo celkom pôsobivé pre akýkoľvek softvérový balík... až na skutočnosť, že stále robíme len 1/24 z počtu požiadaviek čistej verzie PHP.
Ak si myslíte, že s týmto testom musí byť niečo zlé, mali by ste sa porozprávať s autorom PHP frameworku Lucinda. Vo výsledkoch testov má Lucinda porazila Laravel 36-krát pre požiadavky HTML a 90-krát pre požiadavky JSON.
Po testovaní na vlastnom stroji s Apache aj Nginx nemám dôvod o ňom pochybovať. Laravel je naozaj spravodlivá že pomaly! PHP samo o sebe nie je také zlé, ale akonáhle pridáte všetky ďalšie spracovanie, ktoré Laravel pridáva ku každej požiadavke, potom je pre mňa veľmi ťažké odporučiť Laravel ako voľbu v roku 2023.
PHP/Wordpress účty pre asi 40 % všetkých webových stránok na webe , čo z neho robí zďaleka najdominantnejší rámec. Osobne však zisťujem, že popularita sa nemusí nevyhnutne premietať do kvality, o nič viac, ako mám náhlu nekontrolovateľnú túžbu po tomto výnimočnom gurmánskom jedle. najobľúbenejšia reštaurácia na svete ... McDonald's. Keďže sme už testovali čistý PHP kód, nebudeme testovať samotný Wordpress, pretože čokoľvek, čo zahŕňa Wordpress, by bolo nepochybne nižšie ako 700 požiadaviek za sekundu, ktoré sme pozorovali pri čistom PHP.
Django je ďalší populárny rámec, ktorý existuje už dlho. Ak ste ho v minulosti používali, pravdepodobne si radi spomínate na jeho veľkolepé rozhranie na správu databázy spolu s tým, aké otravné bolo konfigurovať všetko tak, ako ste chceli. Pozrime sa, ako dobre Django funguje v roku 2023, najmä s novým rozhraním ASGI, ktoré pridal od verzie 4.0.
Nastavenie Django je pozoruhodne podobné nastaveniu Laravel, pretože obaja boli z veku, kedy boli architektúry MVC štýlové a správne. Preskočíme nudnú konfiguráciu a prejdeme priamo k nastaveniu zobrazenia.
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}")
Štyri riadky kódu sú rovnaké ako pri verzii Laravel. Pozrime sa, ako to funguje.
╰─➤ 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
Nie je to vôbec zlé pri 355 požiadavkách za sekundu. Je to len polovičný výkon v porovnaní s čistou verziou PHP, ale je to tiež 12-násobok výkonu verzie Laravel. Zdá sa, že Django vs. Laravel vôbec nie je súťažou.
Okrem väčších rámov so všetkým, vrátane kuchynských drezov, existujú aj menšie rámce, ktoré vykonajú iba základné nastavenie a zvyšok vám umožnia zvládnuť. Jedným z najlepších na použitie je Flask a jeho ASGI náprotivok Quart. Moje vlastné Rámec PaferaPy je postavený na fľaši, takže som dobre oboznámený s tým, aké ľahké je robiť veci pri zachovaní výkonu.
#!/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}'
Ako vidíte, Flask skript je kratší ako čistý PHP skript. Zistil som, že zo všetkých jazykov, ktoré som použil, je Python pravdepodobne najvýraznejším jazykom, pokiaľ ide o písanie kláves. Nedostatok zložených zátvoriek a zátvoriek, pochopenia zoznamov a diktátov a blokovanie založené na odsadení a nie na bodkočiarke robia Python pomerne jednoduchým, ale výkonným vo svojich schopnostiach.
Bohužiaľ, Python je tiež najpomalším jazykom na všeobecné použitie, a to aj napriek tomu, koľko softvéru v ňom bolo napísané. Počet dostupných knižníc Pythonu je asi štyrikrát väčší ako v podobných jazykoch a pokrýva obrovské množstvo domén, no nikto by nepovedal, že Python je rýchly a výkonný mimo oblastí ako NumPy.
Pozrime sa, ako sa naša verzia Flask porovnáva s našimi predchádzajúcimi rámcami.
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
Náš Flask skript je v skutočnosti rýchlejší ako naša čistá verzia PHP!
Ak vás to prekvapilo, mali by ste si uvedomiť, že naša aplikácia Flask vykoná všetku svoju inicializáciu a konfiguráciu, keď spustíme server gunicorn, zatiaľ čo PHP znova spustí skript zakaždým, keď príde nová požiadavka. ; je ekvivalentom toho, že Flask je mladý, dychtivý taxikár, ktorý už naštartoval auto a čaká pri ceste, zatiaľ čo PHP je starý vodič, ktorý zostáva vo svojom dome a čaká na zavolanie a až potom ide. prísť ťa vyzdvihnúť. Keďže som človek zo starej školy a pochádzal z čias, keď bolo PHP úžasnou zmenou na obyčajné súbory HTML a SHTML, je trochu smutné uvedomiť si, koľko času ubehlo, ale rozdiely v dizajne skutočne sťažujú PHP súťažiť so servermi Python, Java a Node.js, ktoré zostávajú v pamäti a spracovávajú požiadavky s ľahkosťou žongléra.
Flask môže byť zatiaľ naším najrýchlejším rámcom, ale v skutočnosti je to dosť starý softvér. Komunita Pythonu pred pár rokmi prešla na novšie asychrónne servery ASGI a ja sám som s nimi samozrejme prešiel.
Najnovšia verzia Pafera Framework, PaferaPyAsync , je založený na Starlette. Aj keď existuje ASGI verzia Flask s názvom Quart, výkonnostné rozdiely medzi Quart a Starlette boli dostatočné na to, aby som namiesto toho prepracoval svoj kód na Starlette.
Asychrónne programovanie môže mnohých ľudí vystrašiť, ale v skutočnosti to nie je zložitý koncept, pretože chlapci z Node.js tento koncept spopularizovali pred viac ako desiatimi rokmi.
Zvykli sme bojovať proti súbežnosti s multithreadingom, multiprocessingom, distribuovaným výpočtom, reťazením sľubov a všetkými tými zábavnými časmi, ktoré predčasne zostarli a vysušili mnohých skúsených programátorov. Teraz len píšeme async
pred našimi funkciami a await
pred akýmkoľvek kódom, ktorého vykonanie môže chvíľu trvať. Je skutočne podrobnejší ako bežný kód, ale jeho používanie je oveľa menej otravné, ako keď sa musíte zaoberať synchronizačnými primitívami, odovzdávaním správ a riešením sľubov.
Náš súbor Starlette vyzerá takto:
#!/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",
)
Ako môžete vidieť, je to do značnej miery skopírované a prilepené z nášho skriptu Flask iba s niekoľkými zmenami smerovania a async/await
kľúčové slová.
Koľko vylepšení nám skutočne môže priniesť skopírovanie a vloženie kódu?
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
Máme nového šampióna, dámy a páni! Naše predchádzajúce maximum bola naša čistá verzia PHP s rýchlosťou 704 požiadaviek za sekundu, ktorú potom prekonala naša verzia Flask s rýchlosťou 1080 požiadaviek za sekundu. Náš skript Starlette rozdrví všetkých predchádzajúcich uchádzačov rýchlosťou 4562 požiadaviek za sekundu, čo znamená 6x zlepšenie oproti čistému PHP a 4x zlepšenie oproti Flasku.
Ak ste ešte nezmenili svoj kód WSGI Python na ASGI, možno je ten správny čas začať.
Doteraz sme sa zaoberali iba PHP a Python frameworkami. Veľká časť sveta však v skutočnosti pre svoje webové stránky používa technológie Java, DotNet, Node.js, Ruby on Rails a ďalšie podobné technológie. Toto v žiadnom prípade nie je komplexný prehľad všetkých svetových ekosystémov a biómov, takže aby sme sa vyhli programovaniu ekvivalentu organickej chémie, vyberieme len rámce, pre ktoré je najjednoduchšie zadať kód. z ktorých Java rozhodne nie je.
Pokiaľ ste sa neskrývali pod kópiou K&R C alebo Knuth's Umenie počítačového programovania za posledných pätnásť rokov ste pravdepodobne počuli o Node.js. Tí z nás, ktorí sú tu od začiatku JavaScriptu, sú buď neskutočne vystrašení, ohromení, alebo oboje zo stavu moderného JavaScriptu, no nemožno poprieť, že JavaScript sa stal silou, s ktorou treba počítať aj na serveroch. ako prehliadače. Koniec koncov, teraz máme v jazyku aj natívne 64-bitové celé čísla! To je oveľa lepšie ako všetko, čo je uložené v 64-bitových plávajúcich zariadeniach!
ExpressJS je pravdepodobne najjednoduchší server Node.js na použitie, takže urobíme rýchlu a špinavú aplikáciu Node.js/ExpressJS, ktorá bude slúžiť nášmu pultu.
/**********************************************************************
* 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}`));
Tento kód sa v skutočnosti písal ľahšie ako verzie Pythonu, hoci natívny JavaScript sa stáva dosť nepraktickým, keď sa aplikácie zväčšujú, a všetky pokusy o nápravu, ako napríklad TypeScript, sa rýchlo stanú podrobnejšími ako Python.
Pozrime sa, ako to funguje!
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
Možno ste už počuli prastaré (aj tak staré na internetové štandardy...) ľudové rozprávky o Node.js' rýchlosť a tieto príbehy sú väčšinou pravdivé vďaka veľkolepej práci, ktorú Google odviedol s V8 JavaScript engine. Aj keď v tomto prípade naša rýchla aplikácia prekoná skript Flask, jej jednovláknovú povahu prekazia štyri asynchrónne procesy ovládané Starlette Knight, ktorý hovorí „Ni!“.
Získajte ďalšiu pomoc!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Dobre! Teraz je to vyrovnaný súboj štyroch proti štyrom! Poďme porovnávať!
╰─➤ 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
Stále to nie je úplne na úrovni Starlette, ale na rýchly päťminútový hack JavaScript to nie je zlé. Z môjho vlastného testovania je tento skript v skutočnosti trochu brzdený na úrovni rozhrania databázy, pretože node-postgres nie je ani zďaleka taký efektívny ako psycopg pre Python. Prepnutie na sqlite ako ovládač databázy prináša viac ako 3 000 požiadaviek za sekundu pre rovnaký kód ExpressJS.
Hlavná vec, ktorú treba poznamenať, je, že napriek nízkej rýchlosti vykonávania Pythonu môžu rámce ASGI skutočne konkurovať riešeniam Node.js pre určité pracovné zaťaženia.
Takže teraz sa približujeme k vrcholu hory a horou mám na mysli najvyššie referenčné skóre zaznamenané myšami aj mužmi.
Ak sa pozriete na väčšinu rámcových benchmarkov dostupných na webe, všimnete si, že existujú dva jazyky, ktoré majú tendenciu dominovať na vrchole: C++ a Rust. S C++ som pracoval od 90-tych rokov a dokonca som mal svoj vlastný Win32 C++ framework ešte predtým, ako MFC/ATL bolo vecou, takže s jazykom mám bohaté skúsenosti. Nie je príliš zábavné pracovať s niečím, keď to už poznáte, takže namiesto toho urobíme verziu Rust. ;)
Rust je relatívne nový, pokiaľ ide o programovacie jazyky, ale stal sa predmetom mojej zvedavosti, keď Linus Torvalds oznámil, že akceptuje Rust ako programovací jazyk jadra Linuxu. Pre nás starších programátorov je to približne to isté, ako keby sme povedali, že táto nová vychýrená hippie vecička z nového veku bude novým dodatkom k ústave USA.
Teraz, keď ste skúsený programátor, máte tendenciu nenaskočiť do rozbehnutého vlaku tak rýchlo ako mladší ľudia, inak by vás mohli popáliť rýchle zmeny jazyka alebo knižníc. (Každý, kto použil prvú verziu AngularJS, bude vedieť, o čom hovorím.) Rust je stále trochu v štádiu experimentálneho vývoja a zdá sa mi smiešne, že toľko príkladov kódu na webe už kompilovať s aktuálnymi verziami balíkov.
Výkon, ktorý predvádzajú aplikácie Rust, však nemožno uprieť. Ak ste to nikdy neskúsili ripgrep alebo fd-find na veľkých stromoch zdrojového kódu by ste ich mali určite vyskúšať. Sú dokonca dostupné pre väčšinu distribúcií Linuxu jednoducho zo správcu balíkov. S Rustom vymieňate výrečnosť za výkon... a veľa výrečnosti pre a veľa výkonu.
Úplný kód pre Rust je trochu veľký, takže sa tu pozrieme na príslušné obslužné nástroje:
// =====================================================================
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
)))
}
Je to oveľa komplikovanejšie ako verzie Python/Node.js...
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
A oveľa výkonnejšie!
Náš server Rust používajúci Actix/deadpool_postgres s prehľadom poráža nášho predchádzajúceho šampióna Starlette o +125 %, ExpressJS o +362 % a čisté PHP o +1366 %. (Ponechám deltu výkonu s verziou Laravel ako cvičenie pre čitateľa.)
Zistil som, že naučiť sa samotný jazyk Rust je ťažšie ako iné jazyky, pretože má oveľa viac problémov ako čokoľvek, čo som videl mimo zostavy 6502, ale ak váš server Rust dokáže prijať 14-násobok počtu používateľov ako váš server PHP, potom možno s prepínaním technológií predsa len niečo získate. To je dôvod, prečo bude ďalšia verzia Pafera Framework založená na Rust. Krivka učenia je oveľa vyššia ako pri skriptovacích jazykoch, ale výkon bude stáť za to. Ak si nemôžete dať čas na to, aby ste sa naučili Rust, potom založiť svoj technologický stack na Starlette alebo Node.js tiež nie je zlé rozhodnutie.
Za posledných dvadsať rokov sme prešli od lacných statických hostingových stránok cez zdieľaný hosting so zásobníkmi LAMP až po prenájom VPS pre AWS, Azure a ďalšie cloudové služby. V súčasnosti sa veľa spoločností uspokojuje s rozhodovaním o dizajne na základe toho, koho považujú za dostupného alebo najlacnejšieho, pretože nástup pohodlných cloudových služieb uľahčil nasadenie väčšieho množstva hardvéru na pomalé servery a aplikácie. To im poskytlo veľké krátkodobé zisky za cenu dlhodobého technického dlhu.
Pred 70 rokmi prebehli veľké vesmírne preteky medzi Sovietskym zväzom a Spojenými štátmi. Väčšinu prvých míľnikov vyhrali Sovieti. Mali prvý satelit v Sputniku, prvého psa vo vesmíre v Lajke, prvú mesačnú kozmickú loď na Lune 2, prvého muža a ženu vo vesmíre v Jurijovi Gagarinovi a Valentine Tereškovovej a tak ďalej...
Ale pomaly sa im hromadil technický dlh.
Hoci Sovieti boli prví v každom z týchto úspechov, ich inžinierske procesy a ciele ich prinútili zamerať sa skôr na krátkodobé výzvy než na dlhodobú realizovateľnosť. Vyhrali zakaždým, keď skočili, ale boli čoraz unavenejší a pomalší, zatiaľ čo ich súperi pokračovali v konzistentných krokoch smerom k cieľovej čiare.
Akonáhle Neil Armstrong urobil svoje historické kroky na Mesiaci v priamom televíznom prenose, Američania sa ujali vedenia a potom tam zostali, keď sovietsky program zakolísal. To sa nelíši od dnešných spoločností, ktoré sa zamerali na ďalšiu veľkú vec, ďalšiu veľkú výplatu alebo ďalšiu veľkú technológiu, pričom sa im nepodarilo vyvinúť správne návyky a stratégie na dlhú trať.
Byť prvý na trhu neznamená, že sa stanete dominantným hráčom na tomto trhu. Prípadne, ak si urobíte čas správne, nezaručí úspech, ale určite zvýši vaše šance na dlhodobé úspechy. Ak ste technologickým lídrom svojej spoločnosti, vyberte si správny smer a nástroje pre svoju pracovnú záťaž. Nedovoľte, aby popularita nahradila výkon a efektivitu.
Chcete si stiahnuť 7z súbor obsahujúci skripty Rust, ExpressJS, Flask, Starlette a Pure PHP?
O autorovi |
|
![]() |
Jim sa programovaniu venuje od doby, keď v 90. rokoch dostal IBM PS/2. Dodnes stále preferuje ručné písanie HTML a SQL a pri svojej práci sa zameriava na efektivitu a korektnosť. |