Pengantar
Halo, Habr! Nama saya Boris dan dalam pekerjaan ini saya akan berbagi dengan Anda pengalaman saya dalam merancang dan menerapkan layanan surat massal sebagai bagian dari sistem pemberitahuan siswa yang komprehensif oleh para guru (selanjutnya disebut juga sebagai Ada), yang juga saya implementasikan.
Neraka
Ini kemudian diperlukan untuk meniadakan jumlah gangguan dalam proses pendidikan karena alasan berikut:
- Guru tidak ingin membagikan detail kontak pribadi;
- Para siswa benar-benar terlalu - mereka tidak punya banyak pilihan;
- Karena spesifikasi almamater saya, banyak guru yang terpaksa atau lebih suka menggunakan perangkat seluler tanpa akses ke Internet;
- Jika Anda mengirim pesan melalui para pemimpin kelompok, maka efek dari "telepon rusak" ikut bermain, serta faktor "oh, saya lupa :(".
Ini berjalan sekitar jadi :
- Guru melalui salah satu saluran komunikasi yang tersedia baginya: SMS, Telegram, aplikasi SPA - mengirimkan Ada teks pesan dan daftar penerima;
- Ada menyiarkan pesan yang diterima kepada semua siswa * yang tertarik melalui berbagai saluran komunikasi.
* Akses ke layanan disediakan atas dasar aplikasi sukarela.
Ini diasumsikan bahwa
- Jumlah total pengguna tidak akan melebihi sepuluh ribu;
- Rasio murid-guru / anggota Kantor Dalam Negeri (kantor dekan, puskesmas, meja registrasi militer, dll.) Akan dijaga pada level 10: 1;
- : « », « ))0» ..
- ;
- , , ;
- ;
- : - - , , .
Karya ini terdiri dari lima bagian: pengantar, persiapan, konseptual, subjek dan final.
Anda dapat melewati bagian persiapan dengan aman jika Anda terbiasa dengan interpretasi Redis dari pola Pub / Sub, serta mekanisme kejadian, skrip LUA, dan penanganan kunci yang sudah usang, sebagai tambahan, sangat diinginkan untuk memiliki setidaknya beberapa gagasan tentang arsitektur layanan mikro perangkat lunak.
Di bagian subjek, kode di Python ditinjau, tetapi saya yakin ada cukup informasi sehingga Anda dapat menulis sesuatu seperti ini pada apa pun.
Persiapan
Sangat kasar dan sangat abstrak ~ 5 menit
Redis — [BSD 3-clause] , «-» ().
, .
, -, .
( ).
, , , LUA 5.1.
, .
, -, .
( ).
, , , LUA 5.1.
Detail dan langsung ~ 15 menit
- Pub/Sub — Redis. , fire&forget , ,
PUBLISH,SUBSCRIBE-; - Redis Keyspace Notifications. ;
- EXPIRE — Redis. «How Redis expires keys»;
- Redis 6.0 Default Configuration File. . 939:948 (The default effort of the expire cycle…);
- EVAL — Redis.
EVALEVALSHA, «Atomicity of scripts», «Global variables protection» «Available libraries»,cjson; - Redis Lua Scripts Debugger. , . — ;
- . , .
Konseptual
Pendekatan naif
Solusi paling jelas yang dapat Anda pikirkan: beberapa metode pengiriman (
send_vk, send_telegramdll.) Dan satu penangan yang akan memanggilnya dengan argumen yang diperlukan.
Masalah ekstensibilitas
Jika kami ingin menambahkan metode pengiriman baru, kami harus mengubah kode yang ada, dan ini adalah batasan dari platform perangkat lunak.
Masalah stabilitas
Salah satu metode rusak = seluruh layanan rusak.
Masalah terapan
API dari saluran komunikasi yang berbeda sangat berbeda satu sama lain dalam hal interaksi. Misalnya, VKontakte mendukung surat massal, tetapi tidak lebih dari ratusan pengguna per panggilan. Telegram tidak ada, tetapi memungkinkan lebih banyak panggilan per detik.
VK API hanya bekerja melalui HTTP; Telegram memiliki gateway HTTP, tetapi kurang stabil dibandingkan MTProto dan kurang terdokumentasi dengan baik.
Ada banyak perbedaan seperti itu: panjang pesan maksimum
random_id, interpretasi dan penanganan kesalahan, dll. dll.
Bagaimana cara mengatasinya?
Diputuskan untuk memisahkan proses penempatan pesan dalam antrian dan proses pengiriman (selanjutnya disebut sebagai kurir) di tingkat organisasi, dan sehingga yang pertama bahkan tidak mencurigai keberadaan yang terakhir, dan sebaliknya, dan Redis akan bertindak sebagai penghubung di antara mereka.
Tidak jelas? Pesan makanan!
Sementara itu, Anda menunggu - izinkan saya memperkenalkan interpretasi saya tentang tindakan mulia ini, dimulai dengan desain dan diakhiri dengan pintu tertutup di belakang kurir.
- Anda mengklik tombol "Pesan" kuning besar;
- Yandex.Food menemukan kurir, memberi tahu restoran tentang item yang dipilih dan mengembalikan nomor pesanan kepada Anda untuk mengurangi ketidakpastian harapan;
- Setelah selesai memasak, restoran memperbarui status pesanan dan memberikan makanan kepada kurir;
- Kurir, pada gilirannya, memberikan makanan kepada Anda, dan kemudian menandai pesanan sebagai selesai.
Selamat makan!
Kembali ke desain
Ada kemungkinan bahwa model yang diberikan pada paragraf sebelumnya tidak sepenuhnya sesuai dengan kenyataan, tetapi dialah yang membentuk dasar dari solusi yang dikembangkan.
Data yang terkait dengan nomor pesanan akan disebut riwayat , memungkinkan Anda untuk menjawab pertanyaan-pertanyaan berikut ini kapan saja :
- Siapa yang mengirim;
- Apa yang dia kirimkan;
- Darimana;
- Kepada siapa;
- Siapa yang mendapatkannya dan bagaimana.
Riwayat dibuat bersama dengan urutan sebagai dua kunci Redis terpisah, ditautkan melalui sufiks:
suffix={ }:{UNIX- }
=history:{suffix}
=delivery:{suffix}
Pesanan menentukan kapan kurir akan melihat riwayat satu kali, untuk mengubah jawaban atas pertanyaan "Siapa yang menerimanya dan bagaimana" setelah pengiriman selesai.
"Visi" dari kurir bekerja melalui langganan
DELkunci acara dalam formulir delivery:*.
Saat waktu pengiriman tiba, Redis menghapus kunci pesanan, setelah itu kurir mulai memprosesnya.
Karena ada beberapa kurir, maka kemungkinan besar terjadinya persaingan pada tahap perubahan sejarah.
Anda dapat menghindarinya dengan mendefinisikan operasi yang sesuai secara atom - di Redis, ini dilakukan melalui skrip LUA.
Detail implementasi akan dibahas secara rinci di bab berikutnya. Sekarang penting untuk mendapatkan gambaran yang jelas tentang solusi secara keseluruhan, yang dapat dibantu oleh gambar di bawah ini.
Status pelacakan
Klien dapat melacak status pengiriman melalui kunci riwayat, yang dihasilkan oleh metode API terpisah dari layanan yang sedang dikembangkan sebelum pesan diantrekan (seperti nomor pesanan yang dibuat oleh Yandex.Food di awal).
Setelah kunci dibuat, pelacak dengan batas waktu digantung di atasnya (opsional dan juga dengan metode terpisah), yang akan memantau jumlah perubahan riwayat oleh kurir (
SETperistiwa). Hanya sekarang pesan tersebut sedang dalam antrian.
Jika kurir tidak menemukan kontak penerima di domainnya - saluran komunikasi, maka dia memicu kejadian buatan
SETmelalui perintah PUBLISH, dengan demikian menunjukkan bahwa dia "baik-baik saja" dan tidak perlu menunggu lebih lama lagi.
Mengapa mengacaukan acara di Redis jika Anda memiliki RabbitMQ dan Celery
Setidaknya ada lima alasan obyektif untuk ini:
Sistem notifikasi (encompassing) diimplementasikan dalam bentuk sekumpulan layanan mikro. Demi kenyamanan, antarmuka, metode untuk menginisialisasi lapisan data, teks kesalahan, serta beberapa blok logika berulang telah dipindahkan ke pustaka
core, yang, pada gilirannya, bergantung pada: gino(asyncio wrapper SQLAlchemy), aioredisdan aiohttp.
Anda dapat melihat entitas yang berbeda dalam kode, misalnya
User, Contactatau Allegiance. Koneksi di antara mereka disajikan pada diagram di bawah ini, deskripsi singkat ada di bawah spoiler.
Tentang entitas ~ 3 menit
— .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
Menghasilkan kunci sejarah
pengiriman / penangan / history_key / get - GitHub
Antre
pengiriman / penangan / antrian / put - GitHub
Note:
- Komentar 171: 174;
- Bahwa semua manipulasi dengan Redis [164: 179] dibungkus dalam sebuah transaksi.
Penampakan kurir [94: 117]
inti / pengiriman - GitHub
Memperbarui riwayat oleh kurir
core / redis_lua - GitHub
Instruksi [48:60] tidak mengubah daftar kosong menjadi kamus (
[] -> {}), karena kebanyakan bahasa pemrograman, termasuk CPython, menafsirkannya secara berbeda dari LUA.
ISS: Izinkan diferensiasi array dan objek untuk serialisasi objek kosong yang tepat - GitHub
Pelacak
pengiriman / penangan / track / post - GitHub - implementasi.
hubungkan / telegram / handlers / select - GitHub [101: 134] - contoh penggunaan di antarmuka pengguna.
Kurir
Setiap pengiriman dari
task_stream(@Sight Couriers) ditangani dalam coroutine asyncio terpisah.
Strategi umum untuk menangani batasan waktu API adalah sebagai berikut: kami tidak menghitung RPS (permintaan per detik), tetapi kami dengan benar / bereaksi / menanggapi tanggapan berdasarkan jenis
http.TooManyRequests.
Jika antarmuka mengimplementasikan, selain global (untuk aplikasi), batas waktu kustom, maka mereka diproses dalam urutan antrian, mis. pertama kami kirim ke semua orang yang kami bisa dan baru kemudian kami mulai menunggu, jika tidak terlalu lama.
Telegram
kurir / telegram - GitHub
Seperti disebutkan sebelumnya, antarmuka MTProto Telegram mengungguli mitra HTTP-nya dalam hal stabilitas dan ukuran dokumentasi. Untuk berinteraksi dengannya, kami akan menggunakan solusi yang sudah jadi yaitu LonamiWebs / Telethon .
Berhubungan dengan
courier / vk - GitHub
VKontakte API mendukung pengiriman massal dengan meneruskan daftar pengenal ke metode messages.send (tidak lebih dari seratus), dan juga memungkinkan Anda untuk "merekatkan" hingga dua puluh lima
messages.senddalam satu eksekusi , yang memberi kami 2.500 pesan per panggilan.
Fakta penasaran
API,
execute , .
Akhir
Dalam pekerjaan ini, metode pengorganisasian sistem peringatan massal multisaluran diusulkan. Solusi yang dihasilkan memenuhi permintaan (@ Persyaratan utama untuk layanan pengiriman surat) dari sebagian besar pihak yang berkepentingan, dan juga mengasumsikan kemungkinan perluasan.
Kerugian utama adalah efek api & lupakan Pub / Sub, yaitu. jika penghapusan kunci pesanan diperlukan pada saat salah satu kurir sakit, maka tidak ada yang akan menerima apa pun di domain yang sesuai, yang, bagaimanapun, akan tercermin dalam riwayat.