Kardinalstatistiken wie UV usw. |
|
Mein Verständnis davon entspricht in etwa dem dieses Interviewers! !
Die Datenstruktur von Redis kann die Ablaufzeit (TTL) des Schlüssels über EXPIRE-Schlüsselsekunden festlegen. Wir sind es auch gewohnt zu glauben, dass der Redis-Schlüssel automatisch gelöscht wird, wenn er abläuft. Offensichtlich ist diese Idee nicht richtig. Das Design von Redis berücksichtigt umfassende Faktoren wie Leistung/Speicher und entwirft eine Reihe von Ablaufstrategien.
- Aktives Löschen (Lazy Deletion)
- Passives Löschen (periodische Strategie)
Aktives Löschen (Lazy Deletion) bedeutet, dass beim Zugriff auf den Schlüssel zunächst geprüft wird, ob der Schlüssel abgelaufen ist, und wenn er abläuft, ist dies der Fall aktiv gelöscht.
Passives Löschen (periodische Strategie) bezieht sich darauf, dass der Redis-Server regelmäßig und zufällig die Ablaufzeit des Schlüssels testet. Wenn er abläuft, wird er passiv gelöscht. Das Vorhandensein einer passiven Löschung ist wichtig, da einige Schlüssel abgelaufen sind und nie mehr darauf zugegriffen werden kann. Wenn sie auf aktives Löschen angewiesen sind, belegen sie dauerhaft den Speicher.
Um leistungsstarke Dienste sicherzustellen, löscht Redis passiv eine gierige Strategie/einen probabilistischen Algorithmus. Die spezifische Strategie lautet wie folgt:
Mein Verständnis davon entspricht in etwa dem dieses Interviewers! !
In verteilten Szenarien umfassen unsere gängigen Lösungen für verteilte Sperren (wenn Sie wissen, wie es geht, können Sie die anderen beiden hierher bringen, wenn nicht, vermasseln Sie sich nicht!):
Basierend auf verteilten Sperren implementiert zum Datenbanksperrmechanismus
Wenn sich Redis in einer eigenständigen Umgebung befindet, können wir verteilte Sperren über die von Redis bereitgestellten atomaren Anweisungen implementieren.
Schlüsselwert festlegen [EX Sekunden] [PX Millisekunden] [NX | XX]Um dies zu verhindern Ein Zusatz Die Sperre wurde von B gelöscht. Beim Sperren kann das Sperrtag des Clients übergeben werden. Das Entsperren ist nur zulässig, wenn das vom Client übergebene Tag mit dem Sperrtag übereinstimmt. Redis bietet eine solche Funktion jedoch nicht Wir können nur Lua-Skripte zur Verarbeitung übergeben, da Lua-Skripte die atomare Ausführung mehrerer Anweisungen sicherstellen können. Schließlich müssen wir das Problem des Sperrzeitlimits berücksichtigen. Wenn der Client die Sperre niemals aufhebt, kann die Sperre nur garantieren, dass sie innerhalb des angegebenen Zeitlimits nicht aufgehoben wird Diese Situation kann wie folgt optimiert werden:
Versuchen Sie, keine langen Aufgaben in der verteilten Redis-Sperre auszuführen, und führen Sie den Code so eng wie möglich aus. Genau wie bei der synchronisierten Optimierung in einer einzelnen JVM-Sperre können wir die Optimierung des Sperrbereichs in Betracht ziehen
Der Umgang mit dieser Situation sollte auf diese Weise berücksichtigt werden. Eine direkte Redis-Master-Slave-Synchronisation kann das Problem des Datenverlusts auf keinen Fall lösen. Daher erwägen wir, die Sperranwendung von einer Redis- auf mehrere eigenständige Redis-Sperranwendungen umzustellen. Nur die meisten Anwendungen sind erfolgreich. Diese Idee ist RedLock. RedLock verwendet mehrere Redis-Instanzen und sie sind unabhängig voneinander. Beim Sperren sendet der Client Sperranweisungen an alle Knoten Das Sperren ist erfolgreich. Beim Aufheben der Sperre müssen Sie Del-Anweisungen an alle Knoten senden, um die Sperre aufzuheben.
Obwohl Red Lock das Problem der Master-Slave-Synchronisation löst, bringt es neue komplexe Probleme mit sich:
- Das erste Problem ist die Taktabweichung
- Das zweite Problem besteht darin, dass der Client zu unterschiedlichen Zeiten erfolgreich eine Sperre von verschiedenen Redis-Servern beantragt
In RedLock ist es also notwendig, die minimale effektive Dauer der angewendeten Sperre zu berechnen. Angenommen, der Client beantragt die Sperre erfolgreich, der Zeitpunkt, zu dem der erste Schlüssel erfolgreich festgelegt wurde, ist TF, der Zeitpunkt, zu dem der letzte Schlüssel erfolgreich festgelegt wurde, ist TL, das Sperrzeitlimit ist TTL und die Taktdifferenz zwischen verschiedenen Prozessen ist CLOCK_DIFF. dann beträgt die Mindestgültigkeit der Sperre:
TIME = TTL – (TF-TL) – CLOCK_DIFF
Verwendung von Redis zur Implementierung verteilter Sperren, was untrennbar mit Serverausfallzeiten und anderen Nichtverfügbarkeitsproblemen verbunden ist für RedLock hier, auch wenn es mehrere gibt Wenn der Server eine Sperre beantragt, müssen wir auch überlegen, was zu tun ist, nachdem der Server ausgefallen ist. Die offizielle Empfehlung lautet, AOF-Persistenz zu verwenden.
Allerdings kann die AOF-Persistenz nur für normale SHUTDOWN-Anweisungen neu gestartet und wiederhergestellt werden. Bei einem Stromausfall kann es jedoch dazu kommen, dass die Sperrdaten von der letzten Persistenz bis zum Stromausfall verloren gehen. Es kann vorkommen, dass die verteilte Sperrsemantik falsch ist. Um diese Situation zu vermeiden, lautet die offizielle Empfehlung daher, dass der Redis-Dienst nach dem Neustart innerhalb einer maximalen Client-TTL nicht verfügbar sein wird (es wird kein Sperranwendungsdienst bereitgestellt). Dies kann das Problem zwar lösen, aber es Es ist offensichtlich, dass dies definitiv die Leistung des Redis-Servers beeinträchtigen wird, und wenn dies bei den meisten Knoten passiert, wird das System weltweit nicht verfügbar sein.
Das ist mein Verständnis des Interviewers! !
Redis ist sehr schnell, weil Redis-Daten im Speicher gespeichert sind und alle Daten verloren gehen, wenn der Server ausfällt oder ausgeschaltet wird Es werden zwei Mechanismen verwendet, um sicherzustellen, dass nicht alle Redis-Daten aufgrund von Fehlern verloren gehen. Dieser Mechanismus wird als Persistenzmechanismus von Redis bezeichnet.
Redis verfügt über zwei Persistenzmechanismen:
- RDB (Redis Data Base) Speicher-Snapshot
- AOF (Append Only File) inkrementelles Protokoll
RDB (Redis DataBase) bezieht sich auf den angegebenen Datensatz-Snapshot im Speicher wird innerhalb eines Zeitintervalls in Form eines Speicher-Snapshots (binäre Serialisierungsform der Speicherdaten) auf die Festplatte geschrieben. Jedes Mal wird ein Snapshot von Redis generiert, um die Daten vollständig zu sichern.
Vorteile:
- Kompakter Speicher, spart Speicherplatz
- Die Wiederherstellungsgeschwindigkeit ist sehr hoch
- Geeignet für Vollsicherungs- und Vollkopieszenarien, wird häufig für die Notfallwiederherstellung verwendet (im Verhältnis zu Datenintegritäts- und Konsistenzanforderungen). Niedriger Gelegenheiten)
Nachteile:
- Es ist leicht, Daten zu verlieren, und es ist leicht, die Daten zu verlieren, die zwischen zwei Snapshots auf dem Redis-Server geändert wurden.
- RDB führt über den Fork-Unterprozess eine vollständige Sicherung des Speicher-Snapshots durch. Dies ist ein aufwändiger Vorgang und die Kosten für die häufige Ausführung sind hoch.
- Obwohl der untergeordnete Fork-Prozess den Speicher teilt, kann er sich bei einer Änderung des Speichers während der Sicherung auf maximal das Zweifache vergrößern.
RDB-ausgelöste Regeln sind in zwei Kategorien unterteilt, nämlich manuelle Auslösung und automatische Auslösung:
Automatische Auslösung:
Manueller Auslöser:
AOF (Append Only File) zeichnet alle Anweisungen (Schreibvorgänge), die den Speicher ändern, in einer separaten Protokolldatei auf und führt die AOF-Datei während des Neustarts des Redis-Befehls aus, um Daten wiederherzustellen . AOF kann das Problem der Echtzeit-Datenpersistenz lösen und ist die gängige Persistenzlösung im aktuellen Redis-Persistenzmechanismus (wir werden später über Hybridpersistenz nach 4.0 sprechen).
Vorteile:
- Die Datensicherung ist vollständiger, die Wahrscheinlichkeit eines Datenverlusts ist geringer und eignet sich für Szenarien mit hohen Anforderungen an die Datenintegrität.
- Die Protokolldatei ist lesbar, AOF ist besser bedienbar und die Protokolldatei kann betriebene Reparatur
Nachteile:
- AOF-Protokolldatensätze werden im Laufe des Langzeitbetriebs allmählich größer und die Wiederherstellung ist sehr zeitaufwändig. AOF-Protokolle müssen regelmäßig verkleinert werden (Details später)
- Die Wiederherstellungsgeschwindigkeit beträgt relativ langsam
- Häufige synchrone Schreibvorgänge führen zu Leistungsdruck
Das AOF-Protokoll liegt in Form einer Datei vor. Wenn das Programm in die AOF-Protokolldatei schreibt, wird der Inhalt tatsächlich in einen vom Kernel zugewiesenen Speicher geschrieben Für den Dateideskriptor schreibt der Kernel dann die Daten im Puffer asynchron auf die Festplatte. Wenn der Server abstürzt, bevor die Daten im Puffer auf die Festplatte zurückgeschrieben werden können, gehen die Daten verloren.
Also ruft Redis fsync(int fid) auf, das von glibc des Linux-Betriebssystems bereitgestellt wird, um den Inhalt der angegebenen Datei aus dem Kernel-Puffer zurück auf die Festplatte zu erzwingen, um sicherzustellen, dass die Daten im Puffer nicht verloren gehen. Allerdings handelt es sich hierbei um einen E/A-Vorgang, der im Vergleich zur Leistung von Redis sehr langsam ist und daher nicht häufig ausgeführt werden kann.
Es gibt drei Flush-Puffer-Konfigurationen in der Redis-Konfigurationsdatei:
appendfsync immer
Jeder Redis-Schreibvorgang wird in das AOF-Protokoll geschrieben. Theoretisch kann das Linux-Betriebssystem diese Konfiguration nicht verarbeiten, da die Parallelität von Redis die vom Linux-Betriebssystem bereitgestellte maximale Aktualisierungsfrequenz bei weitem übersteigt, selbst wenn es relativ wenige Redis-Schreibvorgänge gibt Diese Konfiguration ist auch sehr leistungsintensiv, da sie E/A-Vorgänge umfasst. Daher verwendet diese Konfiguration in dieser Redis-Konfiguration grundsätzlich nicht jede Sekunde appendfsync, um die Daten im Puffer in der AOF-Datei zu aktualisieren Datei Die Standardstrategie ist mit einem Kompromiss zwischen Leistung und Datenintegrität kompatibel. Bei dieser Konfiguration gehen die Daten theoretisch in etwa einer Sekunde verloren
appendfsync nein
Der Redis-Prozess aktualisiert den Puffer nicht aktiv Die AOF-Datei wird jedoch direkt zur Beurteilung an das Betriebssystem übergeben. Dieser Vorgang wird nicht empfohlen, da die Wahrscheinlichkeit eines Datenverlusts sehr hoch ist.
Als ich zuvor die Mängel von AOF erwähnte, sagte ich, dass AOF eine Form des Anhängens von Protokollen zum Speichern von Redis-Schreibanweisungen ist. Dies führt zu einer großen Anzahl redundanter Befehlsspeicher, wodurch die AOF-Protokolldatei sehr groß wird Es nimmt nur Speicher in Anspruch, führt aber auch dazu, dass die Wiederherstellung sehr langsam ist. Daher bietet Redis einen Umschreibemechanismus, um dieses Problem zu lösen. Nachdem der AOF-Persistenzmechanismus von Redis das Umschreiben durchgeführt hat, speichert er nur den minimalen Befehlssatz zum Wiederherstellen der Daten. Wenn wir ihn manuell auslösen möchten, können wir die folgenden Anweisungen verwendenbgrewriteaof
Beim Umschreiben nach Redis 4.0 werden RDB-Snapshots und AOF-Anweisungen gespleißt zusammen Auf diese Weise ist der Header der AOF-Datei die binäre Form der Daten des RDB-Snapshots und das Ende ist die Anweisung des Schreibvorgangs, der nach der Generierung des Snapshots ausgeführt wurde.
Da das Umschreiben von AOF-Dateien einen gewissen Einfluss auf die Leistung von Redis hat, kann Redis nicht zufällig zwei Indikatoren für das automatische Umschreiben von AOF bereitstellen auto-aof-rewrite-percentage 100: bezieht sich darauf, wann der Speicher der Datei das Doppelte des ursprünglichen Speichers erreicht
auto-aof-rewrite-min-size 64 MB: Bezieht sich auf die minimale Speichergröße für das Umschreiben der Datei
Darüber hinaus Die meisten Nutzungsszenarien nach Redis 4.0 verwenden nicht RDB oder AOF allein als Persistenzmechanismus, sondern berücksichtigen die Vorteile beider. Lassen Sie uns abschließend die beiden zusammenfassen.
Es wird empfohlen, beides zu aktivierenWenn die Daten nicht sensibel sind, können Sie RDB allein verwenden.
Es wird nicht empfohlen, AOF allein zu verwenden, da Fehler auftreten können.
Wenn Sie nur reine Daten verwenden Speicher-Caching, man kann es überhaupt nicht verwenden-
-
- Mein Verständnis davon ist ungefähr das gleiche wie das dieses Interviewers! !
- Redis ist eine Schlüsselwertdatenbank, die auf Speicherspeicherung basiert. Wir wissen, dass der Speicherplatz zwar schnell, aber klein ist. Wenn der physische Speicher die Obergrenze erreicht, wird das System sehr langsam ausgeführt Speicher von Redis. Beim Erreichen des festgelegten Schwellenwerts wird das Speicherrecycling ausgelöst:
noeviction: Wenn das Speicherlimit erreicht ist und der Client versucht, einen Befehl auszuführen, der dazu führen kann Wenn mehr Speicher verwendet wird, wird ein Fehler zurückgegeben. Vereinfacht gesagt sind Lesevorgänge weiterhin zulässig, das Schreiben neuer Daten ist jedoch nicht zulässig. del (Lösch)-Anfragen sind .
allkeys-lru: Eliminieren Sie alle Schlüssel mithilfe des LRU-Algorithmus (Least Latest Used – Least Latest Used). -
allkeys-random:
Entfernen Sie zufällig alle Schlüssel Schlüssel mit festgelegter Ablaufzeit werden durch den LRU-Algorithmus (Least Recent Used – Least Latest Used) eliminiert. Dadurch wird sichergestellt, dass Daten, für die keine Ablaufzeit festgelegt ist und die beibehalten werden müssen, nicht zur Eliminierung ausgewählt werden -
volatile-random: Nach dem Zufallsprinzip aus allen Schlüsseln mit festgelegter Ablaufzeit eliminieren -
volatile-ttl:
Vergleichen Sie aus allen Schlüsseln mit festgelegter Ablaufzeit den Wert der verbleibenden Ablaufzeit TTL des Schlüssels, TTL Je kleiner die Größe , das erste wird eliminiert
volatile-lfu: Verwenden Sie den LFU-Eliminierungsalgorithmus für Schlüssel mit Ablaufzeit -
allkeys-lfu:
Verwenden Sie den LFU-Eliminierungsalgorithmus für alle Schlüssel -
Es gibt zwei davon Diese Strategien Einer der wichtigeren Algorithmen ist LRU, der den zuletzt verwendeten Schlüssel eliminiert. Redis verwendet jedoch einen ungefähren LRU-Algorithmus, der die in letzter Zeit am seltensten verwendeten Schlüssel nicht vollständig eliminiert, aber die Gesamtgenauigkeit kann garantiert werden.
Der ungefähre LRU-Algorithmus ist sehr einfach. Im Redis-Schlüsselobjekt werden 24 Bits hinzugefügt, um den Systemzeitstempel des letzten Zugriffs zu speichern Der Speicher erreicht den maximalen Speicher, wenn der Schlüssel zufällig ausgewählt wird. Vergleichen Sie den letzten im Schlüsselobjekt aufgezeichneten Zugriffszeitstempel und entfernen Sie den ältesten Schlüssel unter den fünf Schlüsseln. Wiederholen Sie diesen Vorgang Schritt.
Wenn in Redis 3.0 maxmemory_samples auf 10 gesetzt ist, kommt der ungefähre LRU-Algorithmus von Redis dem echten LRU-Algorithmus sehr nahe, aber das Festlegen von maxmemory_samples auf 10 verbraucht aufgrund der jedes Mal abgetasteten Beispieldaten offensichtlich mehr CPU-Rechenzeit als das Festlegen von maxmemory_samples auf 5 Wenn es zunimmt, erhöht sich auch die Berechnungszeit.
Der LRU-Algorithmus von Redis3.0 ist genauer als der LRU-Algorithmus von Redis2.8, da Redis3.0 einen Eliminierungspool mit der gleichen Größe wie maxmemory_samples hinzufügt. Jedes Mal, wenn ein Schlüssel entfernt wird, wartet er zunächst darauf, gelöscht zu werden Im Eliminierungspool werden die Schlüssel verglichen und schließlich der älteste Schlüssel eliminiert. Tatsächlich werden die zur Eliminierung ausgewählten Schlüssel erneut zusammengestellt und verglichen, und der älteste Schlüssel wird eliminiert.
LRU weist einen offensichtlichen Mangel auf. Wenn der Benutzer noch nie auf einen Schlüssel zugegriffen hat, wird dies im LRU-Algorithmus berücksichtigt . LFU (Least Frequently Used) ist ein in Redis 4.0 eingeführter Eliminierungsalgorithmus, der Schlüssel durch Vergleich der Zugriffshäufigkeit eliminiert.
Der Unterschied zwischen LRU und LFU:
- LRU -> Zuletzt verwendet, verglichen mit der Zeit des letzten Zugriffs
- LFU -> Häufig verwendet, basierend auf der Zugriffshäufigkeit des Schlüssels
Im LFU-Modus ist das 24-Bit-LRU-Feld des Redis-Objekt-Headers zur Speicherung in zwei Segmente unterteilt. Die oberen 16 Bit speichern ldt (Last Decrement Time) und die unteren 8 Bit speichern logc (Logistic Counter). Die oberen 16 Bits werden verwendet, um den letzten Rückgang des Zählers aufzuzeichnen. Da es nur 8 Bits gibt, speichert den Unix-Minuten-Zeitstempel Modulo 2^16. Der maximale Wert, den 16 Bits darstellen können, ist 65535 (65535/24/60). ≈45,5), was etwa 45,5 Tagen entspricht, wird zurückgedreht (Rückdrehung bedeutet, dass der Modulowert wieder bei 0 beginnt).
Die unteren 8 Bits werden zum Aufzeichnen der Zugriffshäufigkeit verwendet. Der maximale Wert, den 8 Bits darstellen können, ist 255. Logc kann definitiv nicht die tatsächliche Anzahl der Rediskey-Zugriffszeiten aufzeichnen. Dies ist tatsächlich aus dem Namen ersichtlich, den es speichert der Logarithmus der Anzahl der Zugriffe. Der Anfangswert des Schlüssels logc ist 5 (LFU_INITI_VAL), wodurch sichergestellt wird, dass neu hinzugefügte Werte nicht jedes Mal, wenn der Schlüssel hinzugefügt wird, aktualisiert werden zugegriffen wird; außerdem wird logc mit der Zeit verfallen.
Logistic Counter wächst nicht nur, sondern verfällt auch. Die Regeln für Wachstum und Verfall können auch über redis.conf konfiguriert werden.
- Der LFU-Log-Faktor wird verwendet, um die Wachstumsrate des Logistikzählers anzupassen. Je größer der LFU-Log-Faktor-Wert ist, desto langsamer ist die Wachstumsrate des Logistikzählers.
- lfu-decay-time wird verwendet, um die Abklinggeschwindigkeit des Logistikzählers anzupassen. Es handelt sich um einen Wert in Minuten. Der Standardwert ist 1. Je größer der lfu-decay-time-Wert ist, desto langsamer ist der Abklingzeitpunkt.
Das ist mein Verständnis des Interviewers! !
Cache-Aufschlüsselung:
bedeutet, dass sich ein Hotspot-Schlüssel, auf den sehr häufig zugegriffen wird, in einer zentralen Situation mit hoher Parallelität befindet. Wenn der Schlüssel ungültig wird, dringt eine große Anzahl von Anforderungen in den Cache ein Die Datenbank läuft direkt über Redis.
Lösung:
- Wenn die zwischengespeicherten Daten relativ fest sind, können Sie versuchen, die Hotspot-Daten so einzustellen, dass sie nie ablaufen.
- Wenn die zwischengespeicherten Daten nicht häufig aktualisiert werden und der gesamte Prozess der Cache-Aktualisierung weniger Zeit in Anspruch nimmt, können Sie verteilte Mutex-Sperren basierend auf verteilter Middleware wie Redis und Zookeeper oder lokale Mutex-Sperren verwenden, um sicherzustellen, dass nur eine kleine Anzahl von Anforderungen möglich ist Fordern Sie die Datenbank an und erstellen Sie den Cache neu. Die verbleibenden Threads können nach Aufhebung der Sperre auf den neuen Cache zugreifen.
- Wenn die zwischengespeicherten Daten häufig aktualisiert werden oder der Cache-Aktualisierungsprozess lange dauert, können Sie den Timing-Thread verwenden, um den Cache aktiv neu aufzubauen, bevor der Cache abläuft, oder die Cache-Ablaufzeit verzögern, um sicherzustellen, dass alle Anforderungen den entsprechenden Cache erfüllen jederzeit abrufbar.
Cache-Penetration:
bezieht sich auf die Anforderung von Daten, die nicht im Cache oder in der Datenbank vorhanden sind. Diese Situation wird normalerweise durch Hacker verursacht. Wenn keine Abwehrmaßnahmen ergriffen werden, kann dies leicht dazu führen, dass die Datenbank zerstört wird die Anfrage. Wenn ein Hacker beispielsweise eine negative ID verwendet, um eine Ihrer Tabellen abzufragen, wird unsere ID normalerweise nicht auf eine negative Zahl gesetzt.
Lösung:
- Wenn die Datenbank nicht abgefragt wird, legen Sie einen Nullwert im Cache fest. Diese Methode kann die Situation der Verwendung unterschiedlicher negativer ID-Anfragen nicht lösen.
- Verwenden Sie den Bloom-Filter, um alle Daten in der Datenbank dem Bloom-Filter zuzuordnen. Bevor Sie die Anfrage stellen, stellen Sie fest, ob sie vorhanden sind. Geben Sie sie einfach direkt zurück.
Cache-Lawine:
Cache-Lawine tritt auf, wenn eine große Anzahl von Caches gleichzeitig ausfällt, was zum sofortigen Absturz der Datenbank führt (Szenario mit hoher Parallelität), und in diesem Fall, wenn der Cache nicht vorhanden ist Wenn die Datenbank wiederhergestellt wird, ist sie unbrauchbar oder wird weiterhin beschädigt.
Lösung:
- Cache-Architekturdesign: Entwerfen Sie hochverfügbares Redis, Master-Slave + Sentinel, Redis-Cluster-Cluster
- Projektserver: Verwenden Sie lokalen Cache und Service-Degradation-Verarbeitung, um Anfragen an MySQL zu minimieren
- Betriebs- und Wartungsmethoden: Überwachen Sie Redis regelmäßig Cluster, ein dauerhafter Sicherungsmechanismus und die zwischengespeicherten Daten können im Falle einer Lawine rechtzeitig wiederhergestellt werden Machen Sie dieses Mal diesen Trick. Das Interview ist da.
Natürlich kann dieser Wissenspunkt nicht in ein paar Sätzen klar erklärt werden, daher empfehle ich Ihnen, diesen Artikel zu lesen und ihn leicht festzuhalten"
Redis Distributed - Master-Slave-Replikation, Sentinel und Clustering sind gründlich verstanden 》
Dieser Artikel ist reproduziert von: https://juejin.cn/post/7019088999792934926Autor: Li Zi捌
Weitere programmbezogene Kenntnisse finden Sie unter:
Einführung in die Programmierung
! !