Rust netþjónninn okkar sem notar Actix/deadpool_postgres slær fyrri meistara Starlette vel um +125%, ExpressJS um +362% og hreint PHP um +1366%. (Ég mun skilja eftir frammistöðudeltuna með Laravel útgáfunni sem æfingu fyrir lesandann.)
Ég hef komist að því að það hefur verið erfiðara að læra Rust tungumálið sjálft en önnur tungumál þar sem það hefur miklu fleiri möguleika en allt sem ég hef séð utan 6502 Assembly, en ef Rust þjónninn þinn getur tekið á sig 14x fjölda notendur sem PHP netþjóninn þinn, þá er kannski eitthvað hægt að vinna með því að skipta um tækni eftir allt saman. Þess vegna verður næsta útgáfa af Pafera Framework byggð á Rust. Námsferillinn er miklu hærri en forskriftarmál, en frammistaðan verður þess virði. Ef þú getur ekki lagt tíma í að læra Rust, þá er það heldur ekki slæm ákvörðun að byggja tæknibunkann þinn á Starlette eða Node.js.
Tækniskuld
Á síðustu tuttugu árum höfum við farið frá ódýrum kyrrstæðum hýsingarsíðum yfir í sameiginlega hýsingu með LAMP stafla til að leigja VPS til AWS, Azure og annarra skýjaþjónustu. Nú á dögum eru mörg fyrirtæki ánægð með að taka hönnunarákvarðanir byggðar á hverjum þeim sem þeir geta fundið að er í boði eða ódýrast þar sem tilkoma þægilegrar skýjaþjónustu hefur gert það auðvelt að henda meiri vélbúnaði á hæga netþjóna og forrit. Þetta hefur gefið þeim mikinn skammtímahagnað á kostnað langtíma tæknilegra skulda. Viðvörun skurðlæknis í Kaliforníu: Þetta er ekki raunverulegur geimhundur. . Fyrir 70 árum var mikið geimkapphlaup milli Sovétríkjanna og Bandaríkjanna. Sovétmenn unnu flest fyrstu tímamótin. Þeir voru með fyrsta gervihnöttinn í Spútnik, fyrsta hundinn í geimnum í Laika, fyrsta tunglgeimfarið í Luna 2, fyrsta karlinn og konuna í geimnum í Yuri Gagarin og Valentina Tereshkova, og svo framvegis... En þeir voru hægt og rólega að safna tæknilegum skuldum. , Þrátt fyrir að Sovétmenn hafi verið fyrstir að öllum þessum afrekum, ollu verkfræðiferli þeirra og markmiðum þess að þeir einbeittu sér að skammtímaáskorunum frekar en langtímafýsileika. Þeir unnu í hvert skipti sem þeir stökkva, en þeir voru að verða þreyttari og hægari á meðan andstæðingar þeirra héldu áfram að taka stöðugt skref í átt að marklínunni.
Auðlindir
╰─➤ 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
Komdu!
Hvers konar fávitapróf er það, gætirðu spurt? Jæja, ef þú skoðar netbeiðnirnar fyrir þessa síðu muntu taka eftir einni sem heitir sessionvars.js sem gerir nákvæmlega það sama.
Þú sérð, nútíma vefsíður eru flóknar verur og eitt algengasta verkefnið er að vista flóknar síður til að forðast of mikið álag á gagnagrunnsþjóninn.
Ef við endurgerum flókna síðu í hvert sinn sem notandi biður um hana, þá getum við aðeins þjónað um 600 notendum á sekúndu.
╰─➤ 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
En ef við vistum þessa síðu sem kyrrstæða HTML-skrá og leyfum Nginx að henda henni fljótt út um gluggann til notandans, þá getum við þjónað 32.000 notendum á sekúndu, aukið afköst um 50x.
╰─➤ 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
Static index.en.html er sá hluti sem fer til allra og aðeins þeir hlutar sem eru mismunandi eftir notendum eru sendir í sessionvars.js. Þetta dregur ekki aðeins úr gagnagrunnsálagi og skapar betri upplifun fyrir notendur okkar, heldur dregur einnig úr skammtafræðilegum líkum á því að þjónninn okkar gufi upp af sjálfu sér í skekkjukjarnabroti þegar Klingons ráðast á.
Kóðinn sem skilað er fyrir hvern ramma mun hafa eina einföldu kröfu: Sýndu notandanum hversu oft hann hefur endurnýjað síðuna með því að segja "Count er x". Til að hafa hlutina einfalda, munum við halda okkur fjarri Redis biðröðum, Kubernetes íhlutum eða AWS Lambdas í bili.
Lotugögn hvers notanda verða vistuð í PostgreSQL gagnagrunni.
Og þessi gagnagrunnstafla verður stytt fyrir hvert próf.
Einfalt en áhrifaríkt er kjörorð Pafera... fyrir utan myrkustu tímalínuna samt...
Jæja, nú getum við loksins farið að skíta okkur í hendurnar. Við munum sleppa uppsetningunni fyrir Laravel þar sem það er bara fullt af tónskáldum og handverksmönnum skipanir.
Í fyrsta lagi munum við setja upp gagnagrunnsstillingar okkar í .env skránni
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Síðan munum við setja eina bakleið sem sendir allar beiðnir til stjórnandans okkar.
Route::fallback(SessionController::class);
Og stilltu stjórnandann til að sýna fjöldann. Laravel geymir sjálfgefið lotur í gagnagrunninum. Það veitir einnig session()
virka til að tengjast við lotugögnin okkar, svo það eina sem þurfti voru nokkrar línur af kóða til að gera síðuna okkar.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Eftir að hafa sett upp php-fpm og Nginx lítur síðan okkar nokkuð vel út...
╰─➤ 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
Að minnsta kosti þangað til við sjáum niðurstöðurnar í raun og veru...
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
Nei, það er ekki innsláttarvilla. Prófunarvélin okkar hefur farið úr 600 beiðnum á sekúndu sem skilar flókinni síðu... í 21 beiðni á sekúndu sem skilar "Count is 1".
Svo hvað fór úrskeiðis? Er eitthvað athugavert við PHP uppsetninguna okkar? Er Nginx einhvern veginn að hægja á sér þegar það tengist php-fpm?
Við skulum endurtaka þessa síðu í hreinum PHP kóða.
<?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'];
Við höfum nú notað 98 línur af kóða til að gera það sem fjórar línur af kóða (og fullt af stillingarvinnu) í Laravel gerðu. (Auðvitað, ef við gerðum rétta villumeðferð og skilaboð sem snúa að notendum, þá væri þetta um það bil tvöfalt fleiri línur.) Kannski getum við náð 30 beiðnum á sekúndu?
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
vá! Það lítur út fyrir að ekkert sé athugavert við PHP uppsetninguna okkar eftir allt saman. Hreina PHP útgáfan gerir 700 beiðnir á sekúndu.
Ef það er ekkert athugavert við PHP, höfum við kannski rangt stillt Laravel?
Eftir að hafa skoðað vefinn að uppsetningarvandamálum og ráðleggingum um frammistöðu voru tvær af vinsælustu aðferðunum að vista stillingarnar og leiða gögnin til að forðast að vinna úr þeim fyrir hverja beiðni. Þess vegna munum við taka ráðum þeirra og prófa þessar ráðleggingar.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
Allt lítur vel út á skipanalínunni. Við skulum endurtaka viðmiðið.
╰─➤ 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
Jæja, við höfum nú aukið frammistöðu úr 21,04 í 28,80 beiðnir á sekúndu, sem er stórkostleg hækkun upp á næstum 37%! Þetta væri alveg áhrifamikið fyrir hvaða hugbúnaðarpakka sem er... nema fyrir þá staðreynd að við erum enn að gera aðeins 1/24 hluta af fjölda beiðna í hreinu PHP útgáfunni.
Ef þú heldur að eitthvað hljóti að vera athugavert við þetta próf, ættirðu að tala við höfund Lucinda PHP ramma. Í niðurstöðum sínum hefur hann Lucinda barði Laravel um 36x fyrir HTML beiðnir og 90x fyrir JSON beiðnir.
Eftir að hafa prófað á eigin vél með bæði Apache og Nginx hef ég enga ástæðu til að efast um hann. Laravel er í raun bara það hægt! PHP í sjálfu sér er ekki svo slæmt, en þegar þú bætir við allri aukavinnslunni sem Laravel bætir við hverja beiðni, þá finnst mér mjög erfitt að mæla með Laravel sem vali árið 2023.
PHP/Wordpress reikningar fyrir um 40% allra vefsíðna á vefnum , sem gerir það að langstærsta umgjörðinni. Persónulega finnst mér þó vinsældir ekki endilega skila sér í gæðum frekar en ég finn fyrir skyndilegri óviðráðanlega löngun í þennan óvenjulega sælkeramat frá vinsælasti veitingastaður í heimi ... McDonald's. Þar sem við höfum þegar prófað hreinan PHP kóða, ætlum við ekki að prófa Wordpress sjálft, þar sem allt sem tengist Wordpress væri án efa lægra en 700 beiðnir á sekúndu sem við sáum með hreinu PHP.
Django er annar vinsæll rammi sem hefur verið til í langan tíma. Ef þú hefur notað það í fortíðinni, ertu líklega með ánægju að muna eftir stórbrotnu gagnagrunnsviðmóti þess ásamt því hversu pirrandi það var að stilla allt eins og þú vildir. Við skulum sjá hversu vel Django virkar árið 2023, sérstaklega með nýja ASGI viðmótinu sem það hefur bætt við frá og með útgáfu 4.0.
Uppsetning Django er ótrúlega lík því að setja upp Laravel, þar sem þeir voru báðir frá þeim aldri þar sem MVC arkitektúr var stílhrein og rétt. Við munum sleppa leiðinlegu uppsetningunni og fara beint í að setja upp útsýnið.
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}")
Fjórar línur af kóða er það sama og með Laravel útgáfuna. Við skulum sjá hvernig það virkar.
╰─➤ 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
Alls ekki slæmt við 355 beiðnir á sekúndu. Það er aðeins helmingur af frammistöðu hreinu PHP útgáfunnar, en það er líka 12x af Laravel útgáfunni. Django gegn Laravel virðist alls ekki vera nein keppni.
Fyrir utan stærri allt-þar á meðal-eldhúsvaskinn ramma, þá eru líka smærri rammar sem gera bara grunnuppsetningu en leyfa þér að sjá um afganginn. Einn af þeim bestu til að nota er Flask og ASGI hliðstæða Quart. Minn eigin PaferaPy Framework er byggt ofan á Flask, þannig að ég þekki vel hversu auðvelt það er að koma hlutum í verk og halda frammistöðu.
#!/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}'
Eins og þú sérð er Flask handritið styttra en hreina PHP handritið. Ég kemst að því að af öllum tungumálum sem ég hef notað er Python líklega tjáningarmesta tungumálið með tilliti til ásláttar sem slegnar eru inn. Skortur á axlaböndum og svigum, skilningi á lista og fyrirsögnum og lokun byggð á inndrætti frekar en semíkommum gerir Python frekar einfalt en öflugt í getu sinni.
Því miður er Python líka hægasta almenna tungumálið sem til er, þrátt fyrir hversu mikill hugbúnaður hefur verið skrifaður í það. Fjöldi Python bókasöfna sem eru í boði er um það bil fjórfalt fleiri en svipuð tungumál og nær yfir mikið magn af lénum, en samt myndi enginn segja að Python sé hraðvirkur né afkastamikill utan veggskota eins og NumPy.
Við skulum sjá hvernig Flask útgáfan okkar er í samanburði við fyrri ramma okkar.
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
Flask handritið okkar er í raun hraðar en hreina PHP útgáfan okkar!
Ef þú ert hissa á þessu ættirðu að gera þér grein fyrir því að Flask appið okkar gerir allar frumstillingar sínar og stillingar þegar við ræsum gunicorn þjóninn, á meðan PHP keyrir skriftuna aftur í hvert skipti sem ný beiðni kemur inn. Það' það jafngildir því að Flask sé ungi, ákafur leigubílstjórinn sem er búinn að setja bílinn í gang og bíður við hliðina á veginum, en PHP er gamli bílstjórinn sem situr heima hjá sér og bíður eftir að hringt sé inn og keyrir þá fyrst. yfir til að sækja þig. Þar sem ég er gamaldags strákur og kemur frá þeim tímum þar sem PHP var dásamleg breyting á venjulegar HTML og SHTML skrár, það er dálítið leiðinlegt að átta sig á hversu langur tími hefur liðið, en hönnunarmunurinn gerir það virkilega erfitt fyrir PHP að keppa á móti Python, Java og Node.js netþjónum sem halda sig bara í minni og sinna beiðni með lipurri vellíðan gúllara.
Flaskan gæti verið hraðvirkasta umgjörðin okkar hingað til, en það er í raun ansi gamall hugbúnaður. Python samfélagið skipti yfir í nýrri ósamstilltu ASGI netþjóna fyrir nokkrum árum og auðvitað hef ég sjálfur skipt með þeim.
Nýjasta útgáfan af Pafera Framework, PaferaPyAsync , er byggð á Starlette. Þrátt fyrir að það sé til ASGI útgáfa af Flask sem heitir Quart, var frammistöðumunurinn á Quart og Starlette nóg til að ég endurbætti kóðann minn á Starlette í staðinn.
Ósamstillt forritun getur verið ógnvekjandi fyrir marga, en það er í raun ekki erfitt hugtak þökk sé Node.js strákunum sem gerðu hugmyndina vinsæla fyrir meira en áratug síðan.
Við notuðum til að berjast gegn samhliða fjölþráðum, fjölvinnslu, dreifðri tölvuvinnslu, loforðstengingu og öllum þessum skemmtilegu tímum sem ótímabært eldra og þurrka marga gamalreynda forritara. Nú skrifum við bara async
fyrir framan aðgerðir okkar og await
fyrir framan hvaða kóða sem gæti tekið smá tíma að keyra. Hann er að sönnu orðrænnari en venjulegur kóði, en mun minna pirrandi í notkun en að þurfa að takast á við frumsamstillingar, sendingu skilaboða og að leysa loforð.
Starlette skráin okkar lítur svona út:
#!/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",
)
Eins og þú sérð er það nokkurn veginn afritað og límt úr Flask skriftunni okkar með aðeins nokkrum leiðarbreytingum og async/await
leitarorð.
Hversu miklar umbætur geta afritað og límt kóða raunverulega gefið okkur?
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
Við erum með nýjan meistara, dömur og herrar! Fyrra hámarkið okkar var hreina PHP útgáfan okkar með 704 beiðnir á sekúndu, sem síðan var tekin af Flask útgáfunni okkar með 1080 beiðnum á sekúndu. Starlette handritið okkar eyðir öllum fyrri keppinautum við 4562 beiðnir á sekúndu, sem þýðir 6x framför á hreinu PHP og 4x framför á Flask.
Ef þú hefur ekki breytt WSGI Python kóðanum þínum yfir í ASGI ennþá, gæti verið góður tími til að byrja núna.
Hingað til höfum við aðeins fjallað um PHP og Python ramma. Hins vegar notar stór hluti heimsins í raun Java, DotNet, Node.js, Ruby on Rails og aðra slíka tækni fyrir vefsíður sínar. Þetta er alls ekki yfirgripsmikið yfirlit yfir öll vistkerfi heimsins og lífverur, svo til að forðast forritunarsamsvar lífrænnar efnafræði, veljum við aðeins þá ramma sem auðveldast er að slá inn kóða fyrir. . sem Java er örugglega ekki.
Nema þú hafir falið þig undir eintakinu þínu af K&R C eða Knuth's Listin að forritun síðustu fimmtán ár hefur þú sennilega heyrt um Node.js. Við sem höfum verið til frá upphafi JavaScript erum annaðhvort ótrúlega hrædd, undrandi eða bæði yfir ástandi nútíma JavaScript, en því er ekki að neita að JavaScript er orðið að afl sem þarf líka á netþjónum sem vafrar. Þegar öllu er á botninn hvolft höfum við meira að segja innfæddar 64 bita heiltölur núna í tungumálinu! Það er miklu betra en allt sem er geymt í 64 bita fljótum!
ExpressJS er líklega auðveldasti Node.js þjónninn í notkun, svo við munum gera fljótlegt og óhreint Node.js/ExpressJS app til að þjóna teljaranum okkar.
/**********************************************************************
* 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}`));
Þessi kóði var í raun auðveldari að skrifa en Python útgáfurnar, þó að innfæddur JavaScript verði frekar ómeðhöndlaður þegar forrit verða stærri og allar tilraunir til að leiðrétta þetta eins og TypeScript verða fljótt orðaðari en Python.
Við skulum sjá hvernig þetta virkar!
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
Þú gætir hafa heyrt fornar (fornar á netstaðla alla vega...) þjóðsögur um Node.js' hraða, og þessar sögur eru að mestu sannar þökk sé stórbrotnu starfi sem Google hefur unnið með V8 JavaScript vélinni. Í þessu tilviki þó, þó að skyndiforritið okkar standi sig betur en Flask-handritið, er einþráður eðli þess sigrað af fjórum ósamstillingarferlunum sem Starlette Knight beitir sem segir "Ni!".
Við skulum fá meiri hjálp!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Allt í lagi! Nú er bardaginn jafn fjórir á móti fjórum! Við skulum mæla!
╰─➤ 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
Enn ekki alveg á stigi Starlette, en það er ekki slæmt fyrir fljótlega fimm mínútna JavaScript hakk. Frá eigin prófun minni er þessu handriti í raun haldið aftur af gagnagrunnstengingarstigi vegna þess að node-postgres er hvergi nærri eins skilvirkt og psycopg er fyrir Python. Að skipta yfir í sqlite þar sem gagnagrunnsbílstjórinn skilar yfir 3000 beiðnum á sekúndu fyrir sama ExpressJS kóða.
Aðalatriðið sem þarf að hafa í huga er að þrátt fyrir hægan framkvæmdarhraða Python geta ASGI rammar í raun verið samkeppnishæfir við Node.js lausnir fyrir ákveðið vinnuálag.
Þannig að núna erum við að nálgast toppinn á fjallinu, og með fjalli á ég við hæstu viðmiðunarstig sem skráð hafa verið af músum og körlum.
Ef þú skoðar flestar rammaviðmiðanir sem til eru á vefnum muntu taka eftir því að það eru tvö tungumál sem hafa tilhneigingu til að ráða efst: C++ og Rust. Ég hef unnið með C++ síðan á tíunda áratugnum, og ég hafði meira að segja minn eigin Win32 C++ ramma áður en MFC/ATL var eitthvað, svo ég hef mikla reynslu af tungumálinu. Það er ekki skemmtilegt að vinna með eitthvað þegar þú veist það nú þegar, svo við ætlum að gera Rust útgáfu í staðinn. ;)
Rust er tiltölulega nýtt hvað forritunarmál varðar, en það varð mér forvitnilegt þegar Linus Torvalds tilkynnti að hann myndi samþykkja Rust sem Linux kjarna forritunarmál. Fyrir okkur eldri forritarana er það um það bil það sama og að segja að þessi nýja fangleði nýaldar hippaþráður myndi verða ný breyting á stjórnarskrá Bandaríkjanna.
Nú, þegar þú ert reyndur forritari, hefur þú tilhneigingu til að hoppa ekki á vagninn eins hratt og yngra fólkið gerir, annars gætirðu brennt þig af hröðum breytingum á tungumálinu eða bókasöfnum. (Allir sem notuðu fyrstu útgáfuna af AngularJS vita hvað ég er að tala um.) Ryð er enn nokkuð á tilraunastigi og mér finnst fyndið að mörg kóðadæmi á vefnum eru ekki einu sinni setja saman lengur með núverandi útgáfum af pakka.
Hins vegar er ekki hægt að afneita frammistöðunni sem Rust forritin sýna. Ef þú hefur aldrei reynt ripgrep eða fd-finna út á stórum frumkóðatré, þú ættir örugglega að gefa þeim snúning. Þeir eru jafnvel fáanlegir fyrir flestar Linux dreifingar einfaldlega frá pakkastjóranum. Þú ert að skipta um orðræðu fyrir frammistöðu með Rust... a mikið af orðræðu fyrir a mikið af frammistöðu.
Heili kóðinn fyrir Rust er svolítið stór, svo við munum bara skoða viðeigandi meðhöndlun hér:
// =====================================================================
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
)))
}
Þetta er miklu flóknara en Python/Node.js útgáfurnar...
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
Og miklu afkastameiri!
Rust netþjónninn okkar sem notar Actix/deadpool_postgres slær fyrri meistara Starlette vel um +125%, ExpressJS um +362% og hreint PHP um +1366%. (Ég mun skilja eftir frammistöðudeltuna með Laravel útgáfunni sem æfingu fyrir lesandann.)
Ég hef komist að því að það hefur verið erfiðara að læra Rust tungumálið sjálft en önnur tungumál þar sem það hefur miklu fleiri möguleika en allt sem ég hef séð utan 6502 Assembly, en ef Rust þjónninn þinn getur tekið á sig 14x fjölda notendur sem PHP netþjóninn þinn, þá er kannski eitthvað hægt að vinna með því að skipta um tækni eftir allt saman. Þess vegna verður næsta útgáfa af Pafera Framework byggð á Rust. Námsferillinn er miklu hærri en forskriftarmál, en frammistaðan verður þess virði. Ef þú getur ekki lagt tíma í að læra Rust, þá er það heldur ekki slæm ákvörðun að byggja tæknibunkann þinn á Starlette eða Node.js.
Á síðustu tuttugu árum höfum við farið frá ódýrum kyrrstæðum hýsingarsíðum yfir í sameiginlega hýsingu með LAMP stafla til að leigja VPS til AWS, Azure og annarra skýjaþjónustu. Nú á dögum eru mörg fyrirtæki ánægð með að taka hönnunarákvarðanir byggðar á hverjum þeim sem þeir geta fundið að er í boði eða ódýrast þar sem tilkoma þægilegrar skýjaþjónustu hefur gert það auðvelt að henda meiri vélbúnaði á hæga netþjóna og forrit. Þetta hefur gefið þeim mikinn skammtímahagnað á kostnað langtíma tæknilegra skulda.
Fyrir 70 árum var mikið geimkapphlaup milli Sovétríkjanna og Bandaríkjanna. Sovétmenn unnu flest fyrstu tímamótin. Þeir voru með fyrsta gervihnöttinn í Spútnik, fyrsta hundinn í geimnum í Laika, fyrsta tunglgeimfarið í Luna 2, fyrsta karlinn og konuna í geimnum í Yuri Gagarin og Valentina Tereshkova, og svo framvegis...
En þeir voru hægt og rólega að safna tæknilegum skuldum.
Þrátt fyrir að Sovétmenn hafi verið fyrstir að öllum þessum afrekum, ollu verkfræðiferli þeirra og markmiðum þess að þeir einbeittu sér að skammtímaáskorunum frekar en langtímafýsileika. Þeir unnu í hvert skipti sem þeir stökkva, en þeir voru að verða þreyttari og hægari á meðan andstæðingar þeirra héldu áfram að taka stöðugt skref í átt að marklínunni.
Þegar Neil Armstrong tók söguleg skref sín á tunglinu í beinni sjónvarpsútsendingu tóku Bandaríkjamenn forystuna og héldu sig síðan þar sem sovéska dagskráin hiknaði. Þetta er ekkert öðruvísi en fyrirtæki í dag sem hafa einbeitt sér að næsta stóra hlutnum, næsta stóra afborgun eða næstu stóru tækni á meðan ekki tekst að þróa viðeigandi venjur og aðferðir til lengri tíma litið.
Að vera fyrstur á markað þýðir ekki að þú verðir ráðandi aðili á þeim markaði. Að öðrum kosti, að taka tíma til að gera hlutina rétt tryggir ekki árangur, en eykur vissulega möguleika þína á langtíma árangri. Ef þú ert tæknileiðtogi fyrirtækisins þíns skaltu velja réttu stefnuna og verkfærin fyrir vinnuálagið. Ekki láta vinsældir koma í stað frammistöðu og skilvirkni.
Viltu hlaða niður 7z skrá sem inniheldur Rust, ExpressJS, Flask, Starlette og Pure PHP forskriftirnar?
Um höfundinn |
|
![]() |
Jim hefur verið að forrita síðan hann fékk IBM PS/2 aftur á tíunda áratugnum. Enn þann dag í dag vill hann frekar skrifa HTML og SQL í höndunum og leggur áherslu á skilvirkni og réttmæti í starfi sínu. |