Einleitend
Hallo Habr! Mein Name ist Boris und in dieser Arbeit werde ich Ihnen meine Erfahrungen beim Entwerfen und Implementieren eines Massenmailing-Dienstes als Teil eines umfassenden Systems zur Benachrichtigung von Schülern an Lehrer (im Folgenden auch als Ada bezeichnet) mitteilen, das ich ebenfalls implementiere.
Hölle
Es ist dann erforderlich, die Anzahl der Unterbrechungen im Bildungsprozess aus folgenden Gründen zu negieren:
- Lehrer möchten keine persönlichen Kontaktdaten weitergeben.
- Die Schüler sind es auch wirklich - sie haben einfach keine große Auswahl;
- Aufgrund der Besonderheiten meiner Alma Mater sind viele Lehrer gezwungen oder bevorzugen es, mobile Geräte ohne Zugang zum Internet zu verwenden.
- Wenn Sie Nachrichten über die Leiter der Gruppen senden, kommt der Effekt eines "beschädigten Telefons" sowie der Faktor "Oh, ich habe vergessen :(" ins Spiel.
Es läuft ungefähr so :
- Der Lehrer über einen der ihm zur Verfügung stehenden Kommunikationskanäle: SMS, Telegramm, SPA-Anwendung - sendet Ada den Text der Nachricht und die Liste der Adressaten;
- Ada sendet die empfangene Nachricht über verschiedene Kommunikationskanäle an alle interessierten * Studenten.
* Der Zugang zum Service erfolgt auf freiwilliger Basis.
Es wird angenommen dass
- Die Gesamtzahl der Benutzer wird zehntausend nicht überschreiten.
- Das Verhältnis Schüler - Lehrer / Mitglied des Amtes für innere Angelegenheiten (Dekane, Gesundheitszentrum, Militärregistrierungsstelle usw.) wird auf einem Niveau von 10: 1 gehalten;
- : « », « ))0» ..
- ;
- , , ;
- ;
- : - - , , .
Diese Arbeit besteht aus fünf Teilen: Einführung, Vorbereitung, Konzeption, Thema und Abschluss.
Sie können den vorbereitenden Teil sicher überspringen, wenn Sie mit der Redis-Interpretation des Pub / Sub-Musters sowie den Mechanismen von Ereignissen, LUA-Skripten und dem Umgang mit veralteten Schlüsseln vertraut sind. Außerdem ist es äußerst wünschenswert, zumindest eine Vorstellung von der Microservice-Architektur der Software zu haben.
Im Betreff wird der Code in Python überprüft, aber ich glaube, dass es genügend Informationen gibt, damit Sie so etwas auf alles schreiben können.
Vorbereitende
Sehr rau und sehr abstrakt ~ 5 Minuten
Redis — [BSD 3-clause] , «-» ().
, .
, -, .
( ).
, , , LUA 5.1.
, .
, -, .
( ).
, , , LUA 5.1.
Detail und aus erster Hand ~ 15 Minuten
- 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. , . — ;
- . , .
Konzeptionell
Naiver Ansatz
Die naheliegendste Lösung , die Sie sich vorstellen können: mehr Versandmethoden (
send_vk, send_telegramusw.) und eine Prozedur , die sie mit den erforderlichen Argumenten nennen.
Erweiterungsproblem
Wenn wir eine neue Übermittlungsmethode hinzufügen möchten, müssen wir den vorhandenen Code ändern. Dies ist eine Einschränkung der Softwareplattform.
Stabilitätsproblem
Eine der Methoden ist fehlerhaft = der gesamte Dienst ist fehlerhaft.
Angewandtes Problem
Die APIs verschiedener Kommunikationskanäle unterscheiden sich hinsichtlich der Interaktion erheblich voneinander. Beispielsweise unterstützt VKontakte Massenmailings, jedoch nicht mehr als Hunderte von Benutzern pro Anruf. Telegramm existiert nicht, erlaubt aber mehr Anrufe pro Sekunde.
Die VK-API funktioniert nur über HTTP. Telegramm hat ein HTTP-Gateway, ist jedoch weniger stabil als MTProto und weniger gut dokumentiert.
Es gibt viele solche Unterschiede: maximale Nachrichtenlänge
random_id, Interpretation und Fehlerbehandlung usw. usw.
Wie gehe ich damit um?
Es wurde beschlossen, den Prozess des Einreihens von Nachrichten und des Sendens von Prozessen (im Folgenden als Kuriere bezeichnet) auf organisatorischer Ebene zu trennen, damit erstere nicht einmal die Existenz der letzteren vermuten und umgekehrt, und Redis als Bindeglied zwischen ihnen fungieren würde.
Unverständlich? Ein Essen bestellen!
In der Zwischenzeit warten Sie - lassen Sie mich Ihnen meine Interpretation dieser edlen Handlung vorstellen, beginnend mit dem Entwurf und endend mit der geschlossenen Tür hinter dem Kurier.
- Sie klicken auf die große gelbe Schaltfläche "Bestellen".
- Yandex.Food findet einen Kurier, informiert das Restaurant über die ausgewählten Artikel und sendet Ihnen die Bestellnummer zurück, um die Unsicherheit der Erwartungen zu verringern.
- Nach Beendigung des Garvorgangs aktualisiert das Restaurant den Bestellstatus und gibt das Essen an den Kurier weiter.
- Der Kurier wiederum gibt Ihnen das Essen und markiert die Bestellung als erledigt.
Guten Appetit!
Zurück zum Design
Es ist möglich, dass das im vorherigen Absatz angegebene Modell nicht vollständig der Realität entspricht, aber sie war es, die die Grundlage für die entwickelte Lösung bildete.
Die mit der Bestellnummer verknüpften Daten werden als Verlauf bezeichnet . Sie können die folgenden Fragen jederzeit beantworten:
- Wer hat gesendet?
- Was er geschickt hat;
- Woher;
- An wen;
- Wer hat es bekommen und wie.
Der Verlauf wird zusammen mit der Reihenfolge als zwei separate Redis-Schlüssel erstellt, die über ein Suffix verknüpft sind:
suffix={ }:{UNIX- }
=history:{suffix}
=delivery:{suffix}
Die Bestellung bestimmt, wann die Kuriere die Historie einmal sehen, um die Antwort auf die Frage „Wer hat sie erhalten und wie“ nach Abschluss des Versands zu ändern.
Die "Vision" der Kuriere funktioniert durch ein Abonnement der Ereignisschlüssel
DELim Formular delivery:*.
Wenn der Moment der Lieferung kommt, löscht Redis den Bestellschlüssel, woraufhin die Kuriere mit der Verarbeitung beginnen.
Da es mehrere Kuriere gibt, besteht im Stadium des Geschichtswechsels eine hohe Wahrscheinlichkeit eines Wettbewerbs.
Sie können dies vermeiden, indem Sie die entsprechende Operation atomar definieren. In Redis erfolgt dies über LUA-Skripte.
Implementierungsdetails werden im nächsten Kapitel ausführlich besprochen. Jetzt ist es wichtig, sich ein klares Bild von der Gesamtlösung zu machen, was anhand der folgenden Abbildung unterstützt werden kann.
Tracking-Status
Der Client kann den Übermittlungsstatus über den Verlaufsschlüssel verfolgen, der von einer separaten API-Methode des zu entwickelnden Dienstes generiert wird, bevor die Nachricht in die Warteschlange gestellt wird (genau wie die Bestellnummer zu Beginn von Yandex.Eda generiert wird).
Nachdem der Schlüssel generiert wurde, wird ein Tracker mit einer Zeitüberschreitung (optional und auch nach einer separaten Methode) daran gehängt, der die Anzahl der Verlaufsänderungen durch Kuriere (
SETEreignisse) überwacht . Erst jetzt wird die Nachricht in die Warteschlange gestellt.
Wenn der Kurier keine Empfängerkontakte in seiner Domäne - dem Kommunikationskanal - findet, löst er
SETüber den Befehl ein künstliches Ereignis aus PUBLISH, wodurch angezeigt wird, dass er „in Ordnung“ ist und nicht länger warten muss.
Warum sollten Sie sich mit Ereignissen in Redis anlegen, wenn Sie RabbitMQ und Sellerie haben?
Dafür gibt es mindestens fünf objektive Gründe:
Das Benachrichtigungssystem (umfassend) ist in Form einer Reihe von Mikrodiensten implementiert. Der Einfachheit halber wurden Schnittstellen, Methoden zum Initialisieren von Datenschichten, Fehlertext sowie einige Blöcke repetitiver Logik in die Bibliothek verschoben
core, die wiederum auf folgenden ginoElementen beruht: (Asyncio-Wrapper SQLAlchemy) aioredisund aiohttp.
Sie können beispielsweise verschiedene Entitäten im Code sehen
User, Contactoder Allegiance. Die Verbindungen zwischen ihnen sind in der folgenden Abbildung dargestellt, eine kurze Beschreibung finden Sie unter dem Spoiler.
Über Entitäten ~ 3 Minuten
— .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
: , , . ., .
, : , Telegram, . .
[allegiance].
[supergroup].
[ownership] .
Generieren eines Verlaufsschlüssels
Lieferung / Handler / history_key / get - GitHub
Warteschlange
Lieferung / Handler / Warteschlange / Put - GitHub
Hinweis:
- Comment 171: 174;
- Dass alle Manipulationen mit Redis [164: 179] in eine Transaktion eingebunden sind.
Anblick der Kuriere [94: 117]
Kern / Lieferung - GitHub
Aktualisierung der Geschichte durch Kuriere
core / redis_lua - GitHub Die
Anweisungen [48:60] konvertieren keine leeren Listen in Wörterbücher (
[] -> {}), da die meisten Programmiersprachen, einschließlich CPython, sie anders interpretieren als LUA.
ISS: Ermöglichen Sie die Unterscheidung von Arrays und Objekten für eine ordnungsgemäße Serialisierung leerer Objekte - GitHub
Tracker
Lieferung / Handler / Track / Post - GitHub - Implementierung.
connect / telegram / handlers / select - GitHub [101: 134] - Beispiel für die Verwendung in der Benutzeroberfläche.
Kuriere
Jede Lieferung von
task_stream(@Sight Couriers) erfolgt in einer separaten Asyncio-Coroutine.
Die allgemeine Strategie für den Umgang mit den zeitlichen Einschränkungen von APIs lautet wie folgt: Wir zählen keine RPS (Anforderungen pro Sekunde), aber wir reagieren korrekt auf Antworten nach Typ
http.TooManyRequests.
Wenn die Schnittstelle zusätzlich zu globalen (für die Anwendung) benutzerdefinierten Zeitlimits implementiert, werden diese in der Reihenfolge der Warteschlange verarbeitet, d. H. Zuerst senden wir an alle, die wir können, und erst dann beginnen wir zu warten, wenn auch nicht sehr lange.
Telegramm
Kurier / Telegramm - GitHub
Wie bereits erwähnt, übertrifft die MTProto-Schnittstelle von Telegram das HTTP-Gegenstück in Bezug auf Stabilität und Dokumentationsgröße. Um damit zu interagieren, werden wir eine vorgefertigte Lösung verwenden, nämlich LonamiWebs / Telethon .
In Kontakt mit
courier / vk - Die GitHub
VKontakte-API unterstützt Massenmailings, indem eine Liste von Kennungen an die messages.send- Methode übergeben wird (nicht mehr als hundert). Außerdem können Sie bis zu fünfundzwanzig Nachrichten
messages.sendin einer Ausführung "kleben" , wodurch wir 2500 Nachrichten pro Anruf erhalten.
Merkwürdige Tatsache
API,
execute , .
Das endgültige
In der vorliegenden Arbeit wird ein Verfahren zum Organisieren eines Mehrkanal-Massenwarnsystems vorgeschlagen. Die resultierende Lösung erfüllt die Anforderung (@ Schlüsselanforderungen für den Mailing-Service) der meisten interessierten Parteien und geht auch von der Möglichkeit einer Erweiterung aus.
Der Hauptnachteil ist der Fire & Forget Pub / Sub-Effekt, d.h. Wenn die Löschung des Bestellschlüssels zum Zeitpunkt einer Krankheit des Kuriers erforderlich ist, erhält niemand etwas in der entsprechenden Domäne, was sich jedoch in der Geschichte widerspiegelt.