پس از یکی از جدیدترین مصاحبه های شغلی ام، با تعجب متوجه شدم که شرکتی که برای آن درخواست دادم هنوز از لاراول استفاده می کند، یک چارچوب PHP که حدود یک دهه پیش آن را امتحان کردم. برای آن زمان مناسب بود، اما اگر ثابتی در فناوری و مد وجود داشته باشد، تغییر مستمر و ظهور مجدد سبک ها و مفاهیم است. اگر شما یک برنامه نویس جاوا اسکریپت هستید، احتمالاً با این جوک قدیمی آشنا هستید.
برنامه نویس 1: "من این چارچوب جدید جاوا اسکریپت را دوست ندارم!"
برنامه نویس 2: "نیازی به نگرانی نیست. فقط شش ماه صبر کنید تا یکی دیگر جایگزین شود!"
از روی کنجکاوی، تصمیم گرفتم ببینم وقتی قدیم و جدید را امتحان می کنیم دقیقاً چه اتفاقی می افتد. البته، وب مملو از معیارها و ادعاهایی است که احتمالاً محبوب ترین آنها است معیارهای چارچوب وب TechEmpower در اینجا . با این حال، ما امروز هیچ کاری به پیچیدگی آنها انجام نمی دهیم. ما همه چیز را زیبا و ساده نگه می داریم تا این مقاله تبدیل به جنگ و صلح , و اینکه تا زمانی که مطالعه را تمام کنید، شانس کمی برای بیدار ماندن خواهید داشت. اخطارهای معمول اعمال می شود: این ممکن است روی دستگاه شما یکسان عمل نکند، نسخه های مختلف نرم افزار می توانند بر عملکرد تأثیر بگذارند، و گربه شرودینگر در واقع به یک گربه زامبی تبدیل شد که نیمه زنده و نیمه مرده دقیقاً در همان زمان بود.
همانطور که در اینجا نشان داده شده است، برای این تست، از لپ تاپم مجهز به i5 ضعیفی که لینوکس Manjaro را اجرا می کند، استفاده خواهم کرد.
╰─➤ 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 اجازه دهیم آن را به سرعت از پنجره برای کاربر پرتاب کند، در این صورت میتوانیم به 32000 کاربر در ثانیه خدمات رسانی کنیم و عملکرد را 50 برابر افزایش دهیم.
╰─➤ wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.03ms 511.95us 6.87ms 68.10%
Req/Sec 8.20k 1.15k 28.55k 97.26%
327353 requests in 10.10s, 2.36GB read
Requests/sec: 32410.83
Transfer/sec: 238.99MB
Static index.en.html بخشی است که به همه می رسد و فقط قسمت هایی که بر اساس کاربر متفاوت هستند در sessionvars.js ارسال می شوند. این نه تنها بار پایگاه داده را کاهش می دهد و تجربه بهتری را برای کاربران ما ایجاد می کند، بلکه احتمالات کوانتومی را نیز کاهش می دهد که سرور ما به طور خود به خود در هنگام حمله کلینگون ها در یک شکست هسته تار تبخیر شود.
کد بازگشتی برای هر فریم ورک یک شرط ساده دارد: به کاربر نشان دهید چند بار صفحه را با گفتن "شمارش x است" بازخوانی کرده است. برای ساده نگه داشتن همه چیز، فعلاً از صفهای Redis، مؤلفههای Kubernetes یا AWS Lambdas خودداری میکنیم.
داده های جلسه هر کاربر در پایگاه داده PostgreSQL ذخیره می شود.
و این جدول پایگاه داده قبل از هر آزمون کوتاه می شود.
شعار پافرا ساده و در عین حال موثر است... به هر حال خارج از تاریک ترین جدول زمانی...
خوب، پس حالا بالاخره می توانیم دست هایمان را کثیف کنیم. ما از راه اندازی لاراول صرف نظر می کنیم زیرا این فقط یک گروه آهنگساز و صنعتگر است دستورات
ابتدا تنظیمات پایگاه داده خود را در فایل .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 را انجام می دهیم.
اگر فکر می کنید که مشکلی در این تست وجود دارد، باید با نویسنده چارچوب PHP Lucinda صحبت کنید. در نتایج آزمایش خود، او دارد لوسیندا لاراول را شکست داد 36 برابر برای درخواست های HTML و 90 برابر برای درخواست های JSON.
بعد از تست روی دستگاه خودم با آپاچی و نگینکس، دلیلی برای شک ندارم. لاراول واقعا عادلانه است که کند! پی اچ پی به خودی خود آنقدر بد نیست، اما زمانی که تمام پردازش های اضافی را که لاراول به هر درخواست اضافه می کند اضافه کنید، پیشنهاد لاراول به عنوان یک انتخاب در سال 2023 برای من بسیار دشوار است.
حساب های PHP/Wordpress برای حدود 40 درصد از تمام وب سایت های موجود در وب ، آن را تا حد زیادی به غالب ترین چارچوب تبدیل می کند. با این حال، شخصاً متوجه می شوم که محبوبیت لزوماً به کیفیت تبدیل نمی شود تا اینکه احساس می کنم میل ناگهانی غیرقابل کنترلی برای آن غذای لذیذ فوق العاده از محبوب ترین رستوران دنیا ... مک دونالد. از آنجایی که قبلاً کد PHP خالص را آزمایش کردهایم، خود وردپرس را آزمایش نمیکنیم، زیرا هر چیزی که شامل وردپرس باشد بدون شک کمتر از ۷۰۰ درخواست در ثانیه است که با PHP خالص مشاهده کردیم.
جنگو یکی دیگر از فریمورک های محبوبی است که مدت هاست وجود داشته است. اگر در گذشته از آن استفاده کردهاید، احتمالاً رابط مدیریت پایگاه داده تماشایی آن را با علاقه به یاد میآورید و همچنین پیکربندی همه چیز را به همان صورتی که میخواهید آزاردهنده بود. بیایید ببینیم جنگو در سال 2023 چقدر خوب کار می کند، به خصوص با رابط کاربری جدید ASGI که در نسخه 4.0 اضافه کرده است.
راهاندازی جنگو به طرز قابل ملاحظهای شبیه راهاندازی لاراول است، زیرا هر دو از دورانی بودند که معماریهای 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 برابر نسخه لاراول است. جنگو در برابر لاراول به نظر می رسد که اصلا رقابتی نیست.
جدا از چهارچوبهای بزرگتر همه چیز از جمله سینک آشپزخانه، چارچوبهای کوچکتری نیز وجود دارند که فقط برخی از تنظیمات اولیه را انجام میدهند و به شما اجازه میدهند بقیه موارد را مدیریت کنید. یکی از بهترین موارد برای استفاده فلاسک و همتای 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 خالص است. من متوجه شدم که از بین تمام زبانهایی که استفاده کردهام، پایتون احتمالاً رساترین زبان از نظر فشار دادن کلید تایپ شده است. فقدان پرانتز و پرانتز، درک فهرست و دستور، و مسدود کردن بر اساس تورفتگی به جای نیم ویرگول، پایتون را نسبتاً ساده و در عین حال از نظر قابلیتهای قدرتمند میسازد.
متأسفانه، پایتون نیز با وجود نرم افزارهای زیادی که در آن نوشته شده، کندترین زبان هدف عمومی موجود است. تعداد کتابخانههای پایتون در دسترس حدود چهار برابر بیشتر از زبانهای مشابه است و تعداد زیادی از دامنهها را پوشش میدهد، با این حال هیچکس نمیگوید پایتون سریع است و در خارج از جایگاههایی مانند 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 ما تمام تنظیمات اولیه و پیکربندی خود را هنگام راه اندازی سرور گانیکورن انجام می دهد، در حالی که PHP هر بار که درخواست جدیدی وارد می شود، اسکریپت را دوباره اجرا می کند. معادل فلاسک است که راننده تاکسی جوان و مشتاقی است که قبلاً ماشین را روشن کرده و در کنار جاده منتظر است، در حالی که PHP راننده قدیمی است که در خانه خود می ماند و منتظر تماس تلفنی می ماند و تنها پس از آن رانندگی می کند. برای بلند کردن شما از آنجایی که یک دانش آموز قدیمی هستید و از روزهایی که PHP یک تغییر فوق العاده برای فایل های ساده HTML و SHTML بود، کمی غم انگیز است که متوجه شوید چقدر زمان گذشته است، اما تفاوت های طراحی واقعاً کار را برای PHP سخت می کند. با سرورهای پایتون، جاوا و Node.js رقابت کنید که فقط در حافظه باقی می مانند و درخواست ها را به سادگی یک شعبده باز انجام می دهند.
Flask ممکن است سریعترین فریمورک ما تا کنون باشد، اما در واقع نرم افزار بسیار قدیمی است. چند سال پیش جامعه پایتون به سرورهای ناهمزمان 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",
)
همانطور که می بینید، تقریباً با چند تغییر مسیریابی و 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 درخواست در ثانیه خرد می کند، به این معنی که 6 برابر نسبت به PHP خالص و 4 برابر بهبود نسبت به Flask.
اگر هنوز کد WSGI Python خود را به ASGI تغییر نداده اید، ممکن است اکنون زمان خوبی برای شروع باشد.
تا کنون، ما فقط چارچوب های PHP و Python را پوشش داده ایم. با این حال، بخش بزرگی از جهان در واقع از جاوا، DotNet، Node.js، Ruby on Rails و سایر فناوریهای مشابه برای وبسایتهای خود استفاده میکنند. این به هیچ وجه یک نمای کلی از تمام اکوسیستم ها و زیست بوم های جهان نیست، بنابراین برای جلوگیری از انجام برنامه نویسی معادل شیمی آلی، ما فقط چارچوب هایی را انتخاب می کنیم که راحت ترین کد را برای آنها تایپ کنید. که جاوا قطعاً از آن نیست.
مگر اینکه زیر کپی K&R C یا Knuth پنهان شده باشید هنر برنامه نویسی کامپیوتر در پانزده سال گذشته، احتمالاً نام Node.js را شنیده اید. کسانی از ما که از ابتدای جاوا اسکریپت در اطراف بودهاند، یا به طرز باورنکردنی ترسیدهاند، شگفت زده شدهاند، یا هر دو از وضعیت جاوا اسکریپت مدرن میترسند، اما نمیتوان انکار کرد که جاوا اسکریپت به نیرویی برای سرورها تبدیل شده است. به عنوان مرورگر پس از همه، ما حتی اعداد صحیح 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}`));
نوشتن این کد در واقع آسانتر از نسخههای پایتون بود، اگرچه جاوا اسکریپت بومی زمانی که برنامهها بزرگتر میشوند بسیار سخت میشود و تمام تلاشها برای تصحیح آن مانند 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 شنیده باشید' سرعت، و این داستان ها عمدتاً به لطف کار خیره کننده ای که گوگل با موتور جاوا اسکریپت V8 انجام داده است، واقعی هستند. اگرچه در این مورد، اگرچه برنامه سریع ما بهتر از اسکریپت Flask عمل میکند، اما ماهیت تک رشتهای آن توسط چهار فرآیند همگامسازی که توسط شوالیه Starlette به کار میرود که میگوید «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 نیست، اما برای یک هک سریع جاوا اسکریپت پنج دقیقه ای بد نیست. از آزمایش خودم، این اسکریپت در واقع کمی در سطح رابط پایگاه داده نگه داشته شده است، زیرا node-postgres به اندازه psycopg برای پایتون کارآمد نیست. تغییر به sqlite به عنوان درایور پایگاه داده بیش از 3000 درخواست در ثانیه برای همان کد ExpressJS ایجاد می کند.
نکته اصلی این است که علیرغم سرعت اجرای پایین پایتون، فریمورک های ASGI در واقع می توانند با راه حل های Node.js برای بارهای کاری خاص رقابت کنند.
بنابراین اکنون، ما به بالای کوه نزدیکتر میشویم، و منظور من از کوه، بالاترین امتیازات معیار ثبت شده توسط موشها و مردان است.
اگر به اکثر معیارهای چارچوب موجود در وب نگاه کنید، متوجه خواهید شد که دو زبان وجود دارند که تمایل دارند در بالا تسلط داشته باشند: C++ و Rust. من از دهه 90 با C++ کار میکردم، و حتی قبل از اینکه MFC/ATL یک چیز بود، فریمورک Win32 C++ خودم را داشتم، بنابراین تجربه زیادی با این زبان دارم. کار کردن با چیزی در حالی که قبلاً آن را میدانید چندان سرگرمکننده نیست، بنابراین ما به جای آن یک نسخه Rust را انجام میدهیم. ;)
Rust از نظر زبانهای برنامهنویسی نسبتاً جدید است، اما وقتی لینوس توروالدز اعلام کرد که Rust را به عنوان یک زبان برنامهنویسی هسته لینوکس میپذیرد، برای من یک موضوع کنجکاوی شد. برای ما برنامهنویسهای قدیمیتر، این تقریباً شبیه به این است که بگوییم این هیپیهای هیپی عصر جدید، اصلاحیه جدیدی برای قانون اساسی ایالات متحده خواهد بود.
اکنون، زمانی که شما یک برنامه نویس با تجربه هستید، تمایل دارید که به سرعت افراد جوان تر از آن استفاده نکنید، در غیر این صورت ممکن است با تغییرات سریع در زبان یا کتابخانه ها دچار سوختگی شوید. (هرکسی که از اولین نسخه AngularJS استفاده کرده باشد میداند که من در مورد چه چیزی صحبت میکنم.) Rust هنوز تا حدودی در آن مرحله توسعه آزمایشی است، و به نظر من خندهدار است که بسیاری از نمونههای کد در وب حتی نمیشوند. دیگر با نسخه های فعلی بسته ها کامپایل کنید.
با این حال، عملکرد نشان داده شده توسط برنامه های Rust را نمی توان انکار کرد. اگر هرگز امتحان نکرده اید ریپگرپ یا fd-find در درختان کد منبع بزرگ، شما قطعا باید به آنها چرخش دهید. آنها حتی برای اکثر توزیع های لینوکس به سادگی از طریق مدیر بسته در دسترس هستند. شما پرحرفی را با اجرا با 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 دانلود کنید؟
درباره نویسنده |
|
![]() |
جیم از زمانی که در دهه 90 یک IBM PS/2 دریافت کرد، به برنامه نویسی پرداخت. تا به امروز، او همچنان نوشتن HTML و SQL را با دست ترجیح می دهد و روی کارایی و درستی کار خود تمرکز می کند. |