Az egyik legutóbbi állásinterjúm után meglepődve vettem észre, hogy a cég, amelyhez jelentkeztem, még mindig a Laravel-t, egy PHP-keretrendszert használ, amelyet körülbelül egy évtizede próbáltam ki. Abban az időben tisztességes volt, de ha van egy állandó technológia és divat egyaránt, az a stílusok és koncepciók folyamatos változása és újjáéledése. Ha Ön JavaScript programozó, valószínűleg ismeri ezt a régi viccet
1. programozó: "Nem tetszik ez az új JavaScript-keretrendszer!"
2. programozó: „Nem kell aggódni. Csak várjon hat hónapot, és lesz egy másik a helyére!"
Kíváncsiságból úgy döntöttem, hogy megnézem, mi történik pontosan, ha próbára teszünk régit és újat. Természetesen a web tele van benchmarkokkal és állításokkal, amelyek közül a legnépszerűbb valószínűleg a TechEmpower Web Framework benchmarkok itt . De közel sem fogunk olyan bonyolult dolgot csinálni, mint ők ma. Mindkettő szép és egyszerű lesz, hogy ebből a cikkből ne váljon Háború és béke , és van egy kis esélye arra, hogy ébren maradjon, mire befejezi az olvasást. A szokásos figyelmeztetések érvényesek: előfordulhat, hogy ez nem működik ugyanúgy az Ön gépén, a különböző szoftververziók befolyásolhatják a teljesítményt, és Schrödinger macskája valójában egy zombi macska lett, aki félig élt és félig holt volt.
Ehhez a teszthez egy Manjaro Linuxot futtató, csekély i5-tel felvértezett laptopomat fogom használni, ahogy az itt látható.
╰─➤ 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
Kódunk három egyszerű feladatot tartalmaz minden kéréshez:
Milyen idióta teszt ez, kérdezhetik? Nos, ha megnézi az oldalra vonatkozó hálózati kéréseket, észre fog venni egy sessionvars.js nevűt, amely pontosan ugyanezt teszi.
Tudja, a modern weboldalak bonyolult lények, és az egyik leggyakoribb feladat az összetett oldalak gyorsítótárazása, hogy elkerülje az adatbázis-kiszolgáló túlterhelését.
Ha egy összetett oldalt minden alkalommal újra renderelünk, amikor egy felhasználó kéri, akkor másodpercenként csak körülbelül 600 felhasználót tudunk kiszolgálni.
╰─➤ 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
De ha ezt az oldalt statikus HTML-fájlként gyorsítótárazzuk, és hagyjuk, hogy az Nginx gyorsan kidobja az ablakon a felhasználónak, akkor másodpercenként 32 000 felhasználót tudunk kiszolgálni, amivel a teljesítmény 50-szeresére nő.
╰─➤ 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
A statikus index.en.html az a rész, amely mindenkinek jár, és csak a felhasználónként eltérő részek kerülnek elküldésre a sessionvars.js-ben. Ez nemcsak az adatbázisok terhelését csökkenti, és jobb felhasználói élményt biztosít, hanem csökkenti annak kvantum valószínűségét is, hogy szerverünk spontán elpárolog egy vetemedési mag megsértése esetén, amikor a klingonok támadnak.
Az egyes keretrendszerekhez visszaadott kódnak egy egyszerű követelménye van: mutassa meg a felhasználónak, hogy hányszor frissítette az oldalt úgy, hogy azt mondja, hogy „A szám x”. Az egyszerűség kedvéért egyelőre távol maradunk a Redis-várólistáktól, a Kubernetes-összetevőktől és az AWS-lambdáktól.
Minden felhasználó munkamenet-adatai egy PostgreSQL-adatbázisba kerülnek mentésre.
És ez az adatbázistábla minden teszt előtt csonkolva lesz.
Egyszerű, de hatásos a Pafera mottója... mindenesetre a legsötétebb idővonalon kívül...
Oké, most végre elkezdhetjük piszkolni a kezünket. Kihagyjuk a Laravel beállítását, mivel ez csak egy csomó zeneszerző és kézműves parancsokat.
Először is beállítjuk adatbázisunk beállításait az .env fájlban
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Ezután egyetlen tartalék útvonalat állítunk be, amely minden kérést elküld a vezérlőnknek.
Route::fallback(SessionController::class);
És állítsa be a vezérlőt a számláló megjelenítésére. A Laravel alapértelmezés szerint a munkameneteket az adatbázisban tárolja. Azt is biztosítja a session()
funkciót a munkamenet adatainkkal való interfészhez, így mindössze néhány sornyi kódra volt szükség az oldalunk megjelenítéséhez.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
A php-fpm és az Nginx beállítása után az oldalunk egész jól néz ki...
╰─➤ 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
Legalábbis addig, amíg meg nem látjuk a teszteredményeket...
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
Nem, ez nem elírás. Tesztgépünk 600 kérés/másodperc helyett egy összetett oldalt... 21 kérés/másodperc-re változott a "Count is 1".
Szóval mi romlott el? Valami baj van a PHP telepítésével? Valahogy lelassul az Nginx, amikor php-fpm-mel csatlakozik?
Tegyük újra ezt az oldalt tiszta PHP kóddal.
<?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'];
Most 98 kódsort használtunk fel arra, amit a Laravel négy soros kódja (és egy csomó konfigurációs munka) tett. (Természetesen, ha megfelelő hibakezelést és a felhasználói üzeneteket kezelnénk, ez körülbelül kétszerese lenne a sorok számának.) Talán elérjük a másodpercenkénti 30 kérést?
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
Hú! Úgy tűnik, nincs semmi baj a PHP telepítésével. A tiszta PHP verzió 700 kérést teljesít másodpercenként.
Ha nincs semmi baj a PHP-vel, talán rosszul konfiguráltuk a Laravelt?
A konfigurációs problémák és a teljesítményre vonatkozó tippek internetes böngészése után a két legnépszerűbb technika a konfigurációs és az útvonali adatok gyorsítótárazása volt, így elkerülhető, hogy minden kérésnél feldolgozzák azokat. Ezért megfogadjuk tanácsaikat, és kipróbáljuk ezeket a tippeket.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
A parancssorban minden jól néz ki. Tegyük újra a benchmarkot.
╰─➤ 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
Nos, a teljesítményt 21,04-ről másodpercenként 28,80-ra növeltük, ami drámai, közel 37%-os emelkedés! Ez bármilyen szoftvercsomag esetében lenyűgöző lenne... kivéve azt a tényt, hogy még mindig csak a tiszta PHP-verzió kéréseinek 1/24-ét teljesítjük.
Ha úgy gondolja, hogy valami nem stimmel ezzel a teszttel, beszéljen a Lucinda PHP keretrendszer szerzőjével. Vizsgálati eredményei szerint megvan Lucinda legyőzte Laravelt 36-szorosára HTML-kéréseknél és 90-szeresére JSON-kéréseknél.
Miután teszteltem a saját gépemen Apache és Nginx segítségével, nincs okom kételkedni benne. Laravel tényleg igazságos hogy lassú! A PHP önmagában nem olyan rossz, de ha egyszer hozzáadja a Laravel által minden kéréshez hozzáadott extra feldolgozást, akkor nagyon nehéznek találom a Laravelt választásként ajánlani 2023-ban.
PHP/Wordpress fiókok a weben található összes webhely körülbelül 40%-a , így messze a legdominánsabb keretrendszer. Személy szerint azonban úgy gondolom, hogy a népszerűség nem feltétlenül jelent minőséget, mint ahogy azt sem tapasztalom, hogy hirtelen fékezhetetlen vágyam van arra a rendkívüli ínyenc ételre. a világ legnépszerűbb étterme ... McDonald's. Mivel már teszteltük a tiszta PHP-kódot, magát a Wordpress-t nem fogjuk tesztelni, mivel a Wordpress-szel kapcsolatos minden kétségtelenül alacsonyabb lesz, mint a tiszta PHP-vel megfigyelt 700 kérés/másodperc.
A Django egy másik népszerű keretrendszer, amely már régóta létezik. Ha korábban használta, valószínűleg szívesen emlékszik vissza látványos adatbázis-adminisztrációs felületére, valamint arra, hogy milyen bosszantó volt mindent úgy konfigurálni, ahogyan akarta. Lássuk, milyen jól működik a Django 2023-ban, különösen az új ASGI felülettel, amelyet a 4.0-s verziótól kapott.
A Django beállítása rendkívül hasonlít a Laravel beállításához, mivel mindketten abból a korból származtak, amikor az MVC architektúrák stílusosak és korrektek voltak. Kihagyjuk az unalmas konfigurációt, és közvetlenül a nézet beállításához kezdünk.
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}")
A négy soros kód ugyanaz, mint a Laravel verziónál. Lássuk, hogyan működik.
╰─➤ 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
Egyáltalán nem rossz, 355 kérés másodpercenként. Ez csak a fele akkora, mint a tiszta PHP verzió, de 12-szerese a Laravel verzióénak. Úgy tűnik, a Django vs. Laravel egyáltalán nem verseny.
A nagyobb, minden – a mosogatót is beleértve – kereteken kívül vannak kisebb keretek is, amelyek csak néhány alapvető beállítást végeznek, míg a többit Ön kezelheti. Az egyik legjobban használható a Flask és ASGI megfelelője a Quart. a sajátom PaferaPy Framework A Flask tetejére épült, így jól ismerem, milyen egyszerű a teljesítmény megőrzése mellett a dolgok elvégzése.
#!/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}'
Amint látja, a Flask szkript rövidebb, mint a tiszta PHP szkript. Úgy látom, hogy az általam használt nyelvek közül a Python a legkifejezőbb nyelv a leütések tekintetében. A kapcsos zárójelek és a zárójelek hiánya, a listák és a diktatúrák értelmezése, valamint a pontosvessző helyett a behúzáson alapuló blokkolás miatt a Python meglehetősen egyszerű, de mégis erőteljes.
Sajnos a Python a leglassabb általános célú nyelv is, annak ellenére, hogy mennyi szoftvert írtak bele. A rendelkezésre álló Python-könyvtárak száma körülbelül négyszer több, mint a hasonló nyelveké, és hatalmas mennyiségű tartományt fed le, de senki sem mondaná, hogy a Python gyors vagy hatékony az olyan réseken kívül, mint a NumPy.
Lássuk, hogyan viszonyul Flask verziónk korábbi keretrendszereinkhez.
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
A Flask szkriptünk valójában gyorsabb, mint a tiszta PHP verziónk!
Ha ez meglep, észre kell vennie, hogy a Flask alkalmazásunk elvégzi az összes inicializálást és konfigurálást, amikor elindítjuk a gunicorn szervert, míg a PHP minden új kérés beérkezésekor újra végrehajtja a szkriptet. Flask egyenértékű azzal, hogy Flask a fiatal, lelkes taxisofőr, aki már beindította az autót, és az út mellett várakozik, míg a PHP az öreg sofőr, aki a házában marad, várja a bejövő hívást, és csak azután vezet. átmenni érted. Mivel régi iskolai srác vagyok, és abból az időből jöttem, amikor a PHP csodálatos változást jelentett a sima HTML- és SHTML-fájlok felé, kissé szomorú látni, hogy mennyi idő telt el, de a tervezési különbségek megnehezítik a PHP számára versenyeznek a Python, Java és Node.js szerverekkel, amelyek csak a memóriában maradnak, és a kéréseket a zsonglőr fürge könnyedségével kezelik.
Talán a Flask az eddigi leggyorsabb keretrendszerünk, de valójában elég régi szoftver. A Python közösség néhány éve váltott az újabb aszinkron ASGI szerverekre, és természetesen én magam is váltottam velük.
A Pafera Framework legújabb verziója, PaferaPyAsync , a Starlette-en alapul. Bár a Flasknak létezik Quart nevű ASGI-verziója, a Quart és a Starlette közötti teljesítménybeli különbségek elegendőek voltak ahhoz, hogy a kódomat a Starlette-re alapozzam.
Az aszinkron programozás sok ember számára ijesztő lehet, de valójában nem nehéz koncepció, köszönhetően a Node.js srácoknak, akik több mint egy évtizeddel ezelőtt népszerűsítették a koncepciót.
Korábban küzdöttünk az egyidejűség ellen a többszálas megoldással, a többfeldolgozással, az elosztott számítástechnikával, az ígéretláncolással és mindazokkal a szórakoztató időkkel, amelyek idő előtt megöregedtek és kiszáradtak sok veterán programozót. Most csak gépelünk async
funkcióink előtt és await
minden olyan kód előtt, amelynek végrehajtása eltarthat egy ideig. Valóban bőbeszédűbb, mint a normál kód, de sokkal kevésbé bosszantó a használata, mint a szinkronizálási primitívekkel, az üzenettovábbítással és az ígéretek feloldásával.
A Starlette fájlunk így néz ki:
#!/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",
)
Amint látja, nagyjából a Flask szkriptünkből másolták és illesztették be, mindössze néhány útválasztási módosítással és a async/await
kulcsszavakat.
Valójában mennyi fejlesztést tud nekünk adni a kód másolása és beillesztése?
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
Új bajnokunk van, hölgyeim és uraim! Korábbi csúcsunk a tiszta PHP verziónk volt 704 kérés/másodperc sebességgel, amit aztán a Flask verziónk utolért 1080 kérés/másodperc sebességgel. A Starlette szkriptünk 4562 kéréssel másodpercenként zúzza az összes korábbi versenyzőt, ami hatszoros javulást jelent a tiszta PHP-hez képest és 4-szeres javulást a Flaskhoz képest.
Ha még nem változtatta át WSGI Python kódját ASGI-re, akkor most érdemes elkezdeni.
Eddig csak a PHP és a Python keretrendszerekkel foglalkoztunk. A világ nagy része azonban valójában Java, DotNet, Node.js, Ruby on Rails és más hasonló technológiákat használ webhelyeihez. Ez semmiképpen sem egy átfogó áttekintés a világ összes ökoszisztémájáról és életközösségéről, ezért, hogy elkerüljük a szerves kémia programozási megfelelőjét, csak azokat a keretrendszereket fogjuk kiválasztani, amelyekhez a legkönnyebben beírható kód. amelyből a Java biztosan nem.
Hacsak nem bujkált a K&R C vagy a Knuth' példánya alatt A számítógépes programozás művészete az elmúlt tizenöt évben valószínűleg hallott a Node.js-ről. Mi, akik a JavaScript kezdete óta jelen vagyunk, vagy hihetetlenül meg vannak ijedve, csodálkozunk, vagy mindketten félünk a modern JavaScript állapotától, de tagadhatatlan, hogy a JavaScript a szervereken is számolni kell. mint böngészők. Hiszen most már natív 64 bites egész számaink is vannak a nyelvben! Ez sokkal jobb, mintha mindent 64 bites floatokban tárolnának!
Az ExpressJS valószínűleg a legkönnyebben használható Node.js szerver, ezért egy gyors és piszkos Node.js/ExpressJS alkalmazást készítünk a pult kiszolgálására.
/**********************************************************************
* 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}`));
Ezt a kódot valójában könnyebb volt megírni, mint a Python-verziókat, bár a natív JavaScript meglehetősen nehézkessé válik, amikor az alkalmazások egyre nagyobbakká válnak, és minden ennek kijavítására tett kísérlet, például a TypeScript, gyorsan bőbeszédűbbé válik, mint a Python.
Lássuk, hogyan működik ez!
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
Talán hallott már ősi (internetes mércével is ősi...) népmeséket a Node.js-ről' sebességgel, és ezek a történetek többnyire igazak, köszönhetően annak a látványos munkának, amelyet a Google végzett a V8 JavaScript motorral. Ebben az esetben azonban, bár a gyorsalkalmazásunk felülmúlja a Flask szkriptet, egyszálú jellegét legyőzi a Starlette Knight négy aszinkron folyamata, aki azt mondja, hogy "Ni!".
Kérjünk még egy kis segítséget!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Rendben! Most páros négy a négy elleni csata! Tegyünk benchmarkot!
╰─➤ 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
Még mindig nem egészen a Starlette szintjén, de nem rossz egy gyors ötperces JavaScript-hacknek. Saját tesztelésem szerint ez a szkript valójában egy kicsit vissza van tartva az adatbázis-interfész szintjén, mert a node-postgres közel sem olyan hatékony, mint a psycopg a Python esetében. Az adatbázis-illesztőprogramként való sqlite-ra váltás több mint 3000 kérést eredményez másodpercenként ugyanazzal az ExpressJS-kóddal.
A legfontosabb dolog, amit meg kell jegyezni, hogy a Python lassú végrehajtási sebessége ellenére az ASGI-keretrendszerek bizonyos munkaterhelések esetén valóban versenyképesek lehetnek a Node.js megoldásokkal.
Tehát most egyre közelebb kerülünk a hegy tetejéhez, és a hegy alatt az egerek és férfiak által feljegyzett legmagasabb benchmark pontszámokat értem.
Ha megnézi az interneten elérhető keretrendszer legtöbb referenciaértékét, észreveheti, hogy két nyelv dominál a csúcson: a C++ és a Rust. A 90-es évek óta dolgozom C++-szal, és még saját Win32 C++ keretrendszerem is volt, még azelőtt, hogy az MFC/ATL szóba került volna, szóval sok tapasztalatom van a nyelvvel kapcsolatban. Nem túl szórakoztató valamivel dolgozni, ha már ismeri, ezért inkább egy Rust verziót fogunk készíteni. ;)
A Rust viszonylag új, ami a programozási nyelveket illeti, de a kíváncsiság tárgyává vált számomra, amikor Linus Torvalds bejelentette, hogy elfogadja a Rustot Linux kernel programozási nyelvként. Nekünk, idősebb programozóknak ez nagyjából ugyanaz, mintha azt mondanánk, hogy ez az új, felkapott újkori hippi-dolog az Egyesült Államok alkotmányának új módosítása lesz.
Most, hogy tapasztalt programozó vagy, hajlamos nem ugrani olyan gyorsan, mint a fiatalabbak, különben leéghetsz a nyelv vagy a könyvtárak gyors változásai miatt. (Bárki, aki az AngularJS első verzióját használta, tudni fogja, miről beszélek.) A Rust még mindig a kísérleti fejlesztési szakaszban van, és viccesnek találom, hogy az a sok kódpélda a weben még csak nem is fordítsa le többé a csomagok aktuális verzióival.
A Rust alkalmazások által mutatott teljesítmény azonban nem tagadható. Ha még soha nem próbáltad ripgrep vagy fd-find a nagy forráskód fákon, mindenképpen érdemes megpörgetni őket. A legtöbb Linux disztribúcióhoz egyszerűen a csomagkezelőből is elérhetők. Ön a szókimondást teljesítményre cseréli Rusttal... a sok a bőbeszédűség a sok a teljesítményről.
A Rust teljes kódja egy kicsit nagy, ezért csak vessünk egy pillantást a vonatkozó kezelőkre:
// =====================================================================
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
)))
}
Ez sokkal bonyolultabb, mint a Python/Node.js verziók...
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
És sokkal teljesítményesebb!
Az Actix/deadpool_postgres-t használó Rust szerverünk +125%-kal verte meg előző bajnokunkat, a Starlette-et, az ExpressJS-t +362%-kal és a tiszta PHP-t +1366%-kal. (A teljesítmény-deltát a Laravel változatnál hagyom gyakorlatként az olvasó számára.)
Azt tapasztaltam, hogy maga a Rust nyelv megtanulása nehezebb, mint más nyelvek, mivel sokkal több hibája van, mint bárminek, amit a 6502 Assembly-n kívül láttam, de ha a Rust szervere 14-szeresére képes. felhasználókat PHP-szerverként, akkor talán mégis nyerhet valamit a technológiaváltással. Éppen ezért a Pafera Framework következő verziója a Rust-on fog alapulni. A tanulási görbe sokkal magasabb, mint a szkriptnyelveké, de a teljesítmény megéri. Ha nem tud időt szánni a Rust megtanulására, akkor a Starlette vagy a Node.js technológiára való alapozása sem rossz döntés.
Az elmúlt húsz évben az olcsó statikus tárhelyoldalakról a LAMP-veremekkel ellátott megosztott tárhelyszolgáltatásra váltunk, egészen a VPS-ek bérbeadásáig az AWS-hez, az Azure-hoz és más felhőszolgáltatásokhoz. Manapság sok vállalat megelégszik azzal, hogy a tervezési döntéseket az alapján hozza meg, hogy kinek melyik az elérhető vagy a legolcsóbb, mivel a kényelmes felhőszolgáltatások megjelenése megkönnyítette, hogy több hardvert dobjon ki a lassú szerverekre és alkalmazásokra. Ez nagy rövid távú nyereséget eredményezett hosszú távú technikai adósság árán.
70 évvel ezelőtt nagy űrverseny volt a Szovjetunió és az Egyesült Államok között. A korai mérföldkövek többségét a szovjetek nyerték meg. Megvolt az első műhold a Szputnyikban, az első kutya az űrben Lajkában, az első hold űrszonda a Luna 2-ben, az első férfi és nő az űrben Jurij Gagarinban és Valentina Tereshkova, és így tovább...
De lassan technikai adósságot halmoztak fel.
Bár a szovjetek voltak az elsők ezekben az eredményekben, mérnöki folyamataik és céljaik arra késztették őket, hogy a hosszú távú megvalósíthatóság helyett a rövid távú kihívásokra összpontosítsanak. Minden alkalommal nyertek, amikor ugrottak, de egyre fáradtabbak és lassabbak voltak, miközben ellenfeleik folyamatosan haladtak a cél felé.
Miután Neil Armstrong megtette történelmi lépéseit a Holdon az élő televíziós adásban, az amerikaiak átvették a vezetést, majd ott is maradtak, ahogy a szovjet program megingott. Ez nem különbözik a mai vállalatoktól, amelyek a következő nagy dologra, a következő nagy nyereségre vagy a következő nagy technológiára összpontosítanak, miközben nem alakítanak ki megfelelő szokásokat és stratégiákat hosszú távra.
Az, hogy az első a piacon, nem jelenti azt, hogy Ön lesz a domináns szereplő ezen a piacon. Alternatív megoldásként, ha időt szán a dolgok helyes megtételére, az nem garantálja a sikert, de mindenképpen növeli a hosszú távú eredmények esélyét. Ha Ön a technológiai vezető vállalata számára, válassza ki a megfelelő irányt és eszközöket a munkaterheléséhez. Ne hagyja, hogy a népszerűség felváltsa a teljesítményt és a hatékonyságot.
Szeretné letölteni a Rust, ExpressJS, Flask, Starlette és Pure PHP szkripteket tartalmazó 7z fájlt?
A Szerzőről |
|
![]() |
Jim azóta programoz, amióta a 90-es években kapott egy IBM PS/2-t. A mai napig előszeretettel ír HTML-t és SQL-t kézzel, munkája során a hatékonyságra és a korrektségre helyezi a hangsúlyt. |