تغییرات اخیر

در اینجا اطلاعیه‌ها، نسخه‌ها و تغییرات جدید لیارا فهرست می‌شوند.

چگونه عملکرد یک برنامه Flask را بهینه کنیم؟


۳ آذر ۱۴۰۴

Flask یک فریم‌ورک سبک و منعطف پایتون است که توسعه سریع برنامه‌های وب کوچک تا متوسط را امکان‌پذیر می‌کند. از وبلاگ‌های شخصی گرفته تا APIهای پیچیده، پلتفرم‌های SaaS و داشبوردهای داده‌محور، Flask کاربرد گسترده‌ای دارد. با افزایش ترافیک یا پیچیدگی برنامه، گلوگاه‌های عملکردی ممکن است تجربه کاربری را تحت‌تاثیر قرار دهند.

بنابراین، بهینه‌سازی عملکرد Flask برای ارائه پاسخ سریع و مقیاس‌پذیر مناسب، یک ضرورت اساسی است. در این مقاله، با تکنیک‌ها و بهترین روش‌هایی آشنا خواهید شد که به شما کمک می‌کند کارایی و سرعت برنامه Flask خود را به حداکثر برسانید.

در ادامه خواهید خواند:

  • پیش نیاز
  • راه‌اندازی محیط Flask
  • ایجاد یک برنامه Flask
  • استفاده از یک سرور WSGI آماده برای محیط تولید
  • فعال‌سازی کش برای کاهش بار
  • بهینه‌سازی کوئری‌های پایگاه داده
  • فعال‌سازی فشرده‌سازی Gzip
  • واگذار کردن وظایف سنگین به Celery
  • جمع بندی

راه‌اندازی محیط Flask

Ubuntu 24.04 به‌صورت پیش‌فرض Python 3 را ارائه می‌دهد. برای بررسی نصب Python 3، ترمینال را باز کرده و دستور زیر را اجرا کنید:

root@ubuntu:~# python3 --version
Python 3.12.3

اگر Python 3 قبلا روی سیستم شما نصب شده باشد، این دستور نسخه فعلی Python 3 را نمایش می‌دهد. در صورتی که نصب نشده باشد، می‌توانید با دستور زیر Python 3 را نصب کنید.

root@ubuntu:~# sudo apt install python3

سپس نیاز دارید pip، ابزار نصب بسته‌های پایتون را روی سیستم خود نصب کنید:

root@ubuntu:~# sudo apt install python3-pip

پس از نصب pip، حالا می‌توانیم Flask را نصب کنیم.

برای جلوگیری از تداخل بسته‌های دیگر روی سیستم، توصیه می‌شود Flask را در یک محیط مجازی نصب کنید. ابتدا یک محیط مجازی بسازید و فعال کنید:

root@ubuntu:~# python3 -m venv myprojectenv
root@ubuntu:~# source myprojectenv/bin/activate

بعد از فعال شدن محیط مجازی، Flask را نصب کنید:

root@ubuntu:~# pip install Flask

محیط مجازی به شما این اجازه را می‌دهد که بسته‌های پایتون پروژه را جدا از سیستم اصلی نصب و مدیریت کنید. این کار از ایجاد تداخل بین بسته‌های مختلف جلوگیری می‌کند و پروژه‌ شما قابل حمل و مدیریت آسان‌تر می‌شود.

معرفی هاست رایگان Flask
Flask

ایجاد یک برنامه Flask

گام بعدی، نوشتن کد پایتون برای برنامه Flask است. برای ایجاد یک اسکریپت جدید، به دایرکتوری مورد نظر خود بروید:

root@ubuntu:~# cd ~/path-to-your-script-directory

وقتی در دایرکتوری مورد نظر قرار گرفتید، یک فایل پایتون جدید با نام app.py بسازید و Flask را وارد کنید. سپس یک برنامه Flask را مقداردهی کرده و یک مسیر ساده ایجاد کنید:

root@ubuntu:~# nano app.py

این دستور یک ویرایشگر متن خالی باز می‌کند. شما می‌توانید منطق خود را بنویسید یا کد زیر را کپی کنید:

from flask import Flask, jsonify, request

app = Flask(__name__)

# Simulate a slow endpoint
@app.route('/slow')
def slow():
    import time
    time.sleep(2)  # to simulate a slow response
    return jsonify(message="This request was slow!")

# Simulate an intensive database operation
@app.route('/db')
def db_operation():
    # This is a dummy function to simulate a database query
    result = {"name": "User", "email": "[email protected]"}
    return jsonify(result)

# Simulate a static file being served
@app.route('/')
def index():
    return "<h1>Welcome to the Sample Flask App</h1>"

if __name__ == '__main__':
    app.run(debug=True)

حالا بیایید برنامه Flask را اجرا کنیم:

root@ubuntu:~# flask run

می‌توانید endpoints را با استفاده از دستورات زیر تست کنید:

تست مسیر / (خدمات محتوای استاتیک):

root@ubuntu:~# curl http://127.0.0.1:5000/

خروجی:

[secondary_lebel Output]
<h1>Welcome to the Sample Flask App</h1>%

تست مسیر /slow (شبیه‌سازی پاسخ کند):

root@ubuntu:~# time curl http://127.0.0.1:5000/db

برای بررسی این endpoint کند؛ از دستور time در لینوکس استفاده می‌کنیم. دستور time زمان اجرای یک دستور یا برنامه را اندازه‌گیری می‌کند و سه اطلاعات اصلی اراده می‌دهد:

  • بلادرنگ (Real time): زمان کل سپری شده از شروع تا پایان اجرای دستور.
  • زمان کاربر (User time): میزان زمانی که پردازنده در حالت user صرف کرده است.
  • زمان سیستم (System time): میزان زمانی که پردازنده در حالت kernel صرف کرده است.

این اطلاعات به ما کمک می‌کنند تا زمان واقعی اجرای endpoint کند را بسنجیم. خروجی ممکن است چیزی شبیه به این باشد:

Output{"message":"This request was slow!"}
curl http://127.0.0.1:5000/slow  0.00s user 0.01s system 0% cpu 2.023 total

این درخواست حدود 2 ثانیه طول می‌کشد تا پاسخ دهد، به دلیل فراخوانی time.sleep(2) که پاسخ کند را شبیه سازی می‌کند.

حالا مسیر /db را تست کنیم (شبیه‌سازی عملیات دیتابیس):

root@ubuntu:~# curl http://127.0.0.1:5000/db

خروجی:

Output{"email":"[email protected]","name":"User"}

با تست این endpoint با استفاده از curl، می‌توانید مطمئن شوید که برنامه Flask شما به درستی اجرا می‌شود و پاسخ‌ها مطابق انتظار هستند.

استفاده از curl و دستور time به شما این امکان را می‌دهد که نه‌تنها پاسخ API را ببینید، بلکه زمان واقعی پاسخ‌دهی هر endpoint را نیز بسنجید، که برای بهینه‌سازی عملکرد بسیار مهم است.

با سرویس آماده هوش مصنوعی لیارا، پروژه‌های AI خودت رو بدون دردسر اجرا و مدیریت کن.
✅ پشتیبانی GPU و CPU✅ مناسب مدل‌های متن‌باز✅ اجرای سریع و پایدار
خرید و راه‌اندازی سرویس هوش مصنوعی

استفاده از یک سرور WSGI آماده برای محیط تولید

سرور داخلی توسعه Flask برای محیط‌های طراحی نشده است. برای گردازش همزمان درخواست‌ها به‌صورت بهینه، بهتر است از یک سرور WSGI آماده برای محیط تولید مانند Gunicorn استفاده کنید.

نصب و راه‌اندازی Gunicorn

ابتدا Gunicorn را نصب کنید:

root@ubuntu:~# pip install gunicorn

سپس برنامه Flask را با Gunicorn و استفاده از Worker process 4 اجرا کنید:

root@ubuntu:~# gunicorn -w 4 -b 0.0.0.0:8000 app:app

خروحی اجرای دستور ممکن است مشابه خروجی زیر باشد:

Output % /Library/Python/3.9/bin/gunicorn -w 4 -b 0.0.0.0:8000 app:app
[2024-09-13 18:37:24 +0530] [99925] [INFO] Starting gunicorn 23.0.0
[2024-09-13 18:37:24 +0530] [99925] [INFO] Listening at: http://0.0.0.0:8000 (99925)
[2024-09-13 18:37:24 +0530] [99925] [INFO] Using worker: sync
[2024-09-13 18:37:24 +0530] [99926] [INFO] Booting worker with pid: 99926
[2024-09-13 18:37:25 +0530] [99927] [INFO] Booting worker with pid: 99927
[2024-09-13 18:37:25 +0530] [99928] [INFO] Booting worker with pid: 99928
[2024-09-13 18:37:25 +0530] [99929] [INFO] Booting worker with pid: 99929
[2024-09-13 18:37:37 +0530] [99925] [INFO] Handling signal: winch
^C[2024-09-13 18:38:51 +0530] [99925] [INFO] Handling signal: int
[2024-09-13 18:38:51 +0530] [99927] [INFO] Worker exiting (pid: 99927)
[2024-09-13 18:38:51 +0530] [99926] [INFO] Worker exiting (pid: 99926)
[2024-09-13 18:38:51 +0530] [99928] [INFO] Worker exiting (pid: 99928)
[2024-09-13 18:38:51 +0530] [99929] [INFO] Worker exiting (pid: 99929)
[2024-09-13 18:38:51 +0530] [99925] [INFO] Shutting down: Master

مزایای استفاده از Gunicorn:

  • پردازش همزمان درخواست‌ها: Gunicorn با استفاده از چندین Worker می‌تواند چندین درخواست را به‌صورت همزمان پردازش کند.
  • توازن بار (Load Balancing): Gunicorn درخواست‌های ورودی را بین Workerها تقسیم می‌کند تا منبع سرور بهینه استفاده شوند.
  • Workerهای غیرهمزمان (Asynchronous Workers): با استفاده از Workerهای غیرهمزمان مانند gevent، می‌توانید وظایف طولانی را بدون مسدود کردن سایر درخواست‌ها پردازش کند.
  • مقیاس‌پذیری: با افزایش تعداد Workerها می‌توان به صورت افقی برنامه را مقیاس داد و درخواست‌های همزمان بیشتری را مدیریت کرد.
  • تحمل خطا (Fault Tolerance): Workerهای ناکارآمد یا کرش کرده به‌صورت خودکار جایگزین می‌شوند و دسترسی بالا را تضمین می‌کند.
  • آماده برای محیط تولید: برخلاف سرور توسعه، Gunicorn برای محیط تولید بهینه‌سازی شده و ویژگی‌های امنیت، پایداری و عملکرد بهتری دارد.

با استفاده از Gunicorn در محیط تولید، می‌توانید توان عملیاتی و پاسخگویی برنامه Flask خود را به‌طور چشمگیری بهبود دهید و آن را برای مدیریت ترافیک واقعی آماده کنید.

فعال‌سازی کش برای کاهش بار

کش (Caching) یکی از بهترین روش‌ها برای بهبود عملکرد Flask است، زیرا باعث کاهش پردازش‌های تکراری می‌شود. در این بخش، با استفاده از  Flask-Caching نتیجه مسیر  /slow را کش می‌کنیم.

نصب و پکربندی  Flask-Caching با Redis

ابتدا پکیج‌های مورد نیاز را نصب کنید:

root@ubuntu:~# pip install Flask-Caching redis

سپس فایل app.py را باز کنید و آن را برای اضافه‌کردن کش به مسیر  /slow  به‌روزرسانی کنید:

root@ubuntu:~# nano app.py

محتوای فایل:

from flask_caching import Cache

app = Flask(__name__)

# Configure Flask-Caching with Redis
app.config['CACHE_TYPE'] = 'redis'
app.config['CACHE_REDIS_HOST'] = 'localhost'
app.config['CACHE_REDIS_PORT'] = 6379
cache = Cache(app)

@app.route('/slow')
@cache.cached(timeout=60)
def slow():
    import time
    time.sleep(2)  # Simulate a slow response
    return jsonify(message="This request was slow!")

بعد از اولین درخواست به مسیر  /slow، درخواست‌های بعدی که ظرف 60 ثانیه انجام شوند از کش پاسخ داده می‌شوند و دیگر تابع time.sleep() اجرا نمی‌شود. این کار باعث کاهش بار روی سرور و افزایش سرعت پاسخ‌دهی می‌شود.

نکته: در این آموزش، از localhost به‌عنوان هاست Redis استفاده کردیم. در محیط تولید، توصیه می‌شود از یک سرویس Redis مدیریت شده استفاده کنید این کار باعث مقیاس‌پذیری بهتر، پایداری بالاتر و امنیت بیشتر برای نیازهای کشینگ شما می‌شود.

نحوه ایجاد یک REST API با Flask در سرور مجازی اوبونتو Ubuntu
 ایجاد یک REST API با Flask

بررسی عملکرد کش

حالا بیایید بررسی کنیم که آیا داده‌ها به‌درستی کش می‌شوند یا خیر.

اولین درخواست به مسیر  /slow (این درخواست زمان‌بر است و نتیجه آن کش می‌شود):

root@ubuntu:~# time curl http://127.0.0.1:5000/slow

خروجی

Output{"message":"This request was slow!"}
curl http://127.0.0.1:5000/slow  0.00s user 0.01s system 0% cpu 2.023 total

همان‌طور که می‌بینید، اولین درخواست حدود 2 ثانیه طول کشید.

درخواست بعدی به همان مسیر ظرف 60 ثانیه (این بار نتیجه از کش برمی‌گردد):

root@ubuntu:~# time curl http://127.0.0.1:5000/slow

خروجی:

Output{"message":"This request was slow!"}
curl http://127.0.0.1:5000/slow 0.00s user 0.00s system 0% cpu 0.015 total

این بار پاسخ تقریبا فوری بود (0.015 ثانیه)، چون داده‌ها از کش Redis بازگردانده شدند.

بهینه‌سازی کوئری‌های پایگاه داده

کوئری‌های پایگاه داده معمولا می‌‌توانند به یک گلوگاه عملکردی در برنامه Flask تبدیل شوند. در این بخش، با استفاده از SQLAlchemy و Connection Pooling به شبیه‌سازی بهینه‌سازی کوئری‌های پایگاه داده می‌پردازیم.

شبیه‌سازی کوئری پایگاه داده با Connection Pooling

ابتدا باید SQLAlchemy را نصب کنیم:

root@ubuntu:~# pip install Flask-SQLAlchemy

سپس فایل app.py را برای پیکربندی Connection Pooling به‌روزرسانی کنید:

rom flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text

# Simulate an intensive database operation
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_POOL_SIZE'] = 5  # Connection pool size
db = SQLAlchemy(app)

@app.route('/db1')
def db_operation_pooling():
    # Simulate a database query
    result = db.session.execute(text('SELECT 1')).fetchall()
    return jsonify(result=str(result))

حالا اگر یک درخواست Curl به مسیر /db1 ارسال کنیم، خروجی زیر نمایش داده می‌شود:

root@ubuntu:~# curl http://127.0.0.1:5000/db1

خروجی:

output{"result":"[(1,)]"}

مزایای Connection Pooling در Flask

با پیاده‌سازی Connection Pooling در محیط تولید، می‌توانید به شکل قابل توجهی عملکرد برنامه Flask خود را بهینه کنید.

  • Connection Pooling به برنامه این اجازه را می‌دهد تا از اتصالات موجود به پایگاه داده مجددا استفاده کند، به‌جای اتصال جدید برای هر درخواست.
  • این کار باعث کاهش سربار ایجاد اتصال‌های جدید شده و در نتیجه زمان پاسخ‌دهی سریع‌تر و مقیاس‌پذیری بهتر را به همراه دارد.

تنظیمات مهم در SQLAlchemy

تنظیماتی که در بالا اضافه کردیم، شامل SQLALCHEMY_POOL_SIZE بود که تعداد اتصالات موجود در Pool را محدود می‌کند.

در محیط تولید، باید این مقدار را بر اساس نیازهای خاص هر پروژه و منابع سرور تنظیم کنید. علاوه بر آن می‌توانید از تنظیمات زیر هم استفاده کنید:

  • SQLALCHEMY_MAX_OVERFLOW :اجازه می‌دهد در صورت پر بودن Pool، اتصالات اضافی ایجاد شوند.
  • SQLALCHEMY_POOL_TIMEOUT: مشخص می‌کند که یک درخواست چه مدت منتظر اتصال بماند.

در این مثال برای سادگی از SQLite استفاده شد. اما در پروژه‌های واقعی معمولا از پایگاه داده‌های قوی‌تر مانند PostgreSQL یا MySQL استفاده می‌شود. این پایگاه داده‌ها معمولا مکانیزم‌های Connection Pooling داخلی خود را دارند که می‌توانند در کنار Connecti

on Pooling مربوط به SQLAlchemy استفاده شوند تا عملکرد بهتری ارائه دهند.

روش های بهینه‌ سازی منابع سرور مجازی ابری
بهینه‌ سازی منابع سرور مجازی

فعال‌سازی فشرده‌سازی Gzip

فشرده‌سازی پاسخ‌ها می‌تواند به شکل چشمگیری مقدار داده‌ای را که بین سرور و کلاینت منتقل می‌شود را کاهش دهد و باعث بهبود عملکرد شود.

نصب و پیکربندی Flask-Compress

ابتدا پکیج Flask-Compress را نصب کنید:

root@ubuntu:~# pip install Flask-Compress

سپس فایل app.py را برای فعال‌سازی فشرده‌سازی به‌روزرسانی کنید:

from flask_compress import Compress

# This below command enables Gzip compression for the Flask app
# It compresses responses before sending them to clients,
# reducing data transfer and improving performance
Compress(app)

@app.route('/compress')
def Compress():
    return "<h1>Welcome to the optimized Flask app !</h1>"
  • ان تنظیمات به صورت خدکار پاسخ‌هایی که بیشتر از 500 بایت باشند را فشرده می‌کند.
  • این کار باعث کاهش زمان انتقال داده‌ها و افزایش سرعت پاسخ‌دهی می‌شود.

مزایای استفاده از Gzip در محیط تولید

  • کاهش حجم داده‌ها: مخصوصا برای محتوای متنی مانند HTML, CSS, JavaScript.
  • افزایش سرعت بارگذاری صفحات: کاربران سریع‌تر محتوا را دریافت می‌کنند.
  • بهبود تجربه کاربری (UX): زمان بارگذاری کوتاه‌تر باعث رضایت بیشتر کاربران می‌شود.
  • کاهش هزینه‌های پهنای باند: چون داده کمتری بین سرور و کلاینت رو و بدل می‌شود.
  • سازگاری بالا: اکثر مرورگرهای مدرن به‌طور پیش‌فرض از Gzip decompression پشتیبانی می‌کنند، پس هیچ تغییری سمت کلاینت لازم نیست.

واگذار کردن وظایف سنگین به Celery

برای عملیات‌هایی که منابع زیادی مصرف می‌کنند مثل ارسال ایمیل‌ها یا پردازش داده‌های حجیم، بهتر است آن‌ها را به‌عنوان تسک‌های پس‌زمینه با استفاده از Celery اجرا کنید. این کار باعث می‌شود تسک‌های طولانی جلوی پردازش درخواست‌های جدید را نگیرند.

Celery چیست؟

Celery یک سیستم صف وظیفه توزیع‌شده و قدرتمند است که به شما اجازه می‌دهد تسک‌های زمان‌بر را به‌صورت asynchronous اجرا کنید. این ابزار با واگذاری وظایف به پردازشگرهای مستقل کار می‌‌کند. این پردازشگرها حتی می‌توانند روی سرورهای جداگانه اجرا شوند که منجر به استفاده بهتر از منابع و پردازش موازی می‌شود.

مزایای استفاده از Celery

  • بهبود زمان پاسخ‌دهی درخواست‌های کاربران
  • مقیاس‌پذیری بهتر و مدیریت موثر
  • توانایی اجرای وظایف پیچیده و زمان‌بر بدون مسدودکردن اپلیکیشن اصلی
  • پشتیبانی داخلی از زمان‌بندی وظایف و تلاش مجدد در صورت شکست
  • سازگاری آسان با پیام‌رسان‌های مختلف مثل RabbitMQ یا Redis

به کمک Celery می‌توانید مطمئن شوید که حتی هنگام اجرای عملیات‌‌های سنگین، اپلیکیشن‌ Flask شما پاسخگو باقی می‌ماند.

راه‌اندازی Celery برای تسک‌های پس‌زمینه

ابتدا Celery را نصب کنید:

root@ubuntu:~# pip install Celery

سپس فایل app.py را برای پیکربندی Celery و اجرای وظایف asynchronous به‌روزرسانی کنید:

from celery import Celery

celery = Celery(app.name, broker='redis://localhost:6379/0')

@celery.task
def long_task():
    import time
    time.sleep(10)  # Simulate a long task
    return "Task Complete"

@app.route('/start-task')
def start_task():
    long_task.delay()
    return 'Task started'

اجرای Worker

در یک ترمینال جداگانه، Celery worker را اجرا کنید:

root@ubuntu:~#  celery -A app.celery worker --loglevel=info

خروجی مشابه زیر خواهد بود:

Output------------- celery@your-computer-name v5.2.7 (dawn-chorus)
--- ***** ----- 
-- ******* ---- Linux-x.x.x-x-generic-x86_64-with-glibc2.xx 2023-xx-xx
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         app:0x7f8b8c0b3cd0
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     disabled://
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery

[tasks]
  . app.long_task

[2023-xx-xx xx:xx:xx,xxx: INFO/MainProcess] Connected to redis://localhost:6379/0
[2023-xx-xx xx:xx:xx,xxx: INFO/MainProcess] mingle: searching for neighbors
[2023-xx-xx xx:xx:xx,xxx: INFO/MainProcess] mingle: all alone
[2023-xx-xx xx:xx:xx,xxx: INFO/MainProcess] celery@your-computer-name ready.

اجرای درخواست

با یک دستور curl مسیر /start-task را تست کنید:

root@ubuntu:~# curl http://127.0.0.1:5000/start-task

خروجی:

OutputTask started

توضیح عملکرد

تابع /start-task دو کار انجام می‌دهد:

  • فراخوانی long_task.delay(): تسک Celery به‌صورت async اجرا می‌شود (تسک در صف قرار می‌گیرد، ولی منتظر اتمام آن نمی‌ماند).
  • بلافاصله مقدار 'Task started' برگردانده می‌شود.

در نتیجه، پاسخ درجا به کلاینت برمی‌گردد، درحالی که تسک طولانی در پس زمینه توسط Celery اجرا می‌شود.

پس از 10 ثانیه، worker خروجی مشابه زیر در لاگ نمایش می‌دهد:

[2024-xx-xx xx:xx:xx,xxx: INFO/MainProcess] Task app.long_task[task-id] received
[2024-xx-xx xx:xx:xx,xxx: INFO/ForkPoolWorker-1] Task app.long_task[task-id] succeeded in 10.xxxs: 'Task Complete'

Celery در محیط تولید (Production)

در حالت واقعی، بهتر است تنظیمات زیر را اعمال کنید:

  • استفاده از پیام‌رسان‌های قدرتمند مثل RabbitMQ
  • استفاده از یک result backend مطمئن (مثل PostgreSQL)
  • مدیریت Workerها با ابزارهایی مثل Supervisor
  • استفاده از ابزار مانیتورینگ مثل Flower
  • تقویت error handling و logging
  • اولویت‌بندی وظایف (Task Prioritization)
  • اجرای Workerها روی چندین ماشین برای مقیاس‌پذیری بیشتر
  • رعایت اقدامات امنیتی مناسب
سرور مجازی یا VPS چیست؟ انواع و کاربرد
VPS چیست؟

جمع بندی

در این آموزش یاد گرفتیم که با روش‌هایی مثل استفاده از WSGI سرورهای مناسب، کشینگ، فشرده‌سازی و اجرای وظایف سنگین در پس‌زمینه می‌توان عملکرد Flask را بهبود داد. این تکنیک‌ها باعث افزایش سرعت و کاهش فشار روی سرور می‌شوند.

با به‌کارگیری این راهکارها اپلیکیشن شما سریع‌تر، مقیاس‌پذیرتر و آماده‌تر برای محیط تولید خواهد بود و تجربه بهتری برای کاربران فراهم می‌کند.

به اشتراک بگذارید