Di dunia Django, add-on Saluran Django mulai populer. Perpustakaan ini harus membawa ke Django pemrograman jaringan asinkron yang telah kita tunggu-tunggu.
Artyom Malyshev di Moscow Python Conf 2017 menjelaskan bagaimana versi pertama perpustakaan melakukannya (sekarang penulis sudah zip channel2), mengapa ia melakukannya dan melakukannya sama sekali.
Pertama-tama, Zen Zen mengatakan bahwa solusi apa pun harus menjadi satu-satunya. Oleh karena itu,
dalam Python setidaknya ada tiga masing-masing . Sudah ada banyak kerangka kerja jaringan yang tidak sinkron:
- Bengkok
- Eventlet
- Gevent
- Tornado;
- Asyncio
Tampaknya, mengapa menulis perpustakaan lain dan apakah itu perlu sama sekali.
Tentang pembicara: Artyom Malyshev adalah pengembang Python independen. Dia terlibat dalam pengembangan sistem terdistribusi, berbicara di konferensi tentang Python. Artyom dapat ditemukan dengan nama panggilan
PROOFIT404 di Github dan di jejaring sosial.
Django sinkron dengan definisi . Jika kita berbicara tentang ORM, maka secara sinkron mengakses database selama akses atribut, ketika kita menulis, misalnya, post.author.username, tidak ada biaya.
Selain itu, Django adalah kerangka kerja WSGI.
WSGI
WSGI adalah antarmuka sinkron untuk bekerja dengan server web.
def app (environ, callback) : status, headers = '200 OK', [] callback (status, headers) return ['Hello world!\n']
Fitur utamanya adalah kami memiliki fungsi yang mengambil argumen dan segera mengembalikan nilai. Itu semua yang bisa diharapkan oleh server web dari kami.
Tidak sinkron dan tidak berbau .
Ini sudah lama dilakukan, pada tahun 2003, ketika web itu sederhana, pengguna membaca semua jenis berita di Internet, dan masuk ke buku tamu. Cukup menerima permintaan dan memprosesnya. Berikan jawaban dan lupakan bahwa pengguna ini sama sekali.

Tetapi, untuk sesaat, sekarang bukan tahun 2003, jadi pengguna menginginkan lebih banyak dari kami.

Mereka ingin aplikasi web yang kaya, konten langsung, mereka ingin aplikasi bekerja dengan baik di desktop, di laptop, di puncak lainnya, di jam. Yang paling penting,
pengguna tidak ingin menekan F5 , karena, misalnya, tablet tidak memiliki tombol seperti itu.

Browser web secara alami datang untuk menemui kami - mereka menambahkan protokol baru dan fitur baru. Jika Anda dan saya hanya mengembangkan frontend, maka kami hanya akan mengambil browser sebagai platform dan menggunakan fitur intinya, karena siap untuk memberikannya kepada kami.
Tapi, untuk programmer backend, semuanya telah banyak berubah . Soket web, HTTP2, dan sejenisnya merupakan masalah besar dalam hal arsitektur, karena keduanya merupakan koneksi jangka panjang dengan status yang perlu ditangani.

Ini adalah masalah yang coba diselesaikan oleh Saluran Django untuk Django. Pustaka ini dirancang untuk memberi Anda kemampuan menangani koneksi, meninggalkan Django Core yang biasa kami gunakan untuk tidak berubah sama sekali.
Dia menjadi orang yang luar biasa oleh
Andrew Godwin , pemilik aksen bahasa Inggris yang mengerikan yang berbicara dengan sangat cepat. Anda harus mengetahuinya dengan hal-hal seperti Migrasi Django Selatan dan Django yang sudah lama terlupakan, yang datang kepada kami dari versi 1.7. Sejak ia memperbaiki migrasi untuk Django, ia mulai memperbaiki soket web dan HTTP2.
Bagaimana dia melakukannya? Sekali waktu, gambar berikut muncul di Internet: kotak kosong, panah, tulisan "Arsitektur yang baik" - Anda memasukkan teknologi favorit Anda ke dalam kotak kecil ini, Anda mendapatkan situs web yang berskala baik.

Andrew Godwin memasuki server di kotak-kotak ini, yang berdiri di depan dan menerima permintaan apa pun, baik itu asinkron, sinkron, e-mail, apa pun. Di antara mereka ada yang disebut Channel Layer, yang menyimpan pesan yang diterima dalam format yang dapat diakses oleh kumpulan pekerja yang sinkron. Segera setelah koneksi asinkron mengirim sesuatu kepada kami, kami merekamnya di Channel Layer, dan kemudian pekerja sinkron dapat mengambilnya dari sana dan memprosesnya dengan cara yang sama seperti Tampilan Django atau hal lainnya, secara sinkron. Segera setelah kode sinkron mengirim respons kembali ke Kanal Lapisan, server asinkron akan memberikannya, mengalirkannya, melakukan apa pun yang diperlukan. Jadi, abstraksi dilakukan.
Ini menyiratkan beberapa implementasi, dan dalam produksi diusulkan untuk menggunakan
Twisted sebagai server asinkron yang mengimplementasikan frontend untuk Django, dan
Redis , yang akan menjadi saluran komunikasi yang sama antara Django sinkron dan Twink asinkron.
Berita baiknya: untuk menggunakan Saluran Django, Anda tidak perlu tahu Twisted atau Redis sama sekali - ini semua detail implementasi. DevOps Anda akan mengetahui hal ini, atau Anda akan bertemu ketika Anda memperbaiki produksi yang jatuh pukul tiga pagi.
ASGI
Abstraksi adalah protokol yang disebut ASGI. Ini adalah antarmuka standar yang terletak di antara antarmuka jaringan, server, apakah itu protokol sinkron atau asinkron dan aplikasi Anda. Konsep utamanya adalah saluran.
Saluran
Saluran adalah antrian pertama masuk pertama keluar pesan yang memiliki seumur hidup. Pesan-pesan ini dapat dikirimkan nol atau sekali, dan hanya dapat diterima oleh satu Konsumen.
Konsumen
Di Konsumen, Anda hanya menulis kode Anda.
def ws_message (message) : message.reply_channel.send ( { 'text': message.content ['text'], } )
Fungsi yang menerima pesan mungkin mengirim beberapa respons, atau mungkin tidak mengirim respons sama sekali. Sangat mirip dengan tampilan, satu-satunya perbedaan adalah bahwa tidak ada fungsi kembali, sehingga kita dapat berbicara tentang berapa banyak jawaban yang kita kembali dari fungsi.
Kami menambahkan fungsi ini ke perutean, misalnya, menggantungnya untuk menerima pesan di soket web.
from channels.routing import route from myapp.consumers import ws_message channel_routing = [ route ('websocket.receive' ws_message), }
Kami menulis ini dalam pengaturan Django, sama seperti database akan ditentukan.
CHANNEL_LAYERS = { 'default': { 'BACKEND': 'asgiref.inmemory', 'ROUTING': 'myproject.routing', }, }
Sebuah proyek dapat memiliki beberapa Lapisan Saluran, sama seperti ada beberapa basis data. Hal ini sangat mirip dengan router db jika seseorang menggunakannya.
Selanjutnya, kita mendefinisikan aplikasi ASGI kita. Ini menyinkronkan bagaimana Twisted dimulai dan bagaimana pekerja yang disinkronkan mulai - mereka semua membutuhkan aplikasi ini.
import os from channels.asgi import get_channel_layer os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'myproject.settings', ) channel_layer = get_channel_layer()
Setelah itu, sebarkan kode: jalankan gunicorn, kirimkan permintaan HTTP secara standar, secara sinkron, dengan tampilan, seperti yang biasa Anda lakukan. Kami memulai server asinkron, yang akan menjadi bagian depan di depan Django sinkron kami, dan pekerja yang akan memproses pesan.
$ gunicorn myproject.wsgi $ daphne myproject.asgi:channel_layer $ django-admin runworker
Balas saluran
Seperti yang telah kita lihat, pesan memiliki konsep seperti saluran Balas. Mengapa ini dibutuhkan?
Saluran satu arah, masing-masing menerima WebSocket, WebSocket terhubung, putuskan WebSocket - ini adalah saluran umum ke sistem untuk pesan yang masuk. Saluran Balas adalah saluran yang terikat erat dengan koneksi pengguna. Dengan demikian, pesan memiliki saluran input dan output. Pasangan ini memungkinkan Anda mengidentifikasi dari siapa pesan ini berasal.

Grup
Grup adalah kumpulan saluran. Jika kami mengirim pesan ke grup, maka secara otomatis dikirim ke semua saluran grup ini. Ini nyaman karena tidak ada yang suka menulis untuk loop. Plus, implementasi grup biasanya dilakukan dengan menggunakan fungsi asli layer Channel, jadi ini lebih cepat daripada hanya mengirim pesan satu per satu.
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'], })
Grup juga ditambahkan ke perutean.
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), ]
Dan segera setelah saluran ditambahkan ke grup, balasan akan masuk ke semua pengguna yang terhubung ke situs kami, dan bukan hanya jawaban gema untuk diri kami sendiri.
Konsumen umum
Apa yang saya sukai Django adalah deklaratif. Demikian pula, ada Konsumen deklaratif.
Basis Konsumen adalah yang mendasar, hanya dapat memetakan saluran yang Anda tetapkan pada beberapa metode dan menyebutnya.
from channels.generic import BaseConsumer class MyComsumer (BaseConsumer) : method_mapping = { 'channel.name.here': 'method_name', } def method_name (self, message, **kwargs) : pass
Ada sejumlah besar konsumen yang telah ditentukan sebelumnya dengan perilaku yang sengaja diperbesar, seperti WebSocket Consumer, yang menentukan sebelumnya bahwa ia akan menangani koneksi WebSocket, menerima WebSocket, putuskan WebSocket. Anda dapat segera mengindikasikan di grup mana untuk menambahkan saluran balasan, dan segera setelah Anda menggunakan self.send, dia akan mengerti apakah akan mengirim ini ke grup atau ke satu pengguna.
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)
Ada juga opsi konsumen WebSocket dengan JSON, yaitu, bukan teks, bukan byte, tetapi JSON yang sudah diuraikan akan datang untuk menerima, yang nyaman.
Dalam perutean, ditambahkan dengan cara yang sama melalui route_class. Myapp diambil dalam route_class, yang ditentukan dari konsumen, semua saluran diambil dari sana dan semua saluran yang ditentukan dalam myapp dialihkan. Tulis lebih sedikit dengan cara ini.
Routing
Mari kita bicara secara rinci tentang perutean dan apa yang disediakannya bagi kita.
Pertama, ini adalah filter.
// app.js S = new WebSocket ('ws://localhost:8000/chat/')
Ini bisa menjadi jalur yang datang kepada kami dari koneksi soket web URI, atau metode permintaan http. Ini bisa berupa bidang pesan apa saja dari saluran, misalnya, untuk email: teks, isi, salinan karbon, apa pun. Jumlah argumen kata kunci untuk rute adalah arbitrer.
Routing memungkinkan Anda membuat rute bersarang. Jika beberapa konsumen ditentukan oleh beberapa karakteristik umum, mudah untuk mengelompokkan mereka dan menambahkan semua orang ke rute sekaligus.
from channels import route, include blog_routes = [ route ( 'websocket.connect', blog, path = r'^/stream/') , ] routing = [ include (blog_routes, path= r'^/blog' ), ]
Multiplexing
Jika kita membuka beberapa soket web, masing-masing memiliki URI yang berbeda, dan kita dapat menggantung beberapa penangannya. Tapi jujur, membuka beberapa koneksi hanya untuk melakukan sesuatu yang indah di backend tidak terlihat seperti pendekatan teknik.
Oleh karena itu, dimungkinkan untuk memanggil beberapa penangan pada satu soket web. Kami mendefinisikan WebsocketDemultiplexer yang beroperasi pada konsep streaming dalam satu soket web. Melalui aliran ini, itu akan mengarahkan pesan Anda ke saluran lain.
from channels import WebsocketDemultiplexer class Demultiplexer (WebsocketDemultiplexer) : mapping = { 'intval': 'binding.intval', }
Dalam routing, multiplexer ditambahkan dengan cara yang sama seperti pada route_class konsumen deklaratif lainnya.
from channels import route_class, route from .consumers import Demultiplexer, ws_message channel_routing = [ route_class (Demultiplexer, path='^/binding/') , route ('binding.intval', ws_message ) , ]
Argumen stream ditambahkan ke pesan sehingga multiplexer dapat mencari tahu di mana harus meletakkan pesan yang diberikan. Argumen payload berisi semua yang masuk ke saluran setelah multiplexer memprosesnya.
Sangat penting untuk dicatat bahwa di Channel Layer, pesan akan mendapatkan
dua kali : sebelum multiplexer dan setelah multiplexer. Dengan demikian, segera setelah Anda mulai menggunakan multiplexer, Anda secara otomatis menambahkan latensi ke permintaan Anda.
{ "stream" : "intval", "payload" : { … } }
Sesi
Setiap saluran memiliki sesi sendiri. Ini adalah hal yang sangat nyaman, misalnya, untuk menyimpan keadaan antara panggilan ke penangan. Anda dapat mengelompokkannya berdasarkan saluran balasan, karena ini adalah pengidentifikasi milik pengguna. Sesi disimpan di mesin yang sama dengan sesi http biasa. Untuk alasan yang jelas, cookie yang ditandatangani tidak didukung, cookie itu tidak ada di soket web.
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 )
Selama koneksi, Anda bisa mendapatkan sesi http dan menggunakannya di konsumen Anda. Sebagai bagian dari proses negosiasi, mengatur koneksi soket web, cookie dikirimkan kepada pengguna. Dengan demikian, oleh karena itu, Anda bisa mendapatkan sesi pengguna, dapatkan objek pengguna yang sebelumnya Anda gunakan di Django, sama seperti jika Anda bekerja dengan view.
from channels.sessions import http_session_user @http_session_user def ws_connect(message) : message.http_session ['room'] = room if message.user.username : …
Urutan pesan
Saluran dapat memecahkan masalah yang sangat penting. Jika kami membuat koneksi ke soket web dan segera mengirim, ini mengarah pada fakta bahwa dua peristiwa - WebSocket terhubung dan WebSocket menerima - sangat dekat waktu. Sangat mungkin bahwa konsumen untuk soket web ini akan berjalan secara paralel. Debugging ini akan sangat menyenangkan.
Saluran Django memungkinkan Anda memasukkan kunci dari dua jenis:
- Kunci mudah . Dengan menggunakan mekanisme sesi, kami menjamin bahwa sampai konsumen diproses untuk menerima pesan, kami tidak akan memproses pesan apa pun di soket web. Setelah koneksi dibuat, urutannya arbitrer, eksekusi paralel dimungkinkan.
- Hard lock - hanya satu konsumen pengguna tertentu yang berjalan pada satu waktu. Ini overhead untuk sinkronisasi, karena menggunakan mesin sesi lambat. Namun demikian, ada peluang seperti itu.
from channels.generic import WebsocketConsumer class MyConsumer(WebsocketConsumer) : http_user = True slight_ordering = True strict_ordering = False def connection_groups (self, **kwargs) : return ['chat']
Untuk menulis ini, ada dekorator yang sama yang kita lihat sebelumnya di sesi http, sesi saluran. Di konsumen deklaratif, Anda cukup menulis atribut, segera setelah Anda menulisnya, ini akan secara otomatis berlaku untuk semua metode konsumen ini.
Pengikatan data
Pada suatu waktu, Meteor menjadi terkenal karena pengikatan data.
Kami membuka dua browser, pergi ke halaman yang sama, dan di salah satu dari mereka kami klik pada scroll bar. Pada saat yang sama, di browser kedua, di halaman ini, bilah gulir mengubah nilainya. Ini keren.
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 sekarang melakukan hal yang sama.
Ini diimplementasikan menggunakan kait yang disediakan oleh
Sinyal Django . Jika penjilidan didefinisikan untuk model, semua koneksi yang ada dalam grup untuk instance model ini akan diberitahukan tentang setiap peristiwa. Kami membuat model, mengubah model, menghapusnya - semua ini akan menjadi peringatan. Pemberitahuan terjadi pada bidang yang ditunjukkan: nilai bidang ini telah berubah - payload sedang dibentuk, dikirim melalui soket web. Ini nyaman.
Penting untuk dipahami bahwa jika dalam contoh kita, kita terus-menerus mengklik bilah gulir, maka pesan akan terus-menerus pergi dan model akan disimpan. Ini akan bekerja sampai beban tertentu, kemudian semuanya bersandar pada pangkalan.
Lapisan redis
Mari kita bicara sedikit lebih banyak tentang bagaimana mengatur Layer Channel untuk produksi diatur - Redis.
Ini diatur dengan baik:
- bekerja dengan koneksi sinkron di tingkat pekerja;
- sangat ramah untuk Twisted, tidak melambat, di mana itu sangat diperlukan, yaitu di server front-end Anda;
- MSGPACK digunakan untuk membuat serialisasi pesan di dalam Redis, yang mengurangi jejak pada setiap pesan;
- Anda dapat mendistribusikan beban ke beberapa instance Redis, itu akan secara otomatis dikocok menggunakan algoritma hash yang konsisten. Dengan demikian, satu titik kegagalan hilang.
Saluran hanyalah daftar id dari Redis. By id adalah nilai dari pesan tertentu. Ini dilakukan agar Anda dapat mengontrol kehidupan setiap pesan dan saluran secara terpisah. Pada prinsipnya, ini logis.
>> SET "b6dc0dfce" " \x81\xa4text\xachello" >> RPUSH "websocket.send!sGOpfny" "b6dc0dfce" >> EXPIRE "b6dc0dfce" "60" >> EXPIRE "websocket.send!sGOpfny" "61"
Grup diimplementasikan dengan set yang diurutkan. Distribusi ke grup dilakukan di dalam skrip Lua - ini sangat cepat.
>> type group:chat zset >> ZRANGE group:chat 0 1 WITHSCORES 1) "websocket.send!sGOpfny" 2) "1476199781.8159261"
Masalah
Mari kita lihat masalah apa yang dimiliki pendekatan ini.
Panggilan balik neraka
Masalah pertama adalah panggilan balik neraka yang baru ditemukan. Sangat penting untuk memahami bahwa sebagian besar masalah dengan saluran yang akan Anda temui akan bergaya: argumen datang ke konsumen yang tidak ia harapkan. Dari mana mereka berasal, yang menempatkan mereka di Redis - semua ini adalah tugas investigasi yang meragukan. Sistem pendistribusian debugging umumnya untuk yang berkemauan keras. AsyncIO memecahkan masalah ini.
Seledri
Di Internet, mereka menulis bahwa Saluran Django adalah pengganti Celery.

Saya punya berita buruk untuk Anda - tidak, bukan itu.
Dalam saluran:
- jangan coba lagi, Anda tidak bisa menunda eksekusi penangan;
- tanpa kanvas - hanya panggilan balik. Selery juga menyediakan grup, rantai, akor favorit saya, yang, setelah mengeksekusi grup secara paralel, menyebabkan panggilan balik lain dengan sinkronisasi. Semua ini tidak ada dalam saluran;
- tidak ada pengaturan waktu kedatangan pesan, beberapa sistem tanpa ini tidak mungkin dirancang.
Saya melihat masa depan sebagai dukungan resmi untuk menggunakan saluran dan seledri bersama, dengan biaya minimal, dengan sedikit usaha. Tapi Saluran Django bukan pengganti untuk Seledri.
Django untuk web modern
Saluran Django adalah Django untuk web modern. Ini adalah Django yang sama yang kita semua gunakan: sinkron, deklaratif, dengan banyak baterai. Saluran Django hanya ditambah satu baterai. Anda selalu perlu memahami di mana menggunakannya dan apakah itu layak dilakukan. Jika Django tidak diperlukan dalam proyek, maka Saluran juga tidak diperlukan di sana. Mereka hanya berguna dalam proyek-proyek di mana Django dibenarkan.
Moscow Python Conf ++
Sebuah konferensi profesional untuk pengembang Python menuju ke tingkat yang baru - pada 22 dan 23 Oktober 2018 kami akan mengumpulkan 600 programmer Python terbaik di Rusia, menyajikan laporan yang paling menarik dan, tentu saja, menciptakan lingkungan untuk jaringan dalam tradisi terbaik komunitas Python Moskow dengan dukungan tim Ontiko.
Kami mengundang para ahli untuk membuat laporan. Panitia program sudah bekerja dan menerima aplikasi hingga 7 September.
Untuk peserta, program brainstorming online sedang dilakukan. Anda dapat menambahkan topik yang hilang ke dokumen atau pengeras suara ini segera yang pidatonya menarik bagi Anda. Dokumen akan diperbarui, pada kenyataannya, sepanjang waktu Anda dapat mengikuti pembentukan program.