Po enem od mojih zadnjih razgovorov za službo sem bil presenečen ugotovil, da podjetje, za katerega sem se prijavil, še vedno uporablja Laravel, PHP okvir, ki sem ga preizkusil pred približno desetletjem. Za tisti čas je bilo spodobno, a če obstaja ena stalnica v tehnologiji in modi, je to nenehno spreminjanje in ponovno pojavljanje stilov in konceptov. Če ste programer JavaScript, ste verjetno seznanjeni s to staro šalo
Programer 1: "Ni mi všeč to novo ogrodje JavaScript!"
Programer 2: "Ni vam treba skrbeti. Samo počakajte šest mesecev in prišel bo drug, ki ga bo nadomestil!"
Zaradi radovednosti sem se odločil natančno videti, kaj se zgodi, ko preizkusimo staro in novo. Seveda je splet poln meril uspešnosti in trditev, med katerimi je verjetno najbolj priljubljena Primerjalna merila spletnega ogrodja TechEmpower tukaj . Vendar danes ne bomo naredili ničesar tako zapletenega kot oni. Stvari bomo ohranili lepe in enostavne, tako da se ta članek ne bo spremenil v Vojna in mir , in da boste imeli majhno možnost, da ostanete budni, ko končate z branjem. Veljajo običajna opozorila: to morda ne bo delovalo enako na vašem računalniku, različne različice programske opreme lahko vplivajo na zmogljivost in Schrödingerjeva mačka je dejansko postala mačka zombi, ki je bila napol živa in napol mrtva ob istem času.
Za ta preizkus bom uporabljal svoj prenosnik, oborožen s slabim i5, ki poganja Manjaro Linux, kot je prikazano tukaj.
╰─➤ 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
Naša koda bo imela tri preproste naloge za vsako zahtevo:
Kakšen idiotski test je to, se boste morda vprašali? No, če pogledate omrežne zahteve za to stran, boste opazili eno, imenovano sessionvars.js, ki dela popolnoma isto stvar.
Vidite, sodobne spletne strani so zapletena bitja in ena najpogostejših nalog je predpomnjenje zapletenih strani, da se izognete prekomerni obremenitvi strežnika baze podatkov.
Če znova upodobimo zapleteno stran vsakič, ko jo uporabnik zahteva, potem lahko oskrbimo samo približno 600 uporabnikov na sekundo.
╰─➤ 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
Če pa to stran shranimo v predpomnilnik kot statično datoteko HTML in pustimo, da jo Nginx uporabniku hitro vrže skozi okno, potem lahko oskrbimo 32.000 uporabnikov na sekundo, s čimer povečamo zmogljivost za faktor 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
Statični index.en.html je del, ki je namenjen vsem, v sessionvars.js pa so poslani le deli, ki se razlikujejo glede na uporabnika. To ne le zmanjša obremenitev baze podatkov in ustvari boljšo izkušnjo za naše uporabnike, ampak tudi zmanjša kvantno verjetnost, da bo naš strežnik spontano izhlapel v vdoru v warp jedro, ko Klingonci napadejo.
Vrnjena koda za vsako ogrodje bo imela eno preprosto zahtevo: pokažite uporabniku, kolikokrat je osvežil stran, tako da rečete "Štetje je x". Da bi stvari poenostavili, se bomo za zdaj izogibali čakalnim vrstam Redis, komponentam Kubernetes ali AWS Lambdas.
Podatki o seji vsakega uporabnika bodo shranjeni v bazi podatkov PostgreSQL.
In ta tabela baze podatkov bo okrnjena pred vsakim preizkusom.
Enostaven, a učinkovit je moto Pafera ... zunaj najtemnejše časovne osi ...
V redu, zdaj si lahko končno začnemo umazati roke. Preskočili bomo nastavitev za Laravel, ker je le skupek skladateljev in rokodelcev ukazi.
Najprej bomo v datoteki .env nastavili nastavitve baze podatkov
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Nato bomo nastavili eno samo nadomestno pot, ki pošlje vsako zahtevo našemu krmilniku.
Route::fallback(SessionController::class);
In nastavite krmilnik, da prikaže štetje. Laravel privzeto shranjuje seje v bazo podatkov. Zagotavlja tudi session()
funkcija za vmesnik z našimi podatki o seji, tako da je bilo potrebnih le nekaj vrstic kode za upodobitev naše strani.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Po nastavitvi php-fpm in Nginx je naša stran videti precej dobro ...
╰─➤ 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
Vsaj dokler dejansko ne vidimo rezultatov 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
Ne, to ni tipkarska napaka. Naš testni stroj je prešel s 600 zahtev na sekundo za upodabljanje zapletene strani... na 21 zahtev na sekundo za upodabljanje "Count is 1".
Torej, kaj je šlo narobe? Je kaj narobe z našo namestitvijo PHP? Ali se Nginx nekako upočasnjuje pri povezovanju s php-fpm?
Ponovimo to stran v čisti kodi PHP.
<?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'];
Zdaj smo uporabili 98 vrstic kode za to, kar so naredile štiri vrstice kode (in cel kup konfiguracijskega dela) v Laravelu. (Seveda bi bilo to približno dvakratno število vrstic, če bi pravilno obravnavali napake in sporočila, ki bi jih posredovali uporabnikom.) Morda lahko dosežemo 30 zahtev na sekundo?
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
Vau! Videti je, da z našo namestitvijo PHP kljub vsemu ni nič narobe. Čista različica PHP naredi 700 zahtev na sekundo.
Če s PHP-jem ni nič narobe, smo morda napačno konfigurirali Laravel?
Po brskanju po spletu za težavami s konfiguracijo in nasveti o zmogljivosti sta bili dve izmed najbolj priljubljenih tehnik predpomnilnik podatkov o konfiguraciji in usmerjanju, da bi se izognili njihovi obdelavi za vsako zahtevo. Zato bomo upoštevali njihov nasvet in preizkusili te nasvete.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
V ukazni vrstici je vse videti dobro. Ponovimo merilo uspešnosti.
╰─➤ 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
No, zdaj smo povečali zmogljivost z 21,04 na 28,80 zahtev na sekundo, dramatično povečanje za skoraj 37 %! To bi bilo precej impresivno za kateri koli programski paket ... razen dejstva, da še vedno izvajamo le 1/24 števila zahtev čiste različice PHP.
Če menite, da je s tem testom nekaj narobe, se pogovorite z avtorjem ogrodja Lucinda PHP. Po njegovih rezultatih testov je Lucinda premaga Laravel za 36x za zahteve HTML in 90x za zahteve JSON.
Po testiranju na lastnem računalniku z Apache in Nginxom nimam razloga dvomiti vanj. Laravel je res pravi to počasi! PHP sam po sebi ni tako slab, a ko dodate vso dodatno obdelavo, ki jo Laravel doda vsaki zahtevi, se mi zdi zelo težko priporočiti Laravel kot izbiro v letu 2023.
PHP/Wordpress računi za približno 40 % vseh spletnih mest na spletu , zaradi česar je daleč najbolj prevladujoč okvir. Osebno pa ugotavljam, da se priljubljenost ne prevede nujno v kakovost, prav tako ne ugotovim, da imam nenadoma neobvladljivo željo po tej izjemni gurmanski hrani iz najbolj priljubljena restavracija na svetu ... McDonald's. Ker smo že preizkusili čisto kodo PHP, ne bomo testirali samega Wordpressa, saj bi bilo vse, kar vključuje Wordpress, nedvomno nižje od 700 zahtev na sekundo, ki smo jih opazili pri čistem PHP.
Django je še eno priljubljeno ogrodje, ki obstaja že dolgo časa. Če ste ga že uporabljali v preteklosti, se verjetno z veseljem spominjate njegovega spektakularnega skrbniškega vmesnika baze podatkov in tega, kako nadležno je bilo vse konfigurirati tako, kot ste želeli. Poglejmo, kako dobro bo Django deloval leta 2023, zlasti z novim vmesnikom ASGI, ki ga je dodal od različice 4.0.
Nastavitev Djanga je izjemno podobna nastavitvi Laravela, saj sta oba iz obdobja, ko so bile arhitekture MVC elegantne in pravilne. Preskočili bomo dolgočasno konfiguracijo in šli naravnost k nastavitvi pogleda.
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}")
Štiri vrstice kode so enake kot pri različici Laravel. Poglejmo, kako se obnese.
╰─➤ 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
Sploh ni slabo pri 355 zahtevah na sekundo. To je samo polovica zmogljivosti čiste različice PHP, vendar je tudi 12-krat večja od različice Laravel. Zdi se, da Django proti Laravelu sploh ni tekmovanje.
Poleg večjih ogrodij vse-vključno-kuhinjsko-pomivalno korito obstajajo tudi manjša ogrodja, ki opravijo samo nekaj osnovnih nastavitev, medtem ko vam omogočajo, da poskrbite za ostalo. Eden najboljših za uporabo je Flask in njegov dvojnik ASGI Quart. Mojega Ogrodje PaferaPy je zgrajen na vrhu Flaska, zato sem dobro seznanjen s tem, kako preprosto je opraviti stvari in hkrati ohraniti učinkovitost.
#!/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}'
Kot lahko vidite, je skript Flask krajši od čistega skripta PHP. Ugotavljam, da je izmed vseh jezikov, ki sem jih uporabljal, Python verjetno najbolj izrazit jezik v smislu vnesenih tipk. Zaradi pomanjkanja oklepajev in oklepajev, razumevanja seznamov in narekov ter blokiranja, ki temelji na zamiku namesto podpičja, je Python precej preprost, a zmogljiv v svojih zmogljivostih.
Na žalost je Python tudi najpočasnejši splošni jezik, ne glede na to, koliko programske opreme je bilo napisanega v njem. Število razpoložljivih knjižnic Python je približno štirikrat večje od podobnih jezikov in pokriva ogromno področij, vendar nihče ne bi rekel, da je Python hiter ali zmogljiv zunaj niš, kot je NumPy.
Poglejmo, kakšna je naša različica Flask v primerjavi z našimi prejšnjimi okviri.
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
Naš skript Flask je dejansko hitrejši od naše čiste različice PHP!
Če ste nad tem presenečeni, se morate zavedati, da naša aplikacija Flask opravi vso svojo inicializacijo in konfiguracijo, ko zaženemo strežnik gunicorn, medtem ko PHP znova izvede skript vsakič, ko pride nova zahteva. To' ;je enakovredno temu, da je Flask mlad, nestrpen taksist, ki je že zagnal avto in čaka ob cesti, medtem ko je PHP stari voznik, ki ostane doma in čaka na klic in šele nato odpelje da te poberem. Ker sem človek stare šole in prihajam iz časov, ko je bil PHP čudovita sprememba navadnih datotek HTML in SHTML, je nekoliko žalostno ugotoviti, koliko časa je minilo, toda razlike v oblikovanju za PHP resnično otežujejo tekmujejo s strežniki Python, Java in Node.js, ki samo ostanejo v pomnilniku in obravnavajo zahteve z okretno lahkotnostjo žonglerja.
Flask je morda naše najhitrejše ogrodje doslej, vendar je pravzaprav precej stara programska oprema. Skupnost Python je pred nekaj leti prešla na novejše asinhrone strežnike ASGI in seveda sem tudi sam prešel skupaj z njimi.
Najnovejša različica Pafera Framework, PaferaPyAsync , temelji na Starlette. Čeprav obstaja ASGI različica Flaska z imenom Quart, so bile razlike v zmogljivosti med Quartom in Starlette dovolj, da sem svojo kodo namesto tega preoblikoval na Starlette.
Asinhrono programiranje je lahko za veliko ljudi prestrašeno, vendar pravzaprav ni težak koncept, zahvaljujoč fantom iz Node.js, ki so ta koncept popularizirali pred več kot desetletjem.
Proti sočasnosti smo se borili z večnitnostjo, večprocesiranjem, porazdeljenim računalništvom, veriženjem obljub in vsemi tistimi zabavnimi časi, ki so prezgodaj postarali in izsušili mnoge veteranske programerje. Zdaj samo tipkamo async
pred našimi funkcijami in await
pred vsako kodo, katere izvedba lahko traja nekaj časa. Res je bolj natančen kot običajna koda, vendar je veliko manj moteč za uporabo, kot če bi se morali ukvarjati s primitivi za sinhronizacijo, posredovanjem sporočil in razreševanjem obljub.
Naša datoteka Starlette izgleda takole:
#!/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",
)
Kot lahko vidite, je v veliki meri kopiran in prilepljen iz našega skripta Flask z le nekaj spremembami usmerjanja in async/await
ključne besede.
Koliko izboljšav nam lahko dejansko omogoči kopiranje in lepljenje kode?
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
Imamo novega prvaka, dame in gospodje! Naš prejšnji rekord je bila naša čista različica PHP s 704 zahtevami na sekundo, ki jo je nato prehitela naša različica Flask s 1080 zahtevami na sekundo. Naš skript Starlette premaga vse prejšnje tekmece s 4562 zahtevami na sekundo, kar pomeni 6-kratno izboljšavo v primerjavi s čistim PHP in 4-kratno izboljšavo v primerjavi s Flaskom.
Če svoje kode WSGI Python še niste spremenili v ASGI, je zdaj morda pravi čas, da začnete.
Doslej smo obravnavali le okvira PHP in Python. Vendar pa velik del sveta dejansko uporablja Javo, DotNet, Node.js, Ruby on Rails in druge podobne tehnologije za svoja spletna mesta. To nikakor ni izčrpen pregled vseh svetovnih ekosistemov in biomov, zato bomo, da bi se izognili programiranju, ki je enakovredno organski kemiji, izbrali le okvire, za katere je najlažje vnesti kodo. ., kar Java zagotovo ni.
Razen če ste se skrivali pod svojo kopijo K&R C ali Knuth's Umetnost računalniškega programiranja v zadnjih petnajstih letih ste verjetno že slišali za Node.js. Tisti med nami, ki smo prisotni od začetka JavaScripta, smo nad stanjem sodobnega JavaScripta bodisi neverjetno prestrašeni, presenečeni ali oboje, vendar ni mogoče zanikati, da je JavaScript postal sila, s katero je treba računati tudi na strežnikih. kot brskalniki. Navsezadnje imamo zdaj v jeziku celo domača 64-bitna cela števila! To je daleč bolje kot vse, kar je shranjeno v 64-bitnih plavajočih!
ExpressJS je verjetno najlažji strežnik Node.js za uporabo, zato bomo naredili hitro in umazano aplikacijo Node.js/ExpressJS, ki bo služila našemu števcu.
/**********************************************************************
* 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}`));
To kodo je bilo dejansko lažje napisati kot različice Pythona, čeprav postane izvorni JavaScript precej okoren, ko aplikacije postanejo večje, in vsi poskusi popravka tega, kot je TypeScript, hitro postanejo bolj podrobni kot Python.
Poglejmo, kako se to obnese!
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
Morda ste slišali starodavne (itak starodavne po internetnih standardih...) ljudske pravljice o Node.js' hitrosti in te zgodbe so večinoma resnične zahvaljujoč spektakularnemu delu, ki ga je Google opravil z motorjem V8 JavaScript. Čeprav v tem primeru naša hitra aplikacija prekaša skript Flask, njeno naravo z eno nitjo premagajo štirje asinhroni procesi, ki jih upravlja Starlette Knight, ki reče »Ni!«.
Poiščimo še pomoč!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
V redu! Zdaj je to izenačena bitka štiri na štiri! Opravimo primerjavo!
╰─➤ 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
Še vedno ni povsem na ravni Starlette, vendar ni slabo za hiter petminutni vdor v JavaScript. Iz mojega lastnega testiranja je ta skript dejansko nekoliko zadržan na ravni vmesnika baze podatkov, ker node-postgres ni niti približno tako učinkovit kot psycopg za Python. Preklop na sqlite kot gonilnik baze podatkov prinaša več kot 3000 zahtev na sekundo za isto kodo ExpressJS.
Glavna stvar, ki jo je treba opozoriti, je, da so kljub nizki hitrosti izvajanja Pythona okviri ASGI dejansko lahko konkurenčni rešitvam Node.js za določene delovne obremenitve.
Zdaj smo vse bližje vrhu gore, z goro pa mislim na najvišje primerjalne rezultate, ki so jih zabeležili miši in ljudje.
Če pogledate večino meril uspešnosti ogrodja, ki so na voljo v spletu, boste opazili, da dva jezika prevladujeta na vrhu: C++ in Rust. S C++ delam že od 90. let prejšnjega stoletja in sem imel celo lastno ogrodje Win32 C++, preden je bil MFC/ATL stvar, tako da imam veliko izkušenj z jezikom. Ni zabavno delati z nečim, če to že poznate, zato bomo namesto tega naredili različico Rust. ;)
Rust je razmeroma nov, kar se tiče programskih jezikov, vendar me je zanimalo, ko je Linus Torvalds objavil, da bo sprejel Rust kot programski jezik jedra Linuxa. Za nas starejše programerje je to približno enako, kot če bi rekli, da bo ta novodobna hipijevska stvar nove dobe nov amandma k ustavi ZDA.
Zdaj, ko ste izkušen programer, se ne nagibate k temu, da bi skakali tako hitro kot mlajši, sicer se lahko opečete zaradi hitrih sprememb jezika ali knjižnic. (Vsakdo, ki je uporabljal prvo različico AngularJS, bo vedel, o čem govorim.) Rust je še vedno nekoliko v tej eksperimentalni razvojni fazi in zdi se mi smešno, da toliko primerov kode na spletu sploh ne prevajati s trenutnimi različicami paketov.
Vendar zmogljivosti, ki jo kažejo aplikacije Rust, ni mogoče zanikati. Če še nikoli niste poskusili ripgrep oz fd-najdi na velikih drevesih izvorne kode, bi jih vsekakor morali preizkusiti. Na voljo so celo za večino distribucij Linuxa preprosto prek upravitelja paketov. Z Rustom zamenjate besedičnost za uspešnost ... a veliko besednosti za a veliko uspešnosti.
Celotna koda za Rust je nekoliko velika, zato si bomo tukaj samo ogledali ustrezne upravljalnike:
// =====================================================================
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
)))
}
To je veliko bolj zapleteno kot različice 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
In veliko bolj zmogljiv!
Naš strežnik Rust, ki uporablja Actix/deadpool_postgres, hitro premaga našega prejšnjega prvaka Starlette za +125%, ExpressJS za +362% in čisti PHP za +1366%. (Delto zmogljivosti bom pustil pri različici Laravel kot vajo za bralca.)
Ugotovil sem, da je bilo učenje samega jezika Rust težje kot drugih jezikov, saj ima veliko več težav kot kar koli drugega, kar sem videl zunaj 6502 Assembly, vendar če lahko vaš strežnik Rust prenese 14-krat večje število uporabnikov kot vaš strežnik PHP, potem boste morda vendarle lahko kaj pridobili s preklopnimi tehnologijami. Zato bo naslednja različica Pafera Framework temeljila na Rustu. Krivulja učenja je veliko višja kot pri skriptnih jezikih, vendar bo zmogljivost vredna tega. Če si ne morete vzeti časa za učenje Rusta, potem tudi osnovanje tehnološkega sklada na Starlette ali Node.js ni slaba odločitev.
V zadnjih dvajsetih letih smo od poceni statičnih spletnih mest za gostovanje prešli na skupno gostovanje s skladi LAMP do najema VPS-jev za AWS, Azure in druge storitve v oblaku. Dandanes so mnoga podjetja zadovoljna s sprejemanjem oblikovalskih odločitev glede na to, koga najdejo in je na voljo ali je najcenejša, saj je pojav priročnih storitev v oblaku olajšal vlaganje več strojne opreme v počasne strežnike in aplikacije. To jim je prineslo velike kratkoročne dobičke na račun dolgoročnega tehničnega dolga.
Pred 70 leti je med Sovjetsko zvezo in ZDA potekala velika vesoljska tekma. Sovjeti so osvojili večino prvih mejnikov. Imeli so prvi satelit v Sputniku, prvega psa v vesolju v Laiki, prvo lunino vesoljsko plovilo v Luni 2, prvega moškega in žensko v vesolju v Juriju Gagarinu in Valentini Tereškovi in tako naprej ...
Toda počasi so si nabirali tehnični dolg.
Čeprav so bili Sovjeti prvi pri vsakem od teh dosežkov, so se njihovi inženirski procesi in cilji osredotočali na kratkoročne izzive in ne na dolgoročno izvedljivost. Zmagali so ob vsakem skoku, vendar so postajali vse bolj utrujeni in počasnejši, medtem ko so se njihovi nasprotniki enakomerno približevali cilju.
Ko je Neil Armstrong na televiziji v živo naredil svoje zgodovinske korake na Luni, so Američani prevzeli vodstvo, nato pa ostali tam, ko je sovjetski program zatajil. To se ne razlikuje od današnjih podjetij, ki so se osredotočila na naslednjo veliko stvar, naslednji veliki izkupiček ali naslednjo veliko tehnologijo, medtem ko jim ni uspelo razviti pravilnih navad in dolgoročnih strategij.
Biti prvi na trgu ne pomeni, da boste postali prevladujoči igralec na tem trgu. Druga možnost je, da si vzamete čas, da naredite stvari pravilno, ne zagotavlja uspeha, zagotovo pa poveča vaše možnosti za dolgoročne dosežke. Če ste tehnični vodja svojega podjetja, izberite pravo smer in orodja za svojo delovno obremenitev. Ne dovolite, da priljubljenost nadomesti zmogljivost in učinkovitost.
Ali želite prenesti datoteko 7z, ki vsebuje skripte PHP Rust, ExpressJS, Flask, Starlette in Pure?
O avtorju |
|
![]() |
Jim programira, odkar je dobil IBM PS/2 v 90-ih. Še danes najraje ročno piše HTML in SQL, pri svojem delu pa se osredotoča na učinkovitost in korektnost. |