Iva, Virginia, Hemm *Hemm* a Santa Klaws Differenza Bejn Oqfsa tal-Web fl-2023

Vjaġġ ta' programmatur wieħed ta' sfida biex isib kodiċi ta' server tal-web b'rendiment mgħaġġel qabel ma jċedi għall-pressjoni tas-suq u d-dejn tekniku
2023-03-24 11:52:06
👁️ 782
💬 0

Kontenut

  1. Introduzzjoni
  2. It-Test
  3. PHP/Laravel
  4. PHP pur
  5. Nirrevedu lil Laravel
  6. Django
  7. Flixkun
  8. Starlette
  9. Node.js/ExpressJS
  10. Sadid/Actix
  11. Dejn Tekniku
  12. Riżorsi

Introduzzjoni

Wara waħda mill-intervisti tax-xogħol l-aktar riċenti tiegħi, kont sorpriż meta rrealizzajt li l-kumpanija li applikajt għaliha kienet għadha tuża Laravel, qafas PHP li ppruvajt madwar għaxar snin ilu. Kien deċenti għal dak iż-żmien, imma jekk hemm kostanti waħda fit-teknoloġija u l-moda bl-istess mod, hija bidla kontinwa u wiċċ mill-ġdid ta 'stili u kunċetti. Jekk inti programmatur JavaScript, inti probabilment familjari ma 'din iċ-ċajta antika

Programmatur 1: "Ma nħobbx dan il-qafas JavaScript ġdid!"

Programmatur 2: "M'hemmx għalfejn tinkwieta. Just stenna sitt xhur u se jkun hemm ieħor biex tissostitwiha!"

B’kurżità, iddeċidejt li nara eżattament x’jiġri meta npoġġu l-qodma u l-ġdida għat-test. Naturalment, il-web hija mimlija b'punti ta 'referenza u talbiet, li l-aktar popolari huwa probabbilment l- TechEmpower Web Framework Benchmarks hawn . Iżda aħna'mhux se nagħmlu xejn daqshekk ikkumplikat daqshom illum. Aħna nżommu l-affarijiet sbieħ u sempliċi t-tnejn biex dan l-artiklu ma jinbidilx Gwerra u Paċi , u li jkollok ċans żgħir li tibqa' mqajjem saż-żmien li tkun lest taqra. Japplikaw it-twissijiet tas-soltu: dan jista 'ma jaħdimx l-istess fuq il-magna tiegħek, verżjonijiet ta' softwer differenti jistgħu jaffettwaw il-prestazzjoni, u l-qattus ta 'Schrödinger's fil-fatt sar qattus zombie li kien nofs ħaj u nofs mejjet fl-istess ħin eżatt.

It-Test

Ambjent tal-Ittestjar

Għal dan it-test, se nkun qed nuża l-laptop tiegħi armat b'i5 puny li jħaddem Manjaro Linux kif muri hawn.

╰─➤  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

Il-Kompitu fil-idejn

Il-kodiċi tagħna se jkollu tliet kompiti sempliċi għal kull talba:

  1. Aqra l-ID tas-sessjoni tal-utent attwali minn cookie
  2. Tagħbija informazzjoni addizzjonali minn database
  3. Irritorna dik l-informazzjoni lill-utent

X'tip ta 'test idjotiku huwa dak, tista' tistaqsi? Ukoll, jekk tħares lejn it-talbiet tan-netwerk għal din il-paġna, tinnota wieħed imsejjaħ sessionvars.js li jagħmel eżattament l-istess ħaġa.

Il-kontenut ta' sessionvars.js

Tara, paġni tal-web moderni huma kreaturi kkumplikati, u waħda mill-kompiti l-aktar komuni hija l-caching ta 'paġni kumplessi biex tiġi evitata tagħbija żejda fuq is-server tad-database.

Jekk nerġgħu nirrendu paġna kumplessa kull darba li utent jitlobha, allura nistgħu naqdu biss madwar 600 utent kull sekonda.

╰─➤  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

Imma jekk nilqgħu din il-paġna bħala fajl HTML statiku u nħallu lil Nginx jitfgħuha malajr mit-tieqa lill-utent, allura nistgħu naqdu 32,000 utent kull sekonda, u nżidu l-prestazzjoni b'fattur ta '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

L-index.en.html statiku huwa l-parti li tmur lil kulħadd, u l-partijiet biss li jvarjaw skont l-utent jintbagħtu f'sessionvars.js. Dan mhux biss inaqqas it-tagħbija tad-database u joħloq esperjenza aħjar għall-utenti tagħna, iżda jnaqqas ukoll il-probabbiltajiet quantum li s-server tagħna jifvaporizza spontanjament fi ksur tal-qalba tal-medd meta l-attakk tal-Klingons.

Rekwiżiti tal-Kodiċi

Il-kodiċi mibgħuta lura għal kull qafas se jkollu rekwiżit wieħed sempliċi: uri lill-utent kemm-il darba'aġġorna l-paġna billi jgħid "Għadd huwa x". Biex inżommu l-affarijiet sempliċi, aħna se nibqgħu 'l bogħod mill-kjuwijiet Redis, komponenti Kubernetes, jew AWS Lambdas għalissa.

Li turi kemm-il darba żort il-paġna

Id-dejta tas-sessjoni ta' kull utent se tiġi ssejvjata f'database PostgreSQL.

It-tabella tas-sessjonijiet tal-utenti

U din it-tabella tad-database se tkun maqtugħa qabel kull test.

It-tabella wara li tkun maqtugħa

Sempliċi iżda effettiv huwa l-motto ta' Pafera... barra mill-iktar skeda ta' żmien mudlama xorta waħda...

Ir-Riżultati tat-Test Attwali

PHP/Laravel

Tajjeb, allura issa nistgħu fl-aħħar nibdew inħammġu jdejna. Aħna se naqbżu s-setup għal Laravel peress li huwa biss mazz ta 'kompożitur u artiġjan jikkmanda.

L-ewwel, aħna'se nissettjaw is-settings tad-database tagħna fil-fajl .env

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest

Imbagħad se nissettjaw rotta waħda ta' riżerva li tibgħat kull talba lill-kontrollur tagħna.

Route::fallback(SessionController::class);

U ssettja l-kontrollur biex juri l-għadd. Laravel, awtomatikament, jaħżen is-sessjonijiet fid-database. Jipprovdi wkoll il- session() funzjoni biex interface mad-dejta tas-sessjoni tagħna, għalhekk kulma ħadet kien ftit linji ta 'kodiċi biex tirrendi l-paġna tagħna.

class SessionController extends Controller
{
  public function __invoke(Request $request)
  {
    $count  = session('count', 0);

    $count  += 1;

    session(['count' => $count]);

    return 'Count is ' . $count;
  }
}

Wara li waqqaf php-fpm u Nginx, il-paġna tagħna tidher pjuttost tajba...

╰─➤  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

Mill-inqas sakemm fil-fatt naraw ir-riżultati tat-test...

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

Le, dak mhuwiex typo. Il-magna tat-test tagħna marret minn 600 talba kull sekonda li tirrendi paġna kumplessa... għal 21 talba kull sekonda li tirrendi "Count is 1".

Allura x'mar ħażin? Hija xi ħaġa ħażina fl-installazzjoni PHP tagħna? Nginx qed jonqos b'xi mod meta jiffaċċa php-fpm?

PHP pur

Ejja nerġgħu nagħmlu din il-paġna f'kodiċi PHP pur.

<?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'];

Issa użajna 98 linja ta 'kodiċi biex nagħmlu dak li għamlu erba' linji ta 'kodiċi (u mazz sħiħ ta' xogħol ta 'konfigurazzjoni) f'Laravel. (Naturalment, jekk għamilna tqandil xieraq tal-iżbalji u messaġġi li jiffaċċjaw l-utent, dan ikun madwar id-doppju tan-numru ta 'linji.) Forsi nistgħu nagħmluha għal 30 talba kull sekonda?

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

Whoa! Jidher li m'hemm xejn ħażin fl-installazzjoni PHP tagħna wara kollox. Il-verżjoni PHP pura qed tagħmel 700 talba kull sekonda.

Jekk m'hemm xejn ħażin mal-PHP, forsi kkonfigurajna ħażin lil Laravel?

Nirrevedu lil Laravel

Wara li togħrok il-web għal kwistjonijiet ta 'konfigurazzjoni u pariri dwar il-prestazzjoni, tnejn mit-tekniki l-aktar popolari kienu li jqiegħdu fil-cache l-konfigurazzjoni u d-data tar-rotta biex jevitaw li jipproċessawhom għal kull talba. Għalhekk, aħna se nieħdu l-parir tagħhom u nippruvaw dawn il-pariri.

╰─➤  php artisan config:cache

   INFO  Configuration cached successfully.  

╰─➤  php artisan route:cache

   INFO  Routes cached successfully.  

Kollox jidher tajjeb fuq il-linja tal-kmand. Ejja nerġgħu nagħmlu l-benchmark.

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.13s   543.50ms   1.98s    61.90%
    Req/Sec    25.45     13.39    50.00     55.77%
  289 requests in 10.04s, 242.15KB read
  Socket errors: connect 0, read 0, write 0, timeout 247
Requests/sec:     28.80
Transfer/sec:     24.13KB

Ukoll, issa żidna l-prestazzjoni minn 21.04 għal 28.80 talba kull sekonda, żieda drammatika ta 'kważi 37%! Dan ikun pjuttost impressjonanti għal kwalunkwe pakkett ta 'softwer... ħlief għall-fatt li aħna&#x27;għadna nagħmlu biss 1/24th tan-numru ta 'talbiet tal-verżjoni PHP pura.

Jekk qed taħseb li xi ħaġa għandha tkun ħażina f'dan it-test, għandek titkellem mal-awtur tal-qafas Lucinda PHP. Fir-riżultati tat-test tiegħu, huwa għandu Lucinda tegħleb lil Laravel b'36x għal talbiet HTML u 90x għal talbiet JSON.

Wara li ttestja fuq il-magna tiegħi stess kemm b'Apache kif ukoll b'Nginx, m'għandi l-ebda raġuni biex niddubitah. Laravel huwa verament ġust dak bil-mod! PHP waħdu mhuwiex daqshekk ħażin, iżda ladarba żżid l-ipproċessar żejjed kollu li Laravel iżid ma 'kull talba, allura nsibha diffiċli ħafna li nirrakkomanda lil Laravel bħala għażla fl-2023.

Django

PHP/Wordpress jammonta għal madwar 40% tal-websajts kollha fuq il-web , li jagħmilha bil-bosta l-aktar qafas dominanti. Iżda personalment, insib li l-popolarità mhux bilfors tissarraf fi kwalità aktar milli nsib lili nnifsi ħeġġa f'daqqa waħda bla kontroll għal dak l-ikel gourmet straordinarju minn l-aktar restorant popolari fid-dinja ... McDonald&#x27;s. Peress li diġà ttestjajna kodiċi PHP pur, aħna mhux se nittestjaw Wordpress innifsu, peress li kull ħaġa li tinvolvi Wordpress bla dubju tkun inqas mis-700 talba kull sekonda li osservajna b'PHP pur.

Django huwa qafas popolari ieħor li ilu jeżisti għal żmien twil. Jekk inti użajt fil-passat, inti probabilment qed tiftakar bil-qalb l-interface spettakolari ta 'amministrazzjoni tad-database tagħha flimkien ma' kemm kien tedjanti li kkonfigurat kollox eżatt kif ridt. Ejja naraw kemm Django jaħdem tajjeb fl-2023, speċjalment bl-interface ASGI l-ġdida li żiedet mill-verżjoni 4.0.

It-twaqqif ta 'Django huwa simili ħafna għat-twaqqif ta' Laravel, peress li t-tnejn kienu mill-età fejn l-arkitetturi MVC kienu stylish u korretti. Aħna se naqbżu l-konfigurazzjoni boring u mmorru direttament biex inwaqqfu l-veduta.

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}")

Erba 'linji ta' kodiċi huma l-istess bħall-verżjoni Laravel. Ejja naraw kif taħdem.

╰─➤  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

Mhux ħażin xejn għal 355 talba kull sekonda. Huwa biss nofs il-prestazzjoni tal-verżjoni PHP pura, iżda huwa wkoll 12x dak tal-verżjoni Laravel. Django vs Laravel jidher li m'hemm l-ebda kompetizzjoni.

Flixkun

Minbarra l-oqfsa akbar ta 'kollox-inkluż-il-sink tal-kċina, hemm ukoll oqfsa iżgħar li jagħmlu biss xi setup bażiku filwaqt li jħalluk timmaniġġja l-bqija. Waħda mill-aħjar li tuża hija Flask u l-kontroparti tiegħu tal-ASGI Quart. Tiegħi stess Qafas PaferaPy huwa mibni fuq il-Flask, għalhekk jiena naf sew kemm hu faċli li l-affarijiet isiru filwaqt li żżomm il-prestazzjoni.

#!/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}'

Kif tistgħu taraw, l-iskrittura tal-Flask hija iqsar mill-iskrittura PHP pura. Insib li mil-lingwi kollha li użajt, Python huwa probabbilment l-aktar lingwa espressiva f'termini ta 'keystrokes ittajpjati. Nuqqas ta' ċineg u parentesi, komprensjonijiet tal-lista u tad-ditta, u l-imblukkar ibbażat fuq l-indentazzjoni aktar milli fuq il-punti u virgola jagħmlu lil Python pjuttost sempliċi iżda b'saħħtu fil-kapaċitajiet tiegħu.

Sfortunatament, Python huwa wkoll l-aktar lingwa għal skopijiet ġenerali bil-mod hemmhekk, minkejja kemm inkiteb softwer fiha. In-numru ta 'libreriji Python disponibbli huwa madwar erba' darbiet aktar minn lingwi simili u jkopri ammont kbir ta 'dominji, iżda ħadd ma jgħid li Python huwa veloċi u lanqas performant barra minn niċeċ bħal NumPy.

Ejja naraw kif il-verżjoni tal-Flask tagħna tqabbel mal-oqfsa preċedenti tagħna.

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

L-iskript tal-Flask tagħna huwa fil-fatt aktar mgħaġġel mill-verżjoni PHP pura tagħna!

Jekk int sorpriż b'dan, għandek tirrealizza li l-app Flask tagħna tagħmel l-inizjalizzazzjoni u l-konfigurazzjoni kollha tagħha meta nibdew is-server tal-gunicorn, filwaqt li PHP jerġa' jesegwixxi l-iskript kull darba li tidħol talba ġdida. Huwa&#x27; ;huwa ekwivalenti għal Flask li hu x-xufier tat-taxi żagħżugħ u ħerqan li&#x27;;diġà beda l-karozza u qed jistenna ħdejn it-triq, filwaqt li PHP huwa s-sewwieq antik li joqgħod id-dar tiegħu jistenna li tidħol sejħa u mbagħad isuq biss. fuq biex jiġbdek. Li tkun raġel ta 'skola antika u ġej mill-jiem fejn PHP kien bidla mill-isbaħ għal fajls HTML u SHTML sempliċi, huwa daqsxejn imdejjaq li tirrealizza kemm għadda żmien, iżda d-differenzi fid-disinn verament jagħmluha diffiċli għall-PHP biex jikkompetu kontra servers Python, Java, u Node.js li jibqgħu biss fil-memorja u jimmaniġġjaw it-talba bil-faċilità ħafifa ta 'juggler.

Starlette

Flixkun jista 'jkun il-qafas l-aktar mgħaġġel tagħna s'issa, iżda fil-fatt huwa softwer pjuttost antik. Il-komunità Python qalbet għas-servers ASGI asinkroniċi l-aktar ġodda ftit ta 'snin lura, u ovvjament, jien stess qlibt magħhom.

L-aktar verżjoni ġdida tal-Qafas Pafera, PaferaPyAsync , hija bbażata fuq Starlette. Għalkemm hemm verżjoni ASGI ta 'Flask imsejħa Quart, id-differenzi fil-prestazzjoni bejn Quart u Starlette kienu biżżejjed għalija biex nibża l-kodiċi tiegħi fuq Starlette minflok.

L-ipprogrammar asinkroniku jista 'jkun tal-biża' għal ħafna nies, iżda fil-fatt mhuwiex kunċett diffiċli grazzi għall-guys Node.js popolarizzar il-kunċett aktar minn għaxar snin ilu.

Aħna konna niġġieldu l-konkorrenza ma 'multithreading, multiprocessing, kompjuters distribwiti, wegħda katina, u dawk il-ħinijiet kollha divertenti li età qabel iż-żmien u ssikkati ħafna programmaturi veteran. Issa, aħna biss ittajpja async quddiem il-funzjonijiet tagħna u await quddiem kwalunkwe kodiċi li jista' jieħu ftit biex jiġi eżegwit. Huwa tabilħaqq aktar verbose minn kodiċi regolari, iżda ħafna inqas tedjanti għall-użu milli jkollhom jittrattaw primitives tas-sinkronizzazzjoni, il-passaġġ tal-messaġġi, u r-riżoluzzjoni tal-wegħdiet.

Il-fajl Starlette tagħna jidher bħal dan:

#!/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",
)

Kif tistgħu taraw, huwa pjuttost ikkupjat u pasted mill-iskript tal-Flask tagħna biss bi ftit bidliet fir-rotot u l- async/await keywords.

Kemm titjib jista 'kopja u pasted kodiċi verament tagħtina?

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

Għandna champion ġdid, onorevoli u rġulija! L-għoli preċedenti tagħna kien il-verżjoni PHP pura tagħna b'704 talba kull sekonda, li mbagħad inqabeż mill-verżjoni tal-Flask tagħna b'1080 talba kull sekonda. L-iskrittura Starlette tagħna tfarrak lill-kontendenti preċedenti kollha b'4562 talba kull sekonda, li jfisser titjib 6x fuq PHP pur u titjib 4x fuq Flask.

Jekk għadek ma biddiltx il-kodiċi WSGI Python tiegħek għal ASGI, issa jista' jkun żmien tajjeb biex tibda.

Node.js/ExpressJS

S'issa, koprejna biss oqfsa PHP u Python. Madankollu, parti kbira tad-dinja fil-fatt tuża Java, DotNet, Node.js, Ruby on Rails, u teknoloġiji oħra bħal dawn għall-websajts tagħhom. Din bl-ebda mod mhi ħarsa ġenerali komprensiva tal-ekosistemi u l-bijomi kollha tad-dinja, għalhekk biex nevitaw li nagħmlu l-ekwivalenti tal-ipprogrammar tal-kimika organika, aħna se nagħżlu biss l-oqfsa li huma l-aktar faċli biex tittajpja kodiċi għalihom.. . li Java żgur mhux.

Sakemm ma kontx qed taħbi taħt il-kopja tiegħek tal-K&R C jew Knuth&#x27;s L-Arti tal-Programmazzjoni tal-Kompjuter għal dawn l-aħħar ħmistax-il sena, inti&#x27;probabbilment smajt b'Node.js. Dawk minna li ilhom madwar mill-bidu ta 'JavaScript jew huma oerhört mbeżżgħin, étonné, jew it-tnejn bl-istat tal-JavaScript modern, iżda m'hemm l-ebda rifjut li JavaScript sar forza li għandha titqies fuq is-servers ukoll bħala browsers. Wara kollox, għandna saħansitra interi indiġeni ta' 64 bit issa fil-lingwa! Dak huwa ferm aħjar minn dak kollu li jkun maħżun f'sufruni 64 bit bil-bosta!

ExpressJS huwa probabbilment l-eħfef server Node.js biex jintuża, għalhekk aħna&#x27;na nagħmlu app Node.js/ExpressJS malajr u maħmuġin biex iservu l-counter tagħna.

/**********************************************************************
 * 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}`));

Dan il-kodiċi kien fil-fatt aktar faċli biex tikteb mill-verżjonijiet Python, għalkemm JavaScript nattiv isir pjuttost diffiċli meta l-applikazzjonijiet isiru akbar, u t-tentattivi kollha biex jikkoreġu dan bħal TypeScript malajr isiru aktar verbose minn Python.

Ejja naraw kif dan iwettaq!

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

Jista' jkun li smajt rakkonti tal-qedem (antik skont l-istandards tal-Internet xorta...) dwar Node.js&#x27; veloċità, u dawk l-istejjer huma l-aktar veri grazzi għax-xogħol spettakolari li Google għamel bil-magna V8 JavaScript. F'dan il-każ għalkemm, għalkemm l-app ta 'malajr tagħna tisboq l-iskrittura tal-Flask, in-natura unika tagħha bil-kamin hija megħluba mill-erba' proċessi asinkroniċi mħaddma mill-Kavallier Starlette li jgħid &quot;Ni!&quot;.

Ejja nġibu aktar għajnuna!

╰─➤  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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Tajjeb! Issa huwa battalja erbgħa fuq erba! Ejja&#x27;s benchmark!

╰─➤  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

Għadu mhux pjuttost fil-livell ta 'Starlette, iżda mhux ħażin għal hack ta' JavaScript ta 'ħames minuti malajr. Mill-ittestjar tiegħi stess, dan l-iskript fil-fatt qed jinżamm ftit lura fil-livell tal-interfacing tad-database minħabba li node-postgres mhu qrib daqshekk effiċjenti daqs psycopg għal Python. Taqleb għal sqlite peress li s-sewwieq tad-database jagħti aktar minn 3000 talba kull sekonda għall-istess kodiċi ExpressJS.

Il-ħaġa ewlenija li għandek tinnota hija li minkejja l-veloċità ta 'eżekuzzjoni bil-mod ta' Python, l-oqfsa ASGI jistgħu fil-fatt ikunu kompetittivi b'soluzzjonijiet Node.js għal ċertu xogħol.

Sadid/Actix

Allura issa, qed noqorbu lejn il-quċċata tal-muntanji, u bil-muntanji, nifhem l-ogħla punteġġi ta 'referenza rreġistrati mill-ġrieden u l-irġiel bl-istess mod.

Jekk tħares lejn il-biċċa l-kbira tal-benchmarks tal-qafas disponibbli fuq il-web, tinnota li hemm żewġ lingwi li għandhom tendenza li jiddominaw il-quċċata: C++ u Rust. Jien ħdimt mas-C++ mis-snin 90, u anke kelli l-qafas tiegħi Win32 C++ lura qabel ma l-MFC/ATL kienet xi ħaġa, għalhekk għandi ħafna esperjenza bil-lingwa. Mhuwiex pjaċevoli li taħdem ma 'xi ħaġa meta diġà tafha, għalhekk aħna se nagħmlu verżjoni Rust minflok. ;)

Rust huwa relattivament ġdid f'dak li għandu x'jaqsam mal-lingwi tal-ipprogrammar, iżda sar oġġett ta' kurżità għalija meta Linus Torvalds ħabbar li kien se jaċċetta Rust bħala lingwa tal-programmazzjoni tal-kernel Linux. Għalina l-programmaturi anzjani, dak&#x27;s madwar l-istess bħal ngħidu li din il-ħaġa hippie ġdida fangled new age kienet se tkun emenda ġdida għall-Kostituzzjoni tal-Istati Uniti.

Issa, meta int programmatur b'esperjenza, għandek it-tendenza li ma tiżdiedx fuq il-bandwagon malajr daqs kemm jagħmlu n-nies iżgħar, jew inkella tista 'tinħaraq minn bidliet rapidi fil-lingwa jew fil-libreriji. (Kull min uża l-ewwel verżjoni ta 'AngularJS se jkun jaf x'qed nitkellem dwar.) Rust għadu kemmxejn f'dak l-istadju ta 'żvilupp sperimentali, u nsibha umoristiċi li dawk l-eżempji ta' kodiċi ħafna fuq il-web lanqas ma&#x27; jikkompila aktar mal-verżjonijiet kurrenti ta 'pakketti.

Madankollu, il-prestazzjoni murija mill-applikazzjonijiet Rust ma tistax tiġi miċħuda. Jekk qatt ma ppruvajt ripgrep jew fd-sib fuq siġar kbar tal-kodiċi tas-sors, għandek definittivament tagħtihom spin. Huma anke disponibbli għall-biċċa l-kbira tad-distribuzzjonijiet tal-Linux sempliċement mill-maniġer tal-pakketti. Inti qed tiskambja verbosity għall-prestazzjoni ma Rust... a lott ta' verbosity għal a lott tal-prestazzjoni.

Il-kodiċi komplut għal Rust huwa daqsxejn kbir, għalhekk aħna&#x27;na nagħtu ħarsa lejn il-handlers rilevanti hawn:

// =====================================================================
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
  )))
}

Dan huwa ħafna aktar ikkumplikat mill-verżjonijiet 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

U ħafna aktar performant!

Is-server Rust tagħna li juża Actix/deadpool_postgres jegħleb b'mod faċli ċ-champion preċedenti tagħna Starlette b'+125%, ExpressJS b'+362%, u PHP pur b'+1366%. (Jien inħalli d-delta tal-prestazzjoni bil-verżjoni Laravel bħala eżerċizzju għall-qarrej.)

I&#x27;sibt li t-tagħlim tal-lingwa Rust innifsu kien aktar diffiċli minn lingwi oħra peress li għandu ħafna aktar gotchas minn kull ħaġa li rajt barra minn 6502 Assembly, imma jekk is-server Rust tiegħek jista 'jieħu 14x in-numru ta' utenti bħala s-server PHP tiegħek, allura forsi hemm xi ħaġa x'jikseb bit-tibdil tat-teknoloġiji wara kollox. Huwa għalhekk li l-verżjoni li jmiss tal-Qafas Pafera se tkun ibbażata fuq Rust. Il-kurva tat-tagħlim hija ħafna ogħla mil-lingwi tal-kitba, iżda l-prestazzjoni tkun worth it. Jekk ma tistax tpoġġi l-ħin biex titgħallem Rust, allura li tibbaża l-munzell tat-teknoloġija tiegħek fuq Starlette jew Node.js lanqas hija deċiżjoni ħażina.

Dejn Tekniku

F'dawn l-aħħar għoxrin sena, morna minn siti ta 'hosting statiċi irħas għal hosting kondiviż ma' munzelli LAMP għal kiri ta 'VPSes għal AWS, Azure, u servizzi oħra tal-cloud. Illum il-ġurnata, ħafna kumpaniji huma sodisfatti li jieħdu deċiżjonijiet ta 'disinn ibbażati fuq min jistgħu jsibu li&#x27;s disponibbli jew irħas peress li l-miġja ta 'servizzi cloud konvenjenti għamluha faċli biex tarmi aktar ħardwer fuq servers u applikazzjonijiet bil-mod. Dan tahom qligħ kbir fuq żmien qasir għall-ispiża ta 'dejn tekniku fit-tul.

Twissija tal-Kirurgu Ġenerali tal-Kalifornja&#x27;: Dan mhux kelb spazjali reali.

70 sena ilu, kien hemm tellieqa spazjali kbira bejn l-Unjoni Sovjetika u l-Istati Uniti. Is-Sovjetiċi rebħu ħafna mill-istadji bikrin. Kellhom l-ewwel satellita fl-Sputnik, l-ewwel kelb fl-ispazju f’Laika, l-ewwel vettura spazjali tal-qamar f’Luna 2, l-ewwel raġel u mara fl-ispazju f’Yuri Gagarin u Valentina Tereshkova, eċċ...

Iżda bil-mod kienu qed jakkumulaw dejn tekniku.

Għalkemm is-Sovjetiċi kienu l-ewwel għal kull waħda minn dawn il-kisbiet, il-proċessi u l-għanijiet tal-inġinerija tagħhom kienu qed iwassluhom biex jiffokaw fuq sfidi għal żmien qasir aktar milli fattibilità fit-tul. Huma rebħu kull darba li qabżu, iżda kienu qed isiru aktar għajjien u aktar bil-mod filwaqt li l-avversarji tagħhom komplew jagħmlu passi konsistenti lejn il-linja finali.

Ladarba Neil Armstrong ħa l-passi storiċi tiegħu fuq il-qamar fuq it-televiżjoni diretta, l-Amerikani ħadu t-tmexxija, u mbagħad baqgħu hemm hekk kif il-programm Sovjetiku batut. Dan mhux differenti minn kumpaniji llum li ffukaw fuq il-ħaġa kbira li jmiss, il-ħlas kbir li jmiss, jew it-teknoloġija kbira li jmiss filwaqt li naqsu milli jiżviluppaw drawwiet u strateġiji xierqa għat-tul.

Li tkun l-ewwel fis-suq ma jfissirx li inti se ssir l-attur dominanti f'dak is-suq. Inkella, li tieħu l-ħin biex tagħmel l-affarijiet sewwa ma tiggarantixxix is-suċċess, iżda ċertament iżid iċ-ċansijiet tiegħek ta’ kisbiet fit-tul. Jekk int il-mexxej teknoloġiku għall-kumpanija tiegħek, agħżel id-direzzjoni u l-għodda t-tajba għall-ammont tax-xogħol tiegħek. Tħallix il-popolarità tissostitwixxi l-prestazzjoni u l-effiċjenza.

Riżorsi

Trid tniżżel fajl 7z li fih l-iskripts Rust, ExpressJS, Flask, Starlette, u Pure PHP?

Dwar l-Awtur

Jim ilu jipprogramma minn meta kiseb IBM PS/2 lura matul is-snin 90. Sal-lum, għadu jippreferi jikteb HTML u SQL bl-idejn, u jiffoka fuq l-effiċjenza u l-korrettezza fix-xogħol tiegħu.