قنوات Django - الإجابة على شبكة الإنترنت الحديثة

في عالم Django ، تكتسب إضافة قنوات Django شعبية. يجب أن تجلب هذه المكتبة إلى Django برمجة الشبكة غير المتزامنة التي كنا ننتظرها. شرح Artyom Malyshev في Moscow Python Conf 2017 كيف يقوم الإصدار الأول من المكتبة بذلك (الآن قام المؤلف بالفعل بضغط القنوات 2) ، لماذا يفعل ذلك ويفعله على الإطلاق.

بادئ ذي بدء ، يقول Zen Zen أن أي حل يجب أن يكون هو الحل الوحيد. لذلك ، في Python هناك ثلاثة على الأقل لكل منهما . يوجد بالفعل الكثير من الأطر غير المتزامنة للشبكة:

  • الملتوية
  • النشرة الإخبارية
  • جيفنت
  • إعصار
  • Asyncio

يبدو ، لماذا تكتب مكتبة أخرى وما إذا كانت ضرورية على الإطلاق.


حول المتحدث: Artyom Malyshev هو مطور Python مستقل. يشارك في تطوير الأنظمة الموزعة ، ويتحدث في مؤتمرات بيثون. يمكن العثور على Artyom بالاسم المستعار PROOFIT404 على Github وعلى الشبكات الاجتماعية.

جانغو متزامن من حيث التعريف . إذا كنا نتحدث عن ORM ، فإن الوصول إلى قاعدة البيانات بشكل متزامن أثناء الوصول إلى السمة ، عندما نكتب ، على سبيل المثال ، post.author.username ، لا يكلف شيئًا.

بالإضافة إلى ذلك ، Django هو إطار WSGI.

WSGI


WSGI هي واجهة متزامنة للعمل مع خوادم الويب.

def app (environ, callback) : status, headers = '200 OK', [] callback (status, headers) return ['Hello world!\n'] 

ميزتها الرئيسية هي أن لدينا وظيفة تأخذ حجة وتعيد قيمة على الفور. هذا كل ما يمكن أن يتوقعه خادم الويب منا. لا متزامن ولا رائحة .

تم ذلك منذ وقت طويل ، في عام 2003 ، عندما كانت شبكة الإنترنت بسيطة ، قرأ المستخدمون جميع أنواع الأخبار على الإنترنت ، ودخلوا في كتب الضيوف. كان يكفي فقط قبول الطلب ومعالجته. قدم إجابة وننسى أن هذا المستخدم كان على الإطلاق.


ولكن ، لثانية ، الآن ليس عام 2003 ، لذلك يريد المستخدمون المزيد منا.

إنهم يريدون تطبيق الويب الغني ، والمحتوى المباشر ، ويريدون أن يعمل التطبيق بشكل رائع على سطح المكتب ، على الكمبيوتر المحمول ، على قمم أخرى ، على مدار الساعة. الأهم من ذلك ، لا يرغب المستخدمون في الضغط على F5 ، لأنه ، على سبيل المثال ، لا تحتوي الأجهزة اللوحية على مثل هذا الزر.



تأتي متصفحات الويب بشكل طبيعي لمقابلتنا - فهي تضيف بروتوكولات جديدة وميزات جديدة. إذا قمت أنا وأنت فقط بتطوير الواجهة الأمامية ، فسنأخذ المتصفح فقط كنظام أساسي ونستخدم ميزاته الأساسية ، لأنه جاهز لتقديمها إلينا.

لكن بالنسبة للمبرمجين في الخلفية ، تغير كل شيء كثيرًا . مآخذ الويب ، HTTP2 وما شابه ذلك هي ألم كبير من حيث الهندسة المعمارية ، لأنها اتصالات طويلة الأمد مع حالاتهم التي تحتاج إلى التعامل معها بطريقة أو بأخرى.


هذه هي المشكلة التي تحاول قنوات Django لـ Django حلها. تم تصميم هذه المكتبة لتمنحك القدرة على التعامل مع الاتصالات ، تاركًا Django Core الذي اعتدنا عليه دون تغيير تمامًا.

لقد صنعه شخص رائع من قبل أندرو جودوين ، مالك لهجة إنجليزية رهيبة تتحدث بسرعة كبيرة. يجب أن تعرفه من خلال أشياء مثل Django South المنسية منذ فترة طويلة و Django Migrations ، والتي جاءت إلينا من الإصدار 1.7. منذ أن أصلح عمليات الترحيل إلى Django ، بدأ في إصلاح مقابس الويب و HTTP2.

كيف فعلها؟ ذات مرة كانت هناك مثل هذه الصورة على الإنترنت: مربعات فارغة ، وسهام ، ونقش "هندسة معمارية جيدة" - تدخل تقنياتك المفضلة في هذه المربعات الصغيرة ، تحصل على موقع إلكتروني يتناسب بشكل جيد.



دخل أندرو جودوين خادمًا في هذه الصناديق ، يقف في المقدمة ويقبل أي طلبات ، سواء كانت غير متزامنة أو متزامنة أو بريد إلكتروني ، أيا كان. فيما بينها هو ما يسمى طبقة القناة ، التي تخزن الرسائل المستلمة بتنسيق يمكن الوصول إليه من قبل مجموعة من العمال المتزامنين. بمجرد أن يرسل الاتصال غير المتزامن شيئًا إلينا ، نقوم بتسجيله في طبقة القناة ، وبعد ذلك يمكن للعامل المتزامن استلامه من هناك ومعالجته بنفس طريقة أي عرض Django أو أي شيء آخر ، بشكل متزامن. بمجرد أن ترسل الشفرة المتزامنة استجابة إلى Channel Layer ، فإن الخادم غير المتزامن سيعطيها ، ويدفقها ، ويفعل كل ما يحتاجه. وبالتالي ، يتم تنفيذ التجريد.

هذا يعني العديد من التطبيقات ، وفي الإنتاج يقترح استخدام Twisted كخادم غير متزامن يقوم بتنفيذ الواجهة الأمامية لـ Django و Redis ، والتي ستكون نفس قناة الاتصال بين Django المتزامن و Twisted.

الخبر السار: من أجل استخدام قنوات Django ، لا تحتاج إلى معرفة أي من Twisted أو Redis على الإطلاق - هذه كلها تفاصيل التنفيذ. سيعرف DevOps هذا الأمر ، أو ستلتقي عندما تقوم بإصلاح إنتاج هابط في الثالثة صباحًا.

ASGI


التجريد هو بروتوكول يسمى ASGI. هذه هي الواجهة القياسية التي تقع بين أي واجهة شبكة أو خادم ، سواء كان بروتوكولًا متزامنًا أو غير متزامن وتطبيقك. مفهومها الرئيسي هو القناة.

القناة


القناة هي قائمة انتظار الرسائل التي تدخل أولاً في المقدمة. يمكن تسليم هذه الرسائل صفرًا أو مرة واحدة ، ولا يمكن استلامها إلا من قِبل عميل واحد.

المستهلكون


في Consumer ، أنت تكتب رمزك فقط.

 def ws_message (message) : message.reply_channel.send ( { 'text': message.content ['text'], } ) 

قد ترسل وظيفة تقبل رسالة عدة ردود ، أو قد لا ترسل استجابة على الإطلاق. مشابه جدًا للعرض ، والفرق الوحيد هو أنه لا توجد وظيفة إرجاع ، لذلك يمكننا التحدث عن عدد الإجابات التي نرجعها من الوظيفة.

نضيف هذه الوظيفة إلى التوجيه ، على سبيل المثال ، نعلقها لتلقي رسالة على مقبس الويب.

 from channels.routing import route from myapp.consumers import ws_message channel_routing = [ route ('websocket.receive' ws_message), } 

نكتب هذا في إعدادات Django ، تمامًا كما سيتم وصف قاعدة البيانات.

 CHANNEL_LAYERS = { 'default': { 'BACKEND': 'asgiref.inmemory', 'ROUTING': 'myproject.routing', }, } 

يمكن أن يحتوي المشروع على طبقات قنوات متعددة ، تمامًا مثلما يمكن أن تكون هناك قواعد بيانات متعددة. هذا الشيء مشابه جدًا لجهاز التوجيه db إذا استخدمه شخص ما.

بعد ذلك ، نحدد تطبيق ASGI. يقوم بمزامنة كيفية بدء Twisted وكيف يبدأ العمال المتزامنين - يحتاجون جميعًا إلى هذا التطبيق.

 import os from channels.asgi import get_channel_layer os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'myproject.settings', ) channel_layer = get_channel_layer() 

بعد ذلك ، انشر الرمز: قم بتشغيل gunicorn ، وأرسل طلب HTTP بشكل قياسي ، بشكل متزامن ، مع العرض ، كما تعودت. نبدأ الخادم غير المتزامن ، والذي سيكون الواجهة أمام جانغو المتزامن لدينا ، والعمال الذين سيعالجون الرسائل.

 $ gunicorn myproject.wsgi $ daphne myproject.asgi:channel_layer $ django-admin runworker 

قناة الرد


كما رأينا ، الرسالة لها مفهوم مثل قناة الرد. لماذا هذا مطلوب؟

قناة أحادية الاتجاه ، على التوالي تلقي WebSocket ، توصيل WebSocket ، قطع اتصال WebSocket - هذه قناة شائعة للنظام للرسائل الواردة. قناة الرد هي قناة مرتبطة باتصال المستخدم تمامًا. وبناءً على ذلك ، تحتوي الرسالة على قناة إدخال وإخراج. يتيح لك هذا الزوج تحديد مصدر هذه الرسالة.


المجموعات


المجموعة هي مجموعة من القنوات. إذا أرسلنا رسالة إلى مجموعة ، فسيتم إرسالها تلقائيًا إلى جميع قنوات هذه المجموعة. هذا مريح لأنه لا أحد يحب الكتابة للحلقات. بالإضافة إلى ذلك ، عادةً ما يتم تنفيذ المجموعات باستخدام الوظائف الأصلية لطبقة القناة ، لذا فهي أسرع من مجرد إرسال الرسائل واحدة في كل مرة.

 from channels import Group def ws_connect (message): Group ('chat').add (message.reply_channel) def ws_disconnect (message): Group ('chat').discard(message.reply_channel) def ws_message (message): Group ('chat'). Send ({ 'text': message.content ['text'], }) 

تتم إضافة المجموعات أيضًا إلى التوجيه بنفس الطريقة.

 from channels.routing import route from myapp.consumers import * channel_routing = [ route ('websocket.connect' , ws_connect), route ('websocket.disconnect' , ws_disconnect), route ('websocket.receive' , ws_message), ] 

وبمجرد إضافة القناة إلى المجموعة ، سيتم الرد على جميع المستخدمين المتصلين بموقعنا ، وليس فقط إجابة الصدى لأنفسنا.

المستهلكين عامة


إن ما أحبه جانغو هو إعلاني. وبالمثل ، هناك المستهلكين المعلنين.

Base Consumer هو واحد أساسي ، يمكنه فقط تعيين القناة التي حددتها على طريقة ما وتسميتها.

 from channels.generic import BaseConsumer class MyComsumer (BaseConsumer) : method_mapping = { 'channel.name.here': 'method_name', } def method_name (self, message, **kwargs) : pass 

هناك عدد كبير من المستهلكين المحددين مسبقًا الذين لديهم سلوك معزز بشكل متعمد ، مثل WebSocket Consumer ، والذي يحدد مسبقًا أنه سيتعامل مع اتصال WebSocket ، وتلقي WebSocket ، وقطع اتصال WebSocket. يمكنك على الفور تحديد أي المجموعات لإضافة قناة رد ، وبمجرد استخدام self.send ، سيفهم ما إذا كان سيتم إرسال هذا إلى مجموعة أو إلى مستخدم واحد.

 from channels.generic import WebsocketConsumer class MyConsumer (WebsocketConsumer) : def connection_groups (self) : return ['chat'] def connect (self, message) : pass def receive (self, text=None, bytes=None) : self.send (text=text, bytes=bytes) 

هناك أيضًا خيار مستهلك WebSocket مع JSON ، وهو ليس نصًا ، وليس وحدات بايت ، ولكن JSON المعرب بالفعل سيأتي لاستلامه ، وهو أمر مناسب.

في التوجيه ، تتم إضافته بنفس الطريقة عبر route_class. يتم أخذ Myapp في المسار class_class ، والذي يتم تحديده من قبل المستهلك ، ويتم أخذ جميع القنوات من هناك ويتم توجيه جميع القنوات المحددة في myapp. اكتب أقل بهذه الطريقة.

التوجيه


دعونا نتحدث بالتفصيل عن التوجيه وما يوفره لنا.

أولاً ، هذه مرشحات.

 // app.js S = new WebSocket ('ws://localhost:8000/chat/') # routing.py route('websocket.connect', ws_connect, path=r'^/chat/$') 

يمكن أن يكون هذا هو المسار الذي وصلنا من URI لاتصال مقبس الويب ، أو طريقة طلب http. يمكن أن يكون أي حقل رسالة من القناة ، على سبيل المثال ، للبريد الإلكتروني: نص ، نص ، نسخة كربونية ، أيا كان. عدد وسائط الكلمات الأساسية للمسار عشوائي.

يتيح لك التوجيه إنشاء مسارات متداخلة. إذا تم تحديد العديد من المستهلكين من خلال بعض الخصائص المشتركة ، فمن المناسب تجميعهم وإضافة الجميع إلى المسار مرة واحدة.

 from channels import route, include blog_routes = [ route ( 'websocket.connect', blog, path = r'^/stream/') , ] routing = [ include (blog_routes, path= r'^/blog' ), ] 

الضرب


إذا فتحنا العديد من مقابس الويب ، فسيكون لكل منها عنوان URI مختلف ، ويمكننا تعليق العديد من المعالجات عليها. ولكن لنكون صادقين ، فإن فتح العديد من الاتصالات لمجرد القيام بشيء جميل على الواجهة الخلفية لا يبدو كمقاربة هندسية.

لذلك ، من الممكن استدعاء عدة معالجات على مأخذ ويب واحد. نحدد مثل WebsocketDemultiplexer الذي يعمل على مفهوم الدفق داخل مقبس ويب واحد. من خلال هذا التدفق ، سيعيد توجيه رسالتك إلى قناة أخرى.

 from channels import WebsocketDemultiplexer class Demultiplexer (WebsocketDemultiplexer) : mapping = { 'intval': 'binding.intval', } 

في التوجيه ، تتم إضافة معدد الإرسال بنفس الطريقة كما هو الحال في أي مسار_مستخدم توضيحي آخر.

 from channels import route_class, route from .consumers import Demultiplexer, ws_message channel_routing = [ route_class (Demultiplexer, path='^/binding/') , route ('binding.intval', ws_message ) , ] 

تتم إضافة وسيطة الدفق إلى الرسالة بحيث يتمكن معدد الإرسال من معرفة مكان وضع الرسالة المحددة. تحتوي وسيطة الحمولة على كل ما يدخل القناة بعد أن يعالجها معدد الإرسال.

من المهم جدًا ملاحظة أنه في طبقة القناة ، ستحصل الرسالة على مرتين : قبل معدد الإرسال وبعد معدد الإرسال. وهكذا ، بمجرد أن تبدأ في استخدام معدد الإرسال ، فإنك تقوم تلقائيًا بإضافة الكمون لطلباتك.

 { "stream" : "intval", "payload" : { … } } 

الجلسات


كل قناة لها جلساتها الخاصة. هذا أمر مريح للغاية ، على سبيل المثال ، لتخزين الحالة بين المكالمات إلى معالجات. يمكنك تجميعها حسب قناة الرد ، لأن هذا معرف ينتمي إلى المستخدم. يتم تخزين الجلسة في نفس المحرك مثل جلسة http العادية. لأسباب واضحة ، لا يتم دعم ملفات تعريف الارتباط الموقعة ، فهي ببساطة ليست في مقبس الويب.

 from channels.sessions import channel_session @channel_session def ws_connect(message) : room=message.content ['path'] message.channel_session ['room'] = room Croup ('chat-%s' % room).add ( message.reply_channel ) 

أثناء الاتصال ، يمكنك الحصول على جلسة http واستخدامها في المستهلك الخاص بك. كجزء من عملية التفاوض ، وإعداد اتصال مقبس الويب ، يتم إرسال ملفات تعريف الارتباط إلى المستخدم. وفقًا لذلك ، يمكنك الحصول على جلسة مستخدم ، والحصول على كائن المستخدم الذي استخدمته في Django من قبل ، تمامًا كما لو كنت تعمل مع طريقة العرض.

 from channels.sessions import http_session_user @http_session_user def ws_connect(message) : message.http_session ['room'] = room if message.user.username : … 

ترتيب الرسائل


يمكن للقنوات حل مشكلة مهمة للغاية. إذا أنشأنا اتصالًا بمقبس ويب وأرسلناه على الفور ، فإن هذا يؤدي إلى حقيقة أن الحدثين - توصيل WebSocket واستقبال WebSocket - قريبان جدًا من الوقت. من المحتمل جدًا أن يعمل المستهلك لمقابس الويب هذه بالتوازي. تصحيح هذا سيكون الكثير من المرح.

تسمح لك قنوات Django بإدخال قفل من نوعين:

  1. قفل سهل . باستخدام آلية الجلسة ، نضمن أنه حتى تتم معالجة المستهلك لتلقي رسالة ، فلن نقوم بمعالجة أي رسالة على مآخذ الويب. بعد إنشاء الاتصال ، يكون الأمر تعسفيًا ، ويكون التنفيذ المتوازي ممكنًا.
  2. قفل ثابت - يعمل مستهلك واحد فقط لمستخدم معين في كل مرة. هذا هو حمل المزامنة لأنه يستخدم محرك جلسة بطيئة. ومع ذلك ، هناك مثل هذه الفرصة.

 from channels.generic import WebsocketConsumer class MyConsumer(WebsocketConsumer) : http_user = True slight_ordering = True strict_ordering = False def connection_groups (self, **kwargs) : return ['chat'] 

لكتابة هذا ، هناك نفس الديكور الذي رأيناه سابقًا في جلسة http ، جلسة القناة. في المستهلك التقريري ، يمكنك ببساطة كتابة السمات ، بمجرد كتابتها ، سيتم تطبيق هذا تلقائيًا على جميع طرق هذا المستهلك.

ربط البيانات


في وقت من الأوقات ، أصبح النيزك مشهورًا بربط البيانات.

نفتح مستعرضين ، وننتقل إلى نفس الصفحة ، وننقر في أحدهما على شريط التمرير. في الوقت نفسه ، في المتصفح الثاني ، في هذه الصفحة ، يغير شريط التمرير قيمته. هذا رائع.

 class IntegerValueBinding (WebsocketBinding) : model = IntegerValue stream = intval' fields= ['name', 'value'] def group_names (self, instance, action ) : return ['intval-updates'] def has_permission (self, user, action, pk) : return True 

جانغو يفعل الآن نفس الشيء.

يتم تنفيذ ذلك باستخدام الخطاطيف المقدمة من Django Signals . إذا تم تعريف الربط للنموذج ، فسيتم إخطار جميع الاتصالات الموجودة في المجموعة لهذا المثيل من النموذج بكل حدث. لقد أنشأنا نموذجًا ، وغيرنا النموذج ، وحذفناه - كل هذا سيكون تحذيرًا. يحدث الإشعار في الحقول المشار إليها: تغيرت قيمة هذا الحقل - يتم تشكيل حمولة ، يتم إرسالها عبر مقبس الويب. هذا مريح.

من المهم أن نفهم أنه إذا نقرنا في مثالنا باستمرار على شريط التمرير ، فستنتقل الرسائل باستمرار وسيتم حفظ النموذج. سيعمل هذا حتى حمولة معينة ، ثم كل شيء يقع على القاعدة.

طبقة ريديس


لنتحدث قليلاً عن كيفية ترتيب طبقة القناة الأكثر شعبية للإنتاج - Redis.

مرتبة بشكل جيد:

  • يعمل مع اتصالات متزامنة على مستوى العمال ؛
  • ودية للغاية لـ Twisted ، لا تبطئ ، حيث يكون ذلك ضروريًا بشكل خاص ، أي على خادمك الأمامي ؛
  • يتم استخدام MSGPACK لإجراء تسلسل للرسائل داخل Redis ، مما يقلل من أثر كل رسالة ؛
  • يمكنك توزيع الحمل على العديد من حالات Redis ، سيتم تبديله تلقائيًا باستخدام خوارزمية التجزئة المتسقة. وهكذا ، تختفي نقطة واحدة من الفشل.

القناة هي مجرد قائمة معرفات من Redis. حسب المعرّف هي قيمة رسالة معينة. يتم ذلك بحيث يمكنك التحكم في حياة كل رسالة وقناة بشكل منفصل. من حيث المبدأ ، هذا منطقي.

 >> SET "b6dc0dfce" " \x81\xa4text\xachello" >> RPUSH "websocket.send!sGOpfny" "b6dc0dfce" >> EXPIRE "b6dc0dfce" "60" >> EXPIRE "websocket.send!sGOpfny" "61" 

يتم تنفيذ المجموعات من خلال مجموعات مرتبة. يتم التوزيع على المجموعات داخل برنامج Lua النصي - هذا سريع جدًا.

 >> type group:chat zset >> ZRANGE group:chat 0 1 WITHSCORES 1) "websocket.send!sGOpfny" 2) "1476199781.8159261" 

المشاكل


دعونا نرى المشاكل التي يعاني منها هذا النهج.

رد الاتصال الجحيم


المشكلة الأولى هي جحيم رد الاتصال المخترع حديثا. من المهم جدًا أن نفهم أن معظم المشكلات المتعلقة بالقنوات التي ستواجهها ستكون في الأسلوب: جاءت الحجج إلى المستهلك التي لم يتوقعها. من أين أتوا ، الذين وضعوهم على Redis - كل هذه مهمة تحقيق مشكوك فيها. تصحيح أنظمة التوزيع بشكل عام لذوي الإرادة. AsyncIO يحل هذه المشكلة.

الكرفس


على الإنترنت ، يكتبون أن Django Channels هي بديل لـ Celery.

لدي أخبار سيئة لك - لا ، هذا ليس كذلك.

في القنوات:

  • لا إعادة المحاولة ، لا يمكنك تأخير تنفيذ معالج ؛
  • لا قماش - مجرد استدعاء. يوفر الكرفس أيضًا مجموعات ، سلسلة ، وترتي المفضلة ، والتي ، بعد تنفيذ المجموعات بالتوازي ، تتسبب في رد اتصال آخر مع المزامنة. كل هذا ليس في القنوات.
  • لا يوجد إعداد لوقت وصول الرسائل ، فبعض الأنظمة التي بدونها لا يمكن تصميمها ببساطة.

أرى المستقبل كدعم رسمي لاستخدام القنوات والكرفس معًا بأقل تكلفة وبأقل جهد. لكن قنوات جانغو ليست بديلا عن الكرفس.

Django لشبكة الإنترنت الحديثة


Django Channels هي Django للشبكة الحديثة. هذا هو نفس Django الذي اعتدنا جميعًا على استخدامه: متزامن ، إعلاني ، مع الكثير من البطاريات. قنوات Django هي بالإضافة إلى بطارية واحدة. تحتاج دائمًا إلى فهم مكان استخدامه وما إذا كان الأمر يستحق القيام به. إذا لم تكن هناك حاجة إلى Django في المشروع ، فلن تكون هناك حاجة إلى القنوات أيضًا. إنها مفيدة فقط في تلك المشاريع التي تبرر Django.

موسكو بيثون كونفو ++

يذهب مؤتمر محترف لمطوري Python إلى مستوى جديد - في يومي 22 و 23 أكتوبر 2018 ، سنجمع أفضل 600 مبرمج Python في روسيا ، ونقدم التقارير الأكثر إثارة للاهتمام ، وبالطبع ، نخلق بيئة للتواصل في أفضل تقاليد مجتمع Python في موسكو بدعم من فريق Ontiko.

ندعو الخبراء لتقديم تقرير. تعمل لجنة البرنامج بالفعل وتقبل الطلبات حتى 7 سبتمبر.

بالنسبة للمشاركين ، يتم تنفيذ برنامج العصف الذهني عبر الإنترنت. يمكنك إضافة مواضيع مفقودة إلى هذا المستند أو مكبرات الصوت على الفور التي تهمك خطاباتها. سيتم تحديث الوثيقة ، في الواقع ، في كل وقت يمكنك اتباع تشكيل البرنامج.

Source: https://habr.com/ru/post/ar418445/


All Articles