Semua yang baru sudah lama terlupakan!
Sekarang banyak orang menulis berbagai bot yang berkomunikasi dengan pengguna di IM dan entah bagaimana membantu pengguna hidup.

Jika Anda melihat kode banyak bot, maka biasanya turun ke pola yang sama:
- pesan tiba
- itu diteruskan ke penangan pesan pengguna (
callback
)
Ini umumnya merupakan cara universal untuk menulis bot. Cocok untuk obrolan satu orang dan untuk bot yang terhubung dengan grup. Dengan metode ini, semuanya baik-baik saja kecuali satu: kode bot bahkan sederhana seringkali cukup membingungkan.
Mari kita coba mengungkapnya.
Saya akan mulai dengan disclaimer:
- Apa yang dijelaskan dalam artikel ini cocok untuk bot dari
<->
tipe <->
. - Kode dalam artikel ini adalah kode sketsa. Ditulis khusus untuk artikel ini dalam 15 menit. Jadi jangan menilai dengan ketat.
- Saya menggunakan pendekatan serupa dalam bisnis: dengan penyeimbangan beban. Tetapi, sayangnya, kode produksi saya memiliki banyak ketergantungan infrastruktur dan sangat mudah untuk tidak mempublikasikannya. Oleh karena itu, sketsa ini digunakan dalam artikel. Saya akan menyentuh pada isu-isu pengembangan paradigma (saya akan menjelaskan di mana dan bagaimana kami berkembang).
Nah, sekarang mari kita pergi.
Sebagai dukungan, pertimbangkan perpustakaan asinkron aiogram, python3.7 + . Tautan tersebut memiliki contoh bot gema sederhana.
Saya akan menyalinnya di sini:
Lihat kode """ This is a echo bot. It echoes any incoming text messages. """ import logging from aiogram import Bot, Dispatcher, executor, types API_TOKEN = 'BOT TOKEN HERE'
Kami melihat bahwa organisasi bot bersifat tradisional. Setiap kali pengguna menulis sesuatu kepada kami, fungsi handler dipanggil.
Apa yang salah dengan paradigma ini?
Bahwa fungsi pawang untuk mengimplementasikan dialog kompleks harus mengembalikan keadaannya dari beberapa jenis penyimpanan pada setiap panggilannya.
Jika Anda melihat sebagian besar bot yang mendukung beberapa jenis bisnis (misalnya, mempekerjakan), mereka mengajukan pertanyaan kepada pengguna 1..N, maka mereka melakukan sesuatu berdasarkan hasil dari pertanyaan ini (misalnya, mereka menyimpan profil dalam database).
Jika dimungkinkan untuk menulis bot dalam gaya tradisional (bukan gaya dering), maka mungkin untuk menyimpan data pengguna secara langsung di tumpukan.
Mari kita coba melakukannya.
Saya membuat sketsa sketsa modul, menghubungkan yang dapat Anda gunakan dengan perpustakaan ini:
Sedikit penjelasan:
Kelas ChatDispatcher
dengan parameter berikut:
- fungsi sharding dari pesan yang masuk (mengapa disebut sharding - nanti, saat kita menyentuh banyak muatan). Fungsi mengembalikan nomor unik yang menunjukkan dialog. Dalam contoh, ini hanya mengembalikan ID pengguna.
- fungsi yang akan melakukan pekerjaan layanan obrolan.
- Nilai batas waktu untuk ketidakaktifan pengguna.
Deskripsi Pekerjaan:
- Menanggapi pesan pertama pengguna, tugas asinkron dibuat yang akan melayani dialog. Tugas ini akan bekerja sampai dialog selesai.
- Untuk menerima pesan dari pengguna, kami memintanya secara eksplisit. Contoh obrolan
echo
:
async def chat(get_message): message = await get_message() await message.answer(message.text)
- Kami menanggapi pesan saat perpustakaan menawarkan kami (
message.answer
).
Mari kita coba menulis bot dalam paradigma ini
Contoh kode lengkap di sini Contoh bot tertulis - ia hanya menambahkan beberapa angka dan menghasilkan hasilnya.
Hasilnya terlihat seperti ini:

Baiklah, sekarang mari kita melihat lebih dekat pada kode. Contoh tidak boleh mengajukan pertanyaan.
Integrasi dengan sketsa kami dilakukan di handler standar yang kami sebut await chat_dispatcher.handle(message)
. Dan kami menggambarkan chat
di fungsi chat
, saya akan mengulangi kodenya di sini:
async def chat(get_message): try: message = await get_message() await message.answer(' , ') first = await get_message() if not re.match('^\d+$', str(first.text)): await first.answer(' , : /start') return await first.answer(' ') second = await get_message() if not re.match('^\d+$', str(second.text)): await second.answer(' , : /start') return result = int(first.text) + int(second.text) await second.answer(' %s (/start - )' % result) except ChatDispatcher.Timeout as te: await te.last_message.answer('- , ') await te.last_message.answer(' - /start')
Kode layanan obrolan - hanya meminta data satu per satu dari pengguna. Respons pengguna hanya ditumpuk di tumpukan (variabel first
, second
, message
).
Fungsi get_message
dapat mengeluarkan pengecualian jika pengguna tidak memasukkan apa pun selama batas waktu yang ditentukan (dan Anda dapat melewatkan batas waktu itu di tempat yang sama).
Keadaan dialog - terkait langsung dengan nomor baris di dalam fungsi ini. Turun kode, kita bergerak di sepanjang skema dialog . Membuat perubahan pada utas dialog tidak mudah, tetapi sangat sederhana!
Dengan demikian, mesin negara tidak diperlukan. Dalam paradigma ini, Anda dapat menulis dialog yang sangat kompleks dan memahami kode mereka akan jauh lebih sederhana daripada kode dengan callback
.
Kekurangan
Di mana tanpa mereka.
- Untuk setiap pengguna aktif, ada satu tugas-corutin. Rata-rata, satu CPU biasanya melayani sekitar 1000 pengguna, kemudian penundaan dimulai.
- Restart seluruh daemon - mengakhiri semua dialog (dan me-restart-nya).
- Kode [dari contoh] tidak diadaptasi untuk memuat penskalaan dan internasionalisasi.
Jika dengan masalah kedua jelas apa yang harus dilakukan: mencegat sinyal berhenti dan memberi tahu pengguna "Saya memiliki keadaan darurat di sini, tembak, saya akan kembali sedikit nanti." Masalah terakhir itu dapat menyebabkan kesulitan. Mari kita melihatnya:
Memuat skala
Jelas, bot yang dimuat harus diluncurkan pada banyak backend sekaligus. Dengan demikian, mode operasi webHook
akan digunakan.
Jika Anda hanya menyeimbangkan webHook
antara, katakanlah, dua backend, maka jelas Anda perlu entah bagaimana memastikan bahwa pengguna yang sama datang ke coroutine yang sama yang sedang berbicara dengannya.
Kami melakukan ini sebagai berikut.
- Di balancer, parsing JSON dari pesan masuk (
message
) - Pilih ID pengguna dari itu
- Dengan menggunakan pengenal, kami menghitung angka backend (== shard). Misalnya, menggunakan
user_id % Nshards
. - Kami mengarahkan permintaan ke beling.
ID Pengguna - menjadi kunci untuk sharding antara coroutine dari dialog dan dasar untuk menghitung nomor beling backend di penyeimbang.
Kode penyeimbang seperti itu sederhana - ditulis dalam bahasa apa pun dalam 10 menit. Saya tidak akan membawanya.
Kesimpulan
Jika Anda menulis bot dalam paradigma ini, maka Anda dapat dengan mudah mengulang dialog dari satu ke yang lain. Selain itu, yang penting adalah bahwa programmer baru dengan mudah memahami kode dialog yang dibuat seseorang sebelum dia.
Mengapa kebanyakan orang menulis bot dalam arsitektur cincin - saya tidak tahu.
Mereka biasa menulis dalam paradigma seperti itu. Melayani ruang obrolan dalam gaya ini diadopsi di era IRC dan bot untuk itu. Jadi saya tidak berpura-pura sebagai hal baru.
Dan lagi. Jika Anda menggunakan paradigma ini dalam bahasa dengan operator goto
, maka ini akan menjadi contoh indah menggunakan goto
(loop dalam dialog dilakukan dengan indah pada goto
). Sayangnya ini bukan tentang Python.