استقرار پروژه جنگو با بهینگی بالا

در پروژه ای که در بستر داکر روی لینوکس پیاده سازی شده است نرم افزار از performance قابل قبولی برخوردار نیست. بخشی از نرم افزار با websocket کار میکند اما اکثرا در بستر http. دیتابیس، و بخشهای دیگر مثل celery, rabbitMQ و … هر کدام یک کانتینر هستند.

ایرادات استقرار یا deployment:

جنگو در یک کانتینر با دستور runserver اجرا شده است.

کل پروژه از Daphne به عنوان اپلیکیشن وب سرور ASGI استفاده میکند در حالی که بخش کوچکی از نرم افزار asyn است و با websocket کار میکند.

وب سروری مثل Nginx یا Apache وجود ندارد که فایلهای استاتیک را ارائه دهد.

بهتر بود در بخشهای Sync از اپلیکیشن سروری مثل uWSGI که یک WSGI هست استفاده میشد.

بنابراین معماری به شکل زیر تغییر یافت:

NGINX درخواست های websocket رو به Daphne و سایر درخواست ها را به uWSGI ارسال میکند. فایل های کانفیگ از قرار زیر است.

compose.yml

nginx.conf

DockerfileNginx

DockerfileNasim

لینک های مرتبط:

لینک 1

لینک 2

لینک 3

لینک 4

لینک 5

یک نمونه AJAX

در یک پروژه پرتکرار از AJAX استفاده کردیم و یه نمونه پیچیده شو اینجا میزارم.

در کد بالا route زیر که مربوط به یک view توی جنگو هست صدا زده شده

این یک route مربوطه به جنگو هست که یک پارامتر به اسم dep_id هم میگیره. قسمت جالب این کد اینه که همین کد جاوااسکریپت با django template engine داره render میشه و بعد توسط .replcace اصلاح میشه

نمونه route و view رو هم در ادامه میبینید.

Structured vs Functional vs Object Oriented

هر کدوم از این پارادایم های برنامه نویسی شرایط خاصی دارن.

OOP یا برنامه نویسی شیء گرا. وقتی میگیرم یه برنامه OOP هست که سه پیلار زیر در کل برنامه حاکم باشه. بنابراین به ما میگه که برنامه شامل قسمت هایی هست که ما کنترل رو به صورت غیرمستقیم به کامپوننتهای مختلف داده میشه و بنابراین میشه بخش های مختلف برنامه جداگانه قابل توسعه باشه.

  1. Encapsulation
  2. Inheritance
  3. Polymorphism

وقتی میگیم یک برنامه Structured هست که از توابع قابل تست استفاده شده باشه. عملا الان هر کسی داره کد میزنه structured کد میزنه. این مفهوم زمانی شکل گرفت که با زبانهایی مثل Assembly برنامه نوشته میشد. برنامه نویسی Functional روی انتقال کنترل برنامه به صورت مستقیم تاکید داره.

Functional Programming وقتی میتونیم ادعا کنیم که برنامه مون کاملا Functional هست که تمامی متغیرها غیرقابل تغییر باشن Immutable و بنابراین مجبور میشیم از یک زبان Functional هم استفاده کنیم که ابزارهای کار با این متغیرها رو به ما میده. Functional Programming بر روی انتصاب به متغیرها محدودیت میزاره.

یعنی عملا هر کدام از این پارادایم ها یک محدودیت خاص بر روی برنامه های ما میزاره و هیچ کدوم چیزی اضافه نمیکنه.

Structured Programming

کسایی که قبلا Assembly کد زدن میدونن برنامه ها چطور بود. شما یک سری دستور پشت هم میچیدید و هر جایی لازم بود از GOTO و JUMP استفاده میکردید که برنامه بره به جای جدید و اون تیکه رو اجرا کنه. مثلا از خط 20 میگفتیم برگرد به خط 10. با این تکنیک عملا حلقه هم پیاده سازی میشد. ولی بعدا این روش منسوخ شد و توی برنامه توابع تعریف شدن. مفاهیم حلقه با کلید های جدید شکل گرفتن. به این روش جدید میگن structured programming.

Structured programming یعنی برنامه رو به بخش های مختلف و توابع مختلف تقسیم کنیم و بتونیم هر کدوم از اون توابع یا بخش ها رو تست کنیم. تست کنیم که در چه شرایطی غلط کار میکنن و بنابراین اطمینانی از برنامه داشته باشیم.

Functional programming

جمله زیر یه تعریف ریشه ای از Functional Programming هست.

variables in functional programming do not vary

یعنی اگر برنامه ای بنویسیم که تمامی متغیرهای immutable یا غیرقابل تغییر باشن میتونیم ادعا کنیم که برنامه ما از پارادایم functional استفاده میکنه. ربان هایی مثل clojure از این پاردایم استفاده میکنن.

برای اینکه متغیرها تغییر نکنن نیاز هست که برنامه شامل اجرای توابع پشت سر هم باشن. یعنی توی یک زبان functional چیزی که میبینیم یه رشته از توابع هستن که پشت سر هم اجرا میشن

Event Sroucing چیه؟

فرض کنیم یه اپلیکیشن بانکی میخایم بنویسیم که حسابهای افراد رو مدیریت کنه. برای این کار یک روش اینه که اگه طرف مثلا 1 میلیون بریزه توی حسابش اپلیکیشن بره و مقداره متغیر مانده حساب رو با 1 میلیون اضافه کنه. و هر وقت x تومن خرج میکنه این متغیر رو x واحد کم کنه. این چه مشکلی میتونه داشته باشه:

1.ممکنه race condition پیش بیاد و چند تا thread یا پروسه مقدار رو با هم تغییر بدن و در این حالت عملا حساب بانکی مشتری رو خراب کردیم

2.تاریخچه ای از تراکنش ها وجود نداره که بتونیم پرینت حساب بگیریم

بهترین روش اینه که مقدار حساب مشتری یه متغیر غیرقابل تغییر باشه 🙂 “فکر نکنم دیگه بشه بهش گفت متغیر” و تراکنش ها رو ذخیره کنیم مثلا مقدار 1 میلیون هست و وقتی x تومن خرج میکنه دیگه از اون مقدار کم نکنیم و فقط یه تراکنش -x ثبت کنیم. در این حالت دیگه حساب مشتری خراب نمیشه. تاریخچه حساب معلومه و race condition به وجود نمیاد. این روشی هست که توی اپلیکیشن های بانکی ازش استفاده میشه و بهش میگن Event Sourcing.

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

مشکل دوم اینه که دیسک زیادی نیاز هست چون باید تمامی تراکنش های تمامی کاربران نگهداری شه.

اگر منابعی مثل CPU و Disk به اندازه کافی داشته باشیم میتونیم تمامی متغیرها رو immutable کنیم و بنابراین برنامه کاملا Functional باشه. در این حالت البته توسعه سخت تر هم هست.

شیء گرایی چیه Object Oriented Programming

برنامه نویسی شیء گرا یا OOP چیه؟ Object Oriented Programming

توی مصاحه از OOP و خصوصیاتش سوال زیاد میشه. از طرفی لازمه که یه برنامه نویس این رو بدونه. میشه گفت:

به ترکیت دیتا و توابع با هم میگیم OOP. این تعریف خیلی ناقصه. از طرفی به زبان ساده تر میشه گفت “یک روش برنامه نویسی که بشه باهاش دنیای واقعی رو مدل کرد” این هم باز کلیه و اگه بخایم بیشتر توضیح بدیم:

سه تا تکنیک توی برنامه نویسی وجود داره:

  • Encapsulation یا کپسوله کردن یا بسته بندی کردن
  • Inheritance یا ازثبری
  • Polymorphism یا چند شکلی

این 3 تکنیک برنامه نویسی رو اگر در برنامه های خودمون رعایت کنیم، میگیم داریم با تکنیک OOP کد میزنیم. و توی زبانهای OOP مثل Java C# Python و … این تکنیک ها رعایت میشه بخاطر همین بهشون میگیم زبانهای شیء گرا (البته پایتون مجبورتون نمیکنه صد درصد OOP کد بزنید).

آیا این تکنیک ها با پیدایش زبان های OO اومدن؟ خیر خیلی از برنامه نویسان C از قدیم از این تکنیک ها استفاده میکردند اما مجبور نبودند با این تکنیک کد بزنن و چون اینا تکنیک های خوبی بودن بعدا با زبان C++ رسمی شد و بمرور در زبان های مختلف تکامل پیدا کرد و استفاده ازشون آسون تر و بهینه تر شد و حتی اجباری شد.

Encapsulation

فرض کنید که بشه دور دسته ای از داده ها و توابع یک خط کشید و از بیرون این خط به این داده ها نشه دسترسی داشت و فقط خود اون توابع بتونن این داده ها رو تغییر بدن. در واقع داده ها و توابع private باشن و فقط به روش هایی استاندارد بشه بهشون به صورت غیر مستقیم یا بعضی از اونا مستقیم دسترسی داشت. این کار با مفهوم class انجام میشه و به این تکنیک میگن Encapsulation یا کپسوله کردن.

Inheritance

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

مثال بالا خیلی خوب ارثبری رو نشون میده. یک کلاس Animal داریم. طبیعتا همه حیوانات یه سری خصوصیات مشترک دارن مثلا پا دارن حرکت میکنن و صدا دارن. دو کلاس Duck و Lion از Animal ارث بردن چون اینها هم حیوان هستن و این خصوصیات رو دارن و اونا رو به ارث بردن اما این خصوصیات مشترکشون کمی فرق داره اردک شنا میکنه و شیر میدوه، صدای هر حیوان هم فرق داره پس همینطور که میبینید این خصوصیات دوباره تعریف شدن با این که به ارث هم بردن.

Polymorphism

برای توضیح چندشکلی یه مثال واقعی میزنیم. میدونیم توی UNIX برای خوندن و نوشتن داده device های مختلفی وجود داره که به دیوایس های ورودی میگیم STDIN و به دیوایس های خروجی میگیم STDOUT. دیوایس خروجی مثل صفحه مانتور و اسپیکر و دیوایس ورودی مثل کیبورد و ماوس. حالا یه تابع نوشتیم با اسم کپی که از یه دیوایس ورودی میخونه و کپی میکنه تو دیوایس خروجی

ولی توی این کد ما مشخص نکردیم کدوم دیوایس ورودی و خروجی. UNIX این مشکل رو حل کرده میگه شما هر دیوایس که میسازی و می نویسی توی دایورش باید 5 تا تابع تغریف کنی که برنامه های مختلف بتونن باهاش کار کنن. این توابع open close read write seek هستن. یعنی همه دیوایس ها توی درایورشون این توابع رو دارن. و باید تعریف اونا رو هم بنویسید مثلا چجوری بخونی و بنویسی توش چه جوری باز کنی و ببندیش بنابراین مثلا یک فایل باید این توابع رو اینجوری تعریف کنن براش:

و توی اون درایور باید همین توابع رو تعریف کنه و اون رو لود کنه چیزی مثل زیر (یعنی در واقع قابل استفاده اش کنه)

حالا اون تابع copy میتونه ازش استفاده کنه اگر تابع getchar اینجوری تعریف شده باشه

ببینید اینجوری تابع copy نیاز به تغییر نداره فقط getchar رو صدا میزنه و این getchar هم همون تابع read رو صدا میزنه که توی درایور هست.

بنابراین polymorhpism این قابلیت رو به برنامه ها میده که مثل پلاگین عمل کنن. یعنی اگر صد تا دیوایس اضافه بشه با صد تا درایور دیگه نیازی به تغییر copy نیست چون بهرحال اون درایور تابع read داره.

plymophism اینه یعنی این تابع copy صد شکل از دیوایس رو پوشش میده پس بهش میگیم تکنیک چند شکلی.

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

Signal و Middleware توی پروسه لاگین (جلوگیری از چندین لاگین همزمان با یک نام کاربری)

توی یه پروژه خواستیم یک نام کاربری دو تا session نداشته باشه یعنی یه کاربر توی دوتا مرورگر یا دو تا سیستم لاگین نکنه. برای این کار از Middleware و Signal توی پروژه استفاده کردم.

توی Middleware.py توی app مربط کد زیر کد زیر رو زدم:

حالا اینو توی MIDDLEWARE توی SETTING اضافه میکنیم.

Signal زیر رو اضافه میکنم که اگر کسی logtout کرد session اش پاک شه که بشه جای دیگه لاگین کنن:

خروجی کد مصاحبه

خروجی کد زیر چیه؟

طبق حدثم:

ولی else چی میگه

خروجی:

یعنی اگه Exception اتفاق نیوفته else هم اتفاق میوفته. در هر حالتی finally اتفاق میوفته