По едно од моите најнови интервјуа за работа, бев изненаден кога сфатив дека компанијата за која аплицирав сè уште користи Laravel, PHP рамка што ја пробав пред околу една деценија. Тоа беше пристојно за тоа време, но ако постои една константа во технологијата и модата, тоа е постојана промена и повторно појавување на стилови и концепти. Ако сте програмер на JavaScript, веројатно сте запознаени со оваа стара шега
Програмер 1: "Не ми се допаѓа оваа нова JavaScript рамка!"
Програмер 2: „Нема потреба да се грижите. Само почекајте шест месеци и ќе има уште еден да го замени!"
Од љубопитност решив да видам што точно ќе се случи кога ќе ги ставиме на тест старото и новото. Се разбира, мрежата е исполнета со репери и тврдења, од кои најпопуларна е веројатно онаа TechEmpower Web Framework одредници овде . Сепак, нема да направиме ништо ни приближно комплицирано како нив денес. Ќе ги одржуваме работите убави и едноставни, така што овој напис нема да се претвори во Војна и мир , и дека ќе имате мали шанси да останете будни додека не завршите со читањето. Важат вообичаените предупредувања: ова може да не работи исто на вашата машина, различните верзии на софтвер може да влијаат на перформансите, а мачката на Шредингер всушност стана мачка зомби која беше полужива и половина мртва точно во исто време.
За овој тест, ќе го користам мојот лаптоп вооружен со слаб i5 со Manjaro Linux како што е прикажано овде.
╰─➤ 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
Нашиот код ќе има три едноставни задачи за секое барање:
Каков идиотски тест е тоа, можеби ќе прашате? Па, ако ги погледнете мрежните барања за оваа страница, ќе забележите едно наречено sessionvars.js кое го прави истото.
Гледате, модерните веб-страници се комплицирани суштества, а една од најчестите задачи е кеширање на сложени страници за да се избегне прекумерно оптоварување на серверот на базата на податоци.
Ако повторно прикажуваме сложена страница секогаш кога корисникот ја бара, тогаш можеме да опслужиме само околу 600 корисници во секунда.
╰─➤ 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
Но, ако ја кешираме оваа страница како статична HTML-датотека и дозволиме Nginx брзо да ја фрли низ прозорецот до корисникот, тогаш можеме да опслужуваме 32.000 корисници во секунда, зголемувајќи ги перформансите за 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
Статичниот index.en.html е делот што оди кај сите, а само деловите што се разликуваат по корисник се испраќаат во sessionvars.js. Ова не само што го намалува оптоварувањето на базата на податоци и создава подобро искуство за нашите корисници, туку ги намалува и квантните веројатности нашиот сервер спонтано да испарува при пробивање на јадрото на искривување при нападот на Клингонците.
Вратениот код за секоја рамка ќе има едно едноставно барање: покажете му на корисникот колку пати ја освежил страницата со велејќи „Бројот е x“. За работите да бидат едноставни, засега ќе се држиме подалеку од редиците на Redis, компонентите на Kubernetes или AWS Lambdas.
Податоците за сесијата на секој корисник ќе бидат зачувани во базата на податоци PostgreSQL.
И оваа табела со база на податоци ќе биде скратена пред секој тест.
Едноставно, но ефективно е мотото на Пафера... и онака надвор од најтемната временска линија...
Добро, па сега конечно можеме да почнеме да ги валкаме рацете. Ќе го прескокнеме поставувањето за Laravel бидејќи тоа е само еден куп композитори и занаетчии команди.
Прво, ќе ги поставиме поставките за нашата база на податоци во датотеката .env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Потоа ќе поставиме една единствена резервна рута што го испраќа секое барање до нашиот контролер.
Route::fallback(SessionController::class);
И поставете го контролорот да го прикажува броењето. Ларавел, стандардно, складира сесии во базата на податоци. Таа, исто така обезбедува на session()
функцијата за интерфејс со нашите податоци од сесијата, па се што беа потребни беа неколку линии код за да се прикаже нашата страница.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
По поставувањето php-fpm и Nginx, нашата страница изгледа прилично добро...
╰─➤ 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
Барем додека не ги видиме резултатите од тестот...
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
Не, тоа не е печатна грешка. Нашата машина за тестирање се зголеми од 600 барања во секунда за прикажување на сложена страница... на 21 барање во секунда за прикажување "Бројот е 1".
Значи, што тргна наопаку? Дали нешто не е во ред со нашата инсталација на PHP? Дали Nginx некако забавува кога се поврзува со php-fpm?
Да ја повториме оваа страница во чист 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'];
Сега користевме 98 линии код за да го направиме она што го направија четири линии код (и цел куп конфигурациски работи) во Ларавел. (Се разбира, ако направиме правилно справување со грешките и пораките со кои се соочува корисникот, ова би било отприлика двојно повеќе од бројот на линии.) Можеби можеме да достигнеме 30 барања во секунда?
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
Уф! Се чини дека на крајот на краиштата нема ништо лошо во нашата инсталација на PHP. Чистата PHP верзија прави 700 барања во секунда.
Ако нема ништо лошо со PHP, можеби погрешно го конфигуриравме Ларавел?
По пребарувањето на мрежата за проблеми со конфигурацијата и совети за изведба, две од најпопуларните техники беа кеширањето на конфигурацијата и податоците за насочување за да се избегне нивна обработка за секое барање. Затоа, ќе ги земеме нивните совети и ќе ги испробаме овие совети.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
Сè изгледа добро на командната линија. Ајде да го повториме реперот.
╰─➤ 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
Па, сега ги зголемивме перформансите од 21,04 на 28,80 барања во секунда, драматично зголемување од речиси 37%! Ова би било доста импресивно за секој софтверски пакет... освен фактот дека сè уште правиме само 1/24 од бројот на барања од чистата верзија на PHP.
Ако мислите дека нешто мора да не е во ред со овој тест, треба да разговарате со авторот на рамката Lucinda PHP. Во резултатите од тестот, тој има Лусинда го победи Ларавел за 36x за барања за HTML и 90x за барања за JSON.
По тестирањето на мојата сопствена машина и со Apache и со Nginx, немам причина да се сомневам во него. Ларавел е навистина праведен тоа бавно! PHP сам по себе не е толку лош, но штом ќе ја додадете целата дополнителна обработка што Ларавел ја додава на секое барање, тогаш ми е многу тешко да го препорачам Ларавел како избор во 2023 година.
PHP/Wordpress сметки за околу 40% од сите веб-локации на веб , што го прави убедливо најдоминантна рамка. Лично, сепак, сметам дека популарноста не мора да значи квалитет повеќе отколку што имам ненадеен неконтролиран нагон за таа извонредна гурманска храна од најпопуларниот ресторан во светот ... McDonald' Бидејќи веќе тестиравме чист PHP код, нема да го тестираме самиот Wordpress, бидејќи сè што вклучува Wordpress несомнено би било помало од 700 барања во секунда што ги забележавме со чист PHP.
Django е уште една популарна рамка која постои долго време. Ако сте го користеле во минатото, веројатно со задоволство се сеќавате на неговиот спектакуларен интерфејс за администрирање на базата на податоци, како и колку е досадно да конфигурирате сè онака како што сакате. Ајде да видиме колку добро функционира Django во 2023 година, особено со новиот ASGI интерфејс што го додаде од верзијата 4.0.
Поставувањето на Django е неверојатно слично на поставувањето на Laravel, бидејќи и двајцата беа од времето кога MVC архитектурите беа стилски и точни. Ќе ја прескокнеме здодевната конфигурација и ќе одиме директно на поставување на приказот.
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}")
Четири линии код се исти како кај верзијата Ларавел. Ајде да видиме како функционира.
╰─➤ 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
Воопшто не е лошо со 355 барања во секунда. Тоа е само половина од перформансите на чистата PHP верзија, но исто така е 12 пати поголема од онаа на верзијата Laravel. Џанго против Ларавел се чини дека воопшто нема натпревар.
Освен поголемите рамки за сè, вклучително и мијалникот во кујната, има и помали рамки кои само прават некои основни поставки додека ви дозволуваат да се справите со останатото. Еден од најдобрите за употреба е Flask и неговиот ASGI колега Quart. Моето Рамка на PaferaPy е изграден на врвот на Flask, така што добро сум запознаен со тоа колку е лесно да се завршат работите додека се одржуваат перформансите.
#!/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}'
Како што можете да видите, скриптата Flask е пократка од чистата PHP скрипта. Сфатив дека од сите јазици што сум ги користел, Python е веројатно најизразениот јазик во однос на тастатурата на тастатурата. Недостатокот на загради и загради, разбирање на списоци и дикти, како и блокирањето врз основа на вовлекување наместо точка-запирка го прават Python прилично едноставен, но моќен во неговите способности.
За жал, Python е исто така најбавниот јазик за општа намена таму, и покрај тоа колку софтвер е напишан во него. Бројот на достапни библиотеки на Python е околу четири пати повеќе од сличните јазици и покрива огромна количина на домени, но никој не би рекол дека Python е брз ниту има перформанси надвор од ниши како NumPy.
Ајде да видиме како нашата верзија Flask се споредува со нашите претходни рамки.
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 е всушност побрза од нашата чиста PHP верзија!
Ако сте изненадени од ова, треба да сфатите дека нашата апликација Flask ја прави целата своја иницијализација и конфигурација кога ќе го стартуваме серверот Gunicorn, додека PHP повторно ја извршува скриптата секој пат кога доаѓа ново барање. е еквивалентно на Flask да биде младиот, желен таксист кој веќе ја стартуваше колата и чека покрај патот, додека PHP е стариот возач кој останува во својата куќа чекајќи повик да влезе и дури потоа вози да те земам. Да се биде човек од старата школа и доаѓа од деновите кога PHP беше прекрасна промена на обичните HTML и SHTML-датотеки, малку е тажно да се сфати колку време поминало, но разликите во дизајнот навистина го отежнуваат PHP се натпреваруваат со серверите на Python, Java и Node.js кои само остануваат во меморијата и се справуваат со барањата со пргав леснотија на жонглер.
Flask можеби е нашата најбрза рамка досега, но всушност е прилично стар софтвер. Заедницата на Python се префрли на поновите асихрони ASGI сервери пред неколку години, и се разбира, јас самиот се префрлив заедно со нив.
Најновата верзија на Pafera Framework, PaferaPyAsync , се базира на Starlette. Иако постои ASGI верзија на Flask наречена Quart, разликите во перформансите помеѓу Quart и Starlette беа доволни за да го пребазирам мојот код на Starlette наместо тоа.
Асихроното програмирање може да биде застрашувачко за многу луѓе, но всушност не е тежок концепт благодарение на момците од Node.js што го популаризираа концептот пред повеќе од една деценија.
Порано се боревме со истовременост со повеќенишки, мултипроцесирање, дистрибуирано пресметување, синџир на ветувања и сите оние забавни времиња што прерано стареа и исушија многу ветерани програмери. Сега, само пишуваме async
пред нашите функции и await
пред кој било код за кој може да биде потребно извесно време да се изврши. Навистина е пообемно од обичниот код, но многу помалку досаден за користење отколку да се занимавате со примитивите за синхронизација, пренесување пораки и решавање на ветувања.
Нашата датотека Starlette изгледа вака:
#!/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",
)
Како што можете да видите, тој е прилично копиран и залепен од нашата скрипта Flask со само неколку промени во насочувањето и async/await
клучни зборови.
Колку подобрување навистина може да ни даде копирањето и залепениот код?
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
Имаме нов шампион, дами и господа! Нашиот претходен врв беше нашата чиста PHP верзија со 704 барања во секунда, која потоа беше надмината од нашата верзија на Flask со 1080 барања во секунда. Нашата скрипта Starlette ги уништува сите претходни конкуренти со 4562 барања во секунда, што значи 6x подобрување во однос на чистиот PHP и 4x подобрување во однос на Flask.
Ако сè уште не сте го промениле вашиот WSGI Python код во ASGI, сега можеби е добро време да започнете.
Досега покривавме само PHP и Python рамки. Сепак, голем дел од светот всушност користи Java, DotNet, Node.js, Ruby on Rails и други такви технологии за нивните веб-локации. Ова во никој случај не е сеопфатен преглед на сите светски екосистеми и биоми, па за да избегнеме да го правиме програмскиот еквивалент на органската хемија, ќе ги избереме само рамките што најлесно се пишуваат код за.. од кои Java дефинитивно не е.
Освен ако не сте се криеле под вашата копија на K&R C или Knuth's Уметноста на компјутерското програмирање во последните петнаесет години, веројатно сте слушнале за Node.js. Оние од нас кои постојат од почетокот на JavaScript се или неверојатно исплашени, изненадени или и двајцата од состојбата на модерниот JavaScript, но не може да се негира дека JavaScript стана сила со која треба да се смета и на серверите. како прелистувачи. На крајот на краиштата, сега имаме дури и природни 64 битни цели броеви на јазикот! Тоа е далеку подобро од сè што е складирано во 64-битни плови!
ExpressJS е веројатно најлесниот Node.js сервер за користење, така што ќе направиме брза и валкана апликација Node.js/ExpressJS за да го опслужиме нашиот бројач.
/**********************************************************************
* 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}`));
Овој код всушност беше полесен за пишување од верзиите на Python, иако мајчин JavaScript станува прилично незгоден кога апликациите стануваат поголеми и сите обиди да се поправи ова, како што е TypeScript, брзо стануваат пообемни од Python.
Ајде да видиме како функционира ова!
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
Можеби сте слушнале антички (антички според стандардите на Интернет...) народни приказни за Node.js' брзина, а тие приказни се главно вистинити благодарение на спектакуларната работа што Google ја направи со V8 JavaScript моторот. Меѓутоа, во овој случај, иако нашата брза апликација ја надминува скриптата Flask, нејзината природа со единечна нишка е поразена од четирите асинхронизирани процеси со кои управува витезот Старлета кој вели „Ni!“.
Ајде да добиеме уште некоја помош!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Во ред! Сега е дури четири на четири битка! Ајде да бидеме репер!
╰─➤ 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
Сè уште не е сосема на ниво на Starlette, но не е лошо за брзо хакирање на JavaScript од пет минути. Од моето сопствено тестирање, оваа скрипта всушност се задржува малку на ниво на интерфејс на базата на податоци бидејќи node-postgres не е ни приближно ефикасен како psycopg за Python. Префрлувањето на sqlite како двигател на базата на податоци дава над 3000 барања во секунда за истиот код ExpressJS.
Главната работа што треба да се забележи е дека и покрај бавната брзина на извршување на Python, ASGI рамки всушност можат да бидат конкурентни со решенијата Node.js за одредени оптоварувања.
Така, сега се доближуваме до врвот на планината, а под планина мислам на највисоките резултати забележани од глувци и мажи подеднакво.
Ако ги погледнете повеќето од рамковните репери достапни на веб, ќе забележите дека има два јазика кои имаат тенденција да доминираат на врвот: C++ и Rust. Работев со C++ од 90-тите, па дури и имав своја Win32 C++ рамка уште пред да биде нешто MFC/ATL, така што имам многу искуство со јазикот. Не е многу забавно да се работи со нешто кога веќе го знаете, па наместо тоа ќе направиме верзија на Rust. ;)
Rust е релативно нов што се однесува до програмските јазици, но стана предмет на љубопитност за мене кога Линус Торвалдс објави дека ќе го прифати Rust како програмски јазик на Linux кернелот. За нас постарите програмери, тоа е отприлика исто како да се каже дека овој нов хипик од Њу Ејџ ќе биде нов амандман на Уставот на САД.
Сега, кога сте искусен програмер, имате тенденција да не скокате толку брзо како помладите луѓе, или во спротивно може да се изгорите од брзите промени на јазикот или библиотеките. (Секој што ја користел првата верзија на AngularJS ќе знае за што зборувам.) Rust е сè уште малку во таа фаза на експериментален развој и ми е смешно што толку многу примери на код на веб не ни компајлирај повеќе со тековните верзии на пакети.
Сепак, перформансите прикажани од апликациите на Rust не можат да се негираат. Ако никогаш не сте пробале рипгреп или fd-најди надвор на големите дрвја на изворниот код, дефинитивно треба да им дадете спин. Тие се достапни дури и за повеќето дистрибуции на Linux едноставно од менаџерот на пакети. Разменувате зборливост за изведба со Rust... a многу на говорност за а многу на изведбата.
Целосниот код за Rust е малку голем, па ние само ќе ги погледнеме релевантните управувачи овде:
// =====================================================================
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
)))
}
Ова е многу покомплицирано од верзиите 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
И многу повеќе перформанси!
Нашиот Rust сервер кој користи Actix/deadpool_postgres лесно го победи нашиот претходен шампион Starlette за +125%, ExpressJS за +362% и чистиот PHP за +1366%. (Ќе ја оставам делтата на изведбата со верзијата на Ларавел како вежба за читателот.)
Открив дека учењето на јазикот Rust сам по себе е потешко од другите јазици, бидејќи има многу повеќе грешки од сè што сум видел надвор од 6502 Assembly, но ако вашиот Rust сервер може да заземе 14 пати повеќе од корисници како ваш PHP сервер, тогаш можеби има нешто да се добие со префрлување технологии на крајот на краиштата. Тоа е причината зошто следната верзија на Pafera Framework ќе се базира на Rust. Кривата на учење е многу повисока од јазиците за скриптирање, но изведбата ќе вреди. Ако не можете да одвоите време за да го научите Rust, тогаш засновањето на технолошкиот куп на Starlette или Node.js не е ниту лоша одлука.
Во последните дваесет години, преминавме од евтини статични локации за хостирање до споделени хостинг со стекови LAMP до изнајмување VPS на AWS, Azure и други облак услуги. Во денешно време, многу компании се задоволни со донесување одлуки за дизајн врз основа на кој и да е достапно или најевтино, бидејќи појавата на практични облак услуги го олесни фрлањето повеќе хардвер на бавните сервери и апликации. Ова им даде големи краткорочни добивки по цена на долгорочен технички долг.
Пред 70 години имаше голема вселенска трка меѓу Советскиот Сојуз и САД. Советите ги освоија повеќето од раните пресвртници. Тие го имаа првиот сателит во Спутник, првото куче во вселената во Лајка, првото вселенско летало на месечината во Луна 2, првиот маж и жена во вселената во Јуриј Гагарин и Валентина Терешкова и така натаму...
Но, тие полека акумулираа технички долг.
Иако Советите беа први за секое од овие достигнувања, нивните инженерски процеси и цели ги натераа да се фокусираат на краткорочни предизвици наместо на долгорочна изводливост. Победуваа секој пат кога скокаа, но стануваа се поуморни и побавни додека нивните противници продолжија да чекорат кон финишот.
Откако Нил Армстронг ги направи своите историски чекори на Месечината на телевизија во живо, Американците го презедоа водството, а потоа останаа таму додека советската програма пропадна. Ова не се разликува од компаниите денес кои се фокусираа на следната голема работа, на следната голема исплата или на следната голема технологија, додека не успеаја да развијат соодветни навики и стратегии за долги патеки.
Да се биде прв на пазарот не значи дека ќе станете доминантен играч на тој пазар. Алтернативно, одвојувањето време да ги правите работите правилно не гарантира успех, но секако ги зголемува вашите шанси за долгорочни достигнувања. Ако вие сте водечка технологија за вашата компанија, изберете ја вистинската насока и алатки за вашиот обем на работа. Не дозволувајте популарноста да ги замени перформансите и ефикасноста.
Сакате да преземете датотека 7z што ги содржи скриптите Rust, ExpressJS, Flask, Starlette и Pure PHP?
За авторот |
|
![]() |
Џим програмира откако доби IBM PS/2 назад во текот на 90-тите. До денес, тој сè уште претпочита рачно да пишува HTML и SQL и се фокусира на ефикасноста и исправноста во својата работа. |