Memcached ist ein verteiltes Speicherobjekt-Caching-System, das von danga.com (dem technischen Team, das LiveJournal betreibt) entwickelt wurde, um die Datenbanklast zu reduzieren und die Leistung in dynamischen Systemen zu verbessern. Was diese Sache betrifft, glaube ich, dass viele Leute sie verwendet haben. Der Zweck dieses Artikels besteht darin, durch die Implementierung und Codeanalyse von Memcached ein tieferes Verständnis dieser hervorragenden Open-Source-Software zu erlangen und sie entsprechend unseren Anforderungen weiter zu optimieren. Abschließend werden wir durch die Analyse der BSM_Memcache-Erweiterung unser Verständnis für die Verwendung von Memcached vertiefen.
Einige Inhalte in diesem Artikel erfordern möglicherweise eine bessere mathematische Grundlage als Unterstützung.
◎Was ist Memcached?
Bevor wir auf dieses Thema näher eingehen, müssen wir zunächst verstehen, was es „nicht“ ist. Viele Leute verwenden es als Speicherträger wie SharedMemory. Obwohl Memcached die gleiche „Key=>Value“-Methode zum Organisieren von Daten verwendet, unterscheidet es sich stark von lokalen Caches wie Shared Memory und APC. Memcached ist verteilt, was bedeutet, dass es nicht lokal ist. Es vervollständigt den Dienst basierend auf der Netzwerkverbindung (natürlich kann es auch localhost verwenden). Es handelt sich um ein anwendungsunabhängiges Programm oder einen Daemon-Prozess (Daemon-Modus).
Memcached nutzt die Libevent-Bibliothek zur Implementierung von Netzwerkverbindungsdiensten und kann theoretisch eine unbegrenzte Anzahl von Verbindungen verarbeiten. Im Gegensatz zu Apache ist es jedoch häufiger auf stabile kontinuierliche Verbindungen ausgerichtet, sodass seine tatsächlichen Parallelitätsfähigkeiten begrenzt sind. Unter konservativen Umständen beträgt die maximale Anzahl gleichzeitiger Verbindungen für Memcached 200, was mit der Linux-Thread-Fähigkeit zusammenhängt. Dieser Wert kann angepasst werden. Informationen zu libevent finden Sie in der entsprechenden Dokumentation. Auch die Nutzung des Memcached-Speichers unterscheidet sich von der von APC. APC basiert auf Shared Memory und Memcachd verfügt über einen eigenen Speicherzuweisungsalgorithmus und eine eigene Verwaltungsmethode. Normalerweise kann jeder Memcached-Speicher 2 GB Speicherplatz verwalten Da mehr Platz benötigt wird, kann die Anzahl der Prozesse erhöht werden.
◎Für welche Anlässe ist Memcached geeignet?
In vielen Fällen wurde Memcached missbraucht, was natürlich zwangsläufig zu Beschwerden führt. Ich sehe oft Leute, die in Foren posten, ähnlich wie „Wie man die Effizienz verbessert“, und die Antwort lautet „Verwende Memcached“. Es gibt keinen Satz darüber, wie man es verwendet, wo man es verwendet und wofür es verwendet wird. Memcached ist kein Allheilmittel und auch nicht für alle Situationen geeignet.
Memcached ist ein „verteiltes“ Speicherobjekt-Caching-System. Das heißt, für Anwendungen, die nicht „verteilt“ oder gemeinsam genutzt werden müssen oder einfach nur klein genug sind, um nur einen Server zu haben, ist dies bei Memcached nicht der Fall Im Gegenteil: Es verlangsamt auch die Systemeffizienz, da Netzwerkverbindungen auch Ressourcen erfordern, sogar lokale UNIX-Verbindungen. Meine vorherigen Testdaten haben gezeigt, dass die lokale Lese- und Schreibgeschwindigkeit im Memcache um ein Vielfaches langsamer ist als bei direkten PHP-Speicherarrays, während APC- und Shared-Memory-Methoden den direkten Arrays ähneln. Es ist ersichtlich, dass die Verwendung von Memcached sehr unwirtschaftlich ist, wenn es sich nur um einen Cache auf lokaler Ebene handelt.
Memcached wird häufig als Datenbank-Front-End-Cache verwendet. Da es viel weniger SQL-Analyse, Festplattenoperationen und anderen Aufwand erfordert als eine Datenbank und Speicher zum Verwalten von Daten verwendet, kann es eine bessere Leistung bieten als das direkte Lesen der Datenbank. In großen Systemen ist es sehr schwierig, auf dieselben Daten zuzugreifen Häufig kann Memcached den Datenbankdruck erheblich reduzieren und die Effizienz der Systemausführung verbessern. Darüber hinaus wird Memcached häufig als Speichermedium für den Datenaustausch zwischen Servern verwendet. Beispielsweise können Daten, die den Single-Sign-On-Status des Systems in einem SSO-System speichern, in Memcached gespeichert und von mehreren Anwendungen gemeinsam genutzt werden.
Es ist zu beachten, dass Memcached Speicher zum Verwalten von Daten verwendet und daher flüchtig ist. Wenn der Server neu gestartet oder der Memcached-Prozess beendet wird, gehen die Daten verloren, sodass Memcached nicht zum Beibehalten von Daten verwendet werden kann. Viele Leute verstehen falsch, dass die Leistung von Memcached genauso gut ist wie der Vergleich zwischen Speicher und Festplatte. Tatsächlich wird Memcached durch die Verwendung von Speicher keine Hunderte oder Tausende von Verbesserungen bei der Lese- und Schreibgeschwindigkeit erzielen Der Vorteil im Vergleich zum Festplattendatenbanksystem besteht darin, dass es keinen übermäßigen Overhead und keine direkten Lese- und Schreibmethoden aufweist und eine sehr große Menge verarbeiten kann Daher sind häufig zwei Gigabit-Netzwerkbandbreiten vorhanden. Sie sind alle vollständig ausgelastet und der zwischengespeicherte Prozess selbst beansprucht nicht viel CPU-Ressourcen.
◎So funktioniert Memcached
In den folgenden Abschnitten ist es für Leser am besten, eine Kopie des Quellcodes von Memcached vorzubereiten.
Memcached ist ein herkömmliches Netzwerkdienstprogramm. Wenn beim Start der Parameter -d verwendet wird, wird es als Daemon-Prozess ausgeführt. Das Erstellen eines Daemon-Prozesses wird durch daemon.c abgeschlossen. Dieses Programm verfügt nur über eine Daemon-Funktion, die sehr einfach ist (wenn keine besonderen Anweisungen gegeben werden, unterliegt der Code 1.2.1):
CODE:[In Zwischenablage kopieren]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
Daemon(nochdir, noclose)
int nochdir, noclose;
{
int fd;
switch (fork()) {
Fall-1:
Rückkehr (-1);
Fall 0:
brechen;
Standard:
_exit(0);
}
if (setsid() == -1)
return (-1);
if (!nochdir)
(void)chdir("/");
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
(void)close(fd);
}
Rückkehr (0);
}
Nachdem diese Funktion den gesamten Prozess gegabelt hat, wird der übergeordnete Prozess beendet und dann STDIN, STDOUT und STDERR auf leere Geräte verschoben, und der Daemon wird erfolgreich eingerichtet.
Der Startvorgang von Memcached selbst ist in der Hauptfunktion von memcached.c wie folgt:
1. Rufen Sie Settings_init () auf, um die Initialisierungsparameter festzulegen.
2. Lesen Sie die Parameter aus dem Startbefehl, um den Einstellwert festzulegen
3. Parameter LIMIT einstellen
4. Starten Sie die Netzwerk-Socket-Überwachung (falls kein Socket-Pfad vorhanden ist) (UDP-Modus wird nach 1.2 unterstützt)
5. Überprüfen Sie die Benutzeridentität (Memcached lässt das Starten der Root-Identität nicht zu)
6. Wenn Socketpath vorhanden ist, öffnen Sie die lokale UNIX-Verbindung (Sock Pipe).
7. Wenn im -d-Modus gestartet, erstellen Sie einen Daemon-Prozess (rufen Sie die Daemon-Funktion wie oben auf).
8. Element, Ereignis, Statusinformationen, Hash, Verbindung, Platte initialisieren
9. Wenn Managed in den Einstellungen wirksam wird, erstellen Sie ein Bucket-Array
10. Prüfen Sie, ob die Speicherseite gesperrt werden muss
11. Signal initialisieren, Verbindung herstellen, Warteschlange löschen
12. Im Daemon-Modus: Prozess-ID verarbeiten
13. Das Ereignis startet, der Startvorgang endet und die Hauptfunktion tritt in die Schleife ein.
Da stderr im Daemon-Modus an das Schwarze Loch weitergeleitet wurde, werden während der Ausführung keine sichtbaren Fehlermeldungen zurückgegeben.
Die Hauptschleifenfunktion von memcached.c ist „drive_machine“. Der eingehende Parameter ist ein Strukturzeiger, der auf die aktuelle Verbindung zeigt, und die Aktion wird basierend auf dem Status des Statusmitglieds bestimmt.
Memcached verwendet eine Reihe benutzerdefinierter Protokolle, um den Datenaustausch abzuschließen: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
In der API die Newline-Symbole sind vereinheitlicht als rn
◎Memcacheds Speicherverwaltungsmethode
Memcached verfügt über eine sehr einzigartige Speicherverwaltungsmethode. Um die Effizienz zu verbessern, verwendet es Voranwendungs- und Gruppierungsmethoden zur Verwaltung des Speicherplatzes und nicht jedes Mal, wenn Daten geschrieben werden müssen . Geben Sie beim Löschen von Daten einen Zeiger frei. Memcached verwendet die Slab->Chunk-Organisationsmethode zur Speicherverwaltung.
Es gibt einige Unterschiede in den Plattenraumteilungsalgorithmen in slabs.c in 1.1 und 1.2, die später separat vorgestellt werden.
Ein Slab kann als Speicherblock verstanden werden, der für Memcached die kleinste Einheit ist, die gleichzeitig als Speicher verwendet werden kann. In Memcached beträgt die Standardgröße eines Slabs 1048576 Bytes (1 MB), sodass Memcached den gesamten Speicher nutzt. Jede Platte ist in mehrere Blöcke unterteilt, und jeder Block speichert ein Element. Jedes Element enthält außerdem die Elementstruktur, den Schlüssel und den Wert (beachten Sie, dass der Wert in memcached nur eine Zeichenfolge ist). Platten bilden verknüpfte Listen entsprechend ihrer eigenen IDs, und diese verknüpften Listen werden entsprechend ihrer IDs an ein Slabclass-Array gehängt. Die gesamte Struktur sieht ein wenig wie ein zweidimensionales Array aus. Die Länge der Plattenklasse beträgt 21 in 1.1 und 200 in 1.2.
Slab hat eine anfängliche Blockgröße, die in 1.1 1 Byte und in 1.2 80 Byte beträgt. In 1.2 ist der Faktorwert standardmäßig 1,25.
In 1.1 wird die Blockgröße als Anfangsgröße * 2^n, n ausgedrückt classid, Das heißt: Die Platte mit der ID 0 hat eine Blockgröße von 1 Byte, die Platte mit der ID 1 hat eine Blockgröße von 2 Bytes, die Platte mit der ID 2 hat eine Blockgröße von 4 Bytes ... die Platte mit der ID 20 hat eine Blockgröße von 4 Bytes. Die Größe beträgt 1 MB, was bedeutet, dass es nur einen Block mit der ID 20 gibt:
CODE:[In Zwischenablage kopieren]void slabs_init(size_t limit) {
int i;
int size=1;
mem_limit = limit;
for(i=0; i<=POWER_LARGEST; i++, size*=2) {
slabclass[i].size = size;
slabclass[i].perslab = POWER_BLOCK / size;
slabclass[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
slabclass[i].end_page_ptr = 0;
slabclass[i].end_page_free = 0;
slabclass[i].slab_list = 0;
slabclass[i].list_size = 0;
slabclass[i].killing = 0;
}
/* für die Testsuite: Vortäuschen, wie viel wir bereits mallociert haben */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* Platten standardmäßig vorab zuweisen, es sei denn, die Umgebungsvariable
zum Testen ist auf etwas ungleich Null gesetzt */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(limit / POWER_BLOCK);
}
}
}
In 1.2 wird die Blockgröße als Anfangsgröße * f^n ausgedrückt, f ist der Faktor, der in memcached.c definiert ist, und n ist die Klassen-ID. Gleichzeitig müssen nicht alle 201 Köpfe initialisiert werden, da der Faktor ist variabel und die Initialisierung führt nur eine Schleife aus. Die berechnete Größe erreicht die Hälfte der Slab-Größe und beginnt bei ID1, das heißt: Slab mit ID 1, jede Blockgröße beträgt 80 Bytes, Slab mit ID 2, jede Chunk-Größe beträgt 80* f, id ist 3 Slab, jede Blockgröße beträgt 80*f^2 und die Initialisierungsgröße hat einen Korrekturwert CHUNK_ALIGN_BYTES, um eine n-Byte-Ausrichtung sicherzustellen (was garantiert, dass das Ergebnis ein ganzzahliges Vielfaches von CHUNK_ALIGN_BYTES ist). Auf diese Weise wird memcached1.2 unter Standardbedingungen auf id40 initialisiert. Die Größe jedes Blocks in dieser Platte beträgt 504692, und jede Platte enthält zwei Blöcke. Schließlich fügt die Funktion slab_init am Ende eine ID41 hinzu, die einen ganzen Block darstellt, d. h. es gibt nur einen 1-MB-Block in dieser Platte:
CODE:[In Zwischenablage kopieren]void slabs_init(size_t limit, double-factor) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* Faktor 2,0 bedeutet, dass das standardmäßige Memcached-Verhalten verwendet wird */
if (Faktor == 2,0 && Größe < 128)
size = 128;
mem_limit = limit;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* Stellen Sie sicher, dass Elemente immer N-Byte-ausgerichtet sind */
if (Größe % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES)
;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
Größe *= Faktor;
if (settings.verbose > 1) {
fprintf(stderr, „Plattenklasse %3d: Blockgröße %6d, Perslab %5dn“,
i, slabclass[i].size, slabclass[i].perslab);
}
}
power_largest = i;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* für die Testsuite: Vortäuschen, wie viel wir bereits mallociert haben */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(limit / POWER_BLOCK);
}
}
#endif
}
Wie aus dem Obigen hervorgeht, ist die Speicherzuweisung von Memcached redundant. Wenn eine Platte nicht durch die Blockgröße, die sie besitzt, teilbar ist, wird der verbleibende Platz am Ende der Platte verworfen. In id40 werden beispielsweise zwei Blöcke belegt 1009384 Bytes, diese Platte hat insgesamt 1 MB, sodass 39192 Bytes verschwendet werden.
Memcached verwendet diese Methode zum Zuweisen von Speicher, um die Klassen-ID anhand der Elementlänge schnell zu finden. Sie ähnelt in gewisser Weise dem Hash, da die Länge eines Elements beispielsweise 300 Byte beträgt Sie können erhalten, dass es in der Platte von id7 gespeichert werden sollte, da gemäß der obigen Berechnungsmethode die Blockgröße von id6 252 Byte, die Blockgröße von id7 316 Byte und die Blockgröße von id8 396 Byte beträgt. Das bedeutet, dass alle 252 bis 316 Byte großen Elemente in id7 gespeichert werden sollten. In ähnlicher Weise kann in 1.1 auch berechnet werden, dass es zwischen 256 und 512 liegt und in id9 mit einer Chunk_size von 512 (32-Bit-System) platziert werden sollte.
Wenn Memcached initialisiert wird, werden Slabs initialisiert (wie Sie zuvor sehen können, wird slabs_init() in der Hauptfunktion aufgerufen). Es wird eine Konstante DONT_PREALLOC_SLABS in slabs_init() überprüft. Wenn diese nicht definiert ist, bedeutet dies, dass die Platte mit vorab zugewiesenem Speicher initialisiert wird, sodass für jede ID unter allen definierten Plattenklassen eine Platte erstellt wird. Dies bedeutet, dass 1.2 nach dem Starten des Prozesses in der Standardumgebung 41 MB Slab-Speicherplatz zuweist. Während dieses Prozesses tritt die zweite Speicherredundanz von Memcached auf, da möglicherweise überhaupt keine ID verwendet wurde, es sich jedoch auch um A handelt Standardmäßig wird eine Platte beantragt, und jede Platte belegt 1 MB Speicher.
Wenn eine Platte aufgebraucht ist und ein neues Element mit dieser ID eingefügt werden muss, wird bei der Beantragung einer neuen Platte erneut eine Platte beantragt Platte, die entsprechende ID. Die verknüpfte Liste der Platte wächst exponentiell. In der Funktion „grow_slab_list“ ändert sich die Länge dieser Kette von 1 auf 2, von 2 auf 4, von 4 auf 8.
CODE:[In Zwischenablage kopieren]static int grow_slab_list (unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
size_t new_size = p->list_size ? p->list_size * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
if (new_list == 0) return 0;
p->list_size = new_size;
p->slab_list = new_list;
}
Rückgabe 1;
}
Beim Auffinden des Elements wird die Funktion slabs_clsid verwendet. Der eingehende Parameter ist die Elementgröße und der Rückgabewert ist die Klassen-ID. Aus diesem Prozess ist ersichtlich, dass die dritte Speicherredundanz von memcached beim Speichern des Elements auftritt. Das Element ist immer kleiner oder gleich der Blockgröße. Wenn das Element kleiner als die Blockgröße ist, wird erneut Platz verschwendet.
◎Memcacheds NewHash-Algorithmus
Die Elementspeicherung von Memcached basiert auf einer großen Hash-Tabelle. Ihre tatsächliche Adresse ist der Chunk-Offset in der Platte, ihre Positionierung hängt jedoch vom Ergebnis des Hashings des Schlüssels ab, der in der Primary_Hashtable zu finden ist. Alle Hash- und Item-Operationen sind in assoc.c und items.c definiert.
Memcached verwendet einen Algorithmus namens NewHash, der sehr effektiv und effizient ist. Es gibt einige Unterschiede zwischen NewHash in 1.1 und 1.2. Die Hauptimplementierungsmethode ist immer noch dieselbe. Die Hash-Funktion von 1.2 wurde organisiert und optimiert, und ihre Anpassungsfähigkeit ist besser.
Referenz zum NewHash-Prototyp: http://burtleburtle.net/bob/hash/evahash.html . Mathematiker sind immer etwas seltsam, haha~
Um die Konvertierung zu erleichtern, werden zwei Datentypen definiert, u4 und u1, u4 ist eine vorzeichenlose lange Ganzzahl und u1 ist ein vorzeichenloses Zeichen (0-255).
Spezifische Codes finden Sie in den Quellcodepaketen 1.1 und 1.2.
Achten Sie hier auch auf die Hashtabellenlänge. In 1.1 ist die HASHPOWER-Konstante als 20 definiert, und die Hashtabellenlänge beträgt 4 MB (Hashsize ist ein Makro). dass 1 um n Bits nach rechts verschoben wird. In 1.2 ist es die Variable 16, das heißt, die Länge der Hashtabelle beträgt 65536:
CODE:[In die Zwischenablage kopieren]typedef unsigned long int ub4 /* unsigned 4-Byte-Mengen */
typedef unsigned char ub1; /* unsigned 1-Byte-Mengen */
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)
In assoc_init() wird die primäre_hashtable initialisiert. Zu den entsprechenden Hash-Operationen gehören: assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete(), entsprechend den Lese- und Schreibvorgängen des Elements. Unter diesen ist assoc_find() eine Funktion, die die entsprechende Elementadresse basierend auf dem Schlüssel und der Schlüssellänge findet (beachten Sie, dass in C häufig die Zeichenfolge und die Zeichenfolgenlänge direkt gleichzeitig übergeben werden, anstatt strlen innerhalb der Funktion auszuführen). ), und was zurückgegeben wird, ist ein Elementstrukturzeiger, dessen Datenadresse sich auf einem Block in der Platte befindet.
items.c ist das Betriebsprogramm für Datenelemente. Jedes vollständige Element enthält mehrere Teile, die in item_make_header() definiert sind als:
Schlüssel: Schlüssel
nkey: Schlüssellänge
Flags: benutzerdefiniertes Flag (tatsächlich ist dieses Flag in Memcached nicht aktiviert)
nbytes: Wertlänge (einschließlich Zeilenumbruchsymbol rn)
Suffix: Suffix Puffer
NSuffix: Die Suffixlänge
eines vollständigen Elements ist die Schlüssellänge + Wertlänge + Suffixlänge + Elementstrukturgröße (32 Byte). Die Elementoperation basiert auf dieser Länge, um die Klassen-ID der Platte zu berechnen.
Jeder Bucket in der Hashtabelle ist mit einer doppelt verknüpften Liste verknüpft. Während item_init() wurden die drei Arrays für Kopf, Ende und Größe auf 0 initialisiert. Die Größen dieser drei Arrays sind die Konstante LARGEST_ID (der Standardwert ist 255). Dieser Wert muss mit dem Faktor geändert werden. Bei jedem Aufruf von item_assoc() wird zunächst versucht, einen freien Block von der Platte abzurufen. Wenn kein verfügbarer Block vorhanden ist, wird die verknüpfte Liste 50 Mal durchsucht, um einen vorhandenen Block zu erhalten Von LRU gestartetes Element, heben Sie die Verknüpfung auf und fügen Sie dann das Element ein, das in die verknüpfte Liste eingefügt werden soll.
Achten Sie auf das Refcount-Mitglied des Artikels. Nachdem die Verknüpfung des Elements aufgehoben wurde, wird es nur aus der verknüpften Liste entfernt. Es wird nicht sofort freigegeben, sondern lediglich in die Löschwarteschlange gestellt (Funktion „item_unlink_q()“).
Das Element entspricht einigen Lese- und Schreibvorgängen, einschließlich Entfernen, Aktualisieren und Ersetzen. Der wichtigste Vorgang ist natürlich der Zuordnungsvorgang.
Ein weiteres Merkmal von item ist, dass es eine Ablaufzeit hat, was eine sehr nützliche Funktion von memcached ist. Viele Anwendungen sind auf den Ablauf von memcached-Elementen angewiesen, z. B. Sitzungsspeicher, Betriebssperren usw. Die Funktion item_flush_expired() scannt die Elemente in der Tabelle und führt einen Vorgang zum Aufheben der Verknüpfung für abgelaufene Elemente durch. Tatsächlich ist beim Abrufen auch eine zeitliche Beurteilung erforderlich.
CODE:[In die Zwischenablage kopieren]/* lässt Elemente ablaufen, die aktueller sind als die „eastest_live“-Einstellung */
void item_flush_expired() {
int i;
item *iter, *next;
if (! Settings.oldest_live)
zurückkehren;
for (i = 0; i < LARGEST_ID; i++) {
/* Die LRU wird in absteigender Zeitreihenfolge und dem Zeitstempel eines Elements sortiert
* ist nie neuer als die Zeit des letzten Zugriffs, wir müssen also nur laufen
* zurück, bis wir auf ein Element stoßen, das älter als die älteste Live-Zeit ist.
* Bei der ältesten_live-Prüfung verfallen die verbleibenden Elemente automatisch.
*/
for (iter = head[i]; iter != NULL; iter = next) {
if (iter->time >= Settings.oldest_live) {
next = iter->next;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} anders {
/* Wir haben das erste alte Element erreicht. Weiter zur nächsten Warteschlange.
brechen;
}
}
}
}
CODE:[In Zwischenablage kopieren]/* Wrapper um assoc_find, der die verzögerte Ablauf-/Löschlogik ausführt */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
item *it = assoc_find(key, nkey);
if (delete_locked) *delete_locked = 0;
if (it && (it->it_flags & ITEM_DELETED)) {
/* Es ist als löschgesperrt gekennzeichnet. Mal sehen, ob diese Bedingung erfüllt ist
ist überfällig und der 5-Sekunden-delete_timer ist einfach nicht überfällig
Habe es schon geschafft... */
if (!item_delete_lock_over(it)) {
if (delete_locked) *delete_locked = 1;
es = 0;
}
}
if (it && Settings.oldest_live && Settings.oldest_live <= current_time &&
it->time <= Settings.oldest_live) {
item_unlink(it);
es = 0;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink(it);
es = 0;
}
gib es zurück;
}
Die Speicherverwaltungsmethode von Memcached ist sehr ausgefeilt und effizient. Sie reduziert die Anzahl der direkten Zuweisungen von Systemspeicher erheblich, verringert den Funktionsaufwand und die Wahrscheinlichkeit einer Speicherfragmentierung. Obwohl diese Methode eine gewisse Redundanzverschwendung verursacht, ist diese Verschwendung in großen Systemen trivial Anwendungen.
◎Die theoretische Parameterberechnungsmethode von Memcached
hat mehrere Parameter, die sich auf die Arbeit von Memcached auswirken:
Konstante REALTIME_MAXDELTA 60*60*24*30
Maximale Ablaufzeit von 30 Tagen
freetotal (=200) in conn_init()
Maximale Anzahl gleichzeitiger Verbindungen,
Konstante KEY_MAX_LENGTH 250
Maximaler Schlüssellängen-
Einstellungsfaktor (=1,25)
Faktor beeinflusst die Schrittgröße der Chunk
-Einstellungen.maxconns (=1024)
Maximale Soft-Verbindungseinstellungen.chunk_size
(=48)
Eine konservativ geschätzte Schlüssel-Wert-Länge, die zum Generieren der Blocklänge (1,2) in id1 verwendet wird. Die Blocklänge von id1 entspricht diesem Wert plus der Länge der Elementstruktur (32), die standardmäßig 80 Byte beträgt.
Konstante POWER_SMALLEST 1
Minimale Klassen-ID (1.2)
Konstante POWER_LARGEST 200
Maximale Klassen-ID (1.2)
Konstante POWER_BLOCK 1048576
Standard-Plattengrößenkonstante
CHUNK_ALIGN_BYTES (sizeof(void *))
Stellen Sie sicher, dass die Blockgröße ein ganzzahliges Vielfaches dieses Werts ist, um außerhalb der Grenzen zu verhindern (die Länge von void * ist auf verschiedenen Systemen unterschiedlich, auf Standard-32-Bit-Systemen beträgt sie 4).
Konstante ITEM_UPDATE_INTERVAL 60
Warteschlangenaktualisierungsintervallkonstante
LARGEST_ID 255
Maximale Anzahl von Elementen in der verknüpften Liste (dieser Wert kann nicht kleiner sein als die größte Klassen-ID)
Variable Hashpower (konstante HASHPOWER in 1.1)
Bestimmen der Größe der Hashtabelle.
Basierend auf den oben vorgestellten Inhalts- und Parametereinstellungen können einige Ergebnisse berechnet werden:
1. Es gibt keine Software-Obergrenze für die Anzahl der Elemente, die im Memcached gespeichert werden können falsch.
2. Unter der Annahme, dass der NewHash-Algorithmus gleichmäßige Kollisionen aufweist, ist die Anzahl der Zyklen zum Finden eines Elements die Gesamtzahl der Elemente dividiert durch die Hashtabellengröße (bestimmt durch Hashpower), die linear ist.
3. Memcached begrenzt das maximal zulässige Element auf 1 MB und Daten, die größer als 1 MB sind, werden ignoriert.
4. Die Speicherplatznutzung von Memcached steht in engem Zusammenhang mit den Dateneigenschaften und hängt auch mit der Konstante DONT_PREALLOC_SLABS zusammen. Im schlimmsten Fall werden 198 Platten verschwendet (alle Elemente sind in einer Platte konzentriert und alle 199 IDs sind vollständig zugewiesen).
◎Memcacheds Optimierung mit fester Länge
Basierend auf den Beschreibungen in den obigen Abschnitten habe ich ein tieferes Verständnis von Memcached. Es kann nur auf der Grundlage eines tiefgreifenden Verständnisses optimiert werden.
Memcached selbst ist für Daten variabler Länge konzipiert und kann daher als „öffentlichkeitsorientiert“ bezeichnet werden. In typischen Fällen sind unsere Daten jedoch nicht so „universell“. ungleichmäßige Verteilung, das heißt, die Datenlänge ist auf mehrere Bereiche konzentriert (z. B. das Speichern von Benutzersitzungen); der andere extremere Zustand sind Daten gleicher Länge (z. B. Schlüsselwerte fester Länge, meistens). sichtbar in Zugriffen, Online-Statistiken oder Ausführungssperren).
Hier untersuchen wir hauptsächlich die Optimierungslösung für Daten fester Länge (1.2). Die zentralisierten verteilten Daten variabler Länge dienen nur als Referenz und sind einfach zu implementieren.
Um Daten mit fester Länge zu lösen, muss zunächst das Plattenzuordnungsproblem gelöst werden. Das erste, was bestätigt werden muss, ist, dass wir nicht so viele Platten mit unterschiedlichen Blocklängen benötigen, um die Nutzung zu maximieren Bei der Anzahl der Ressourcen ist es am besten, wenn Blöcke und Elemente gleich lang sind. Berechnen Sie daher zunächst die Länge des Elements.
Es gab bereits einen Algorithmus zur Berechnung der Elementlänge. Es ist zu beachten, dass zusätzlich zur Stringlänge die Länge der Elementstruktur von 32 Bytes hinzugefügt werden muss.
Angenommen, wir haben berechnet, dass wir 200 Bytes gleichlanger Daten speichern müssen.
Der nächste Schritt besteht darin, die Beziehung zwischen der Klassen-ID der Platte und der Blocklänge zu ändern. In der Originalversion gibt es eine entsprechende Beziehung zwischen Chunk-Länge und Classid. Wenn nun alle Chunks auf 200 Bytes eingestellt sind, besteht diese Beziehung nicht. Wir müssen die Beziehung zwischen den beiden neu bestimmen. Eine Methode besteht darin, nur eine feste ID für die gesamte Speicherstruktur zu verwenden, d. h. nur einen der 199 Steckplätze zu verwenden. Unter dieser Bedingung muss DONT_PREALLOC_SLABS definiert werden, um zusätzliche Verschwendung vor der Zuordnung zu vermeiden. Eine andere Methode besteht darin, eine Hash-Beziehung herzustellen, um die Klassen-ID aus dem Element zu ermitteln. Sie können die Länge nicht als Schlüssel verwenden. Sie können variable Daten wie das NewHash-Ergebnis des Schlüssels verwenden oder den Hash direkt auf der Grundlage des Schlüssels durchführen Schlüssel von Daten fester Länge müssen ebenfalls die gleiche Länge haben). Der Einfachheit halber wählen wir hier die erste Methode. Der Nachteil dieser Methode besteht darin, dass nur eine ID verwendet wird. Wenn die Datenmenge sehr groß ist, ist die Plattenkette sehr lang (da alle Daten überfüllt sind). eine Kette). Der Aufwand für die Traversierung ist relativ hoch.
Die drei Arten der Platzverschwendung wurden bereits früher eingeführt. Wenn man die Blocklänge gleich der Elementlänge setzt, löst man das erste Problem (das Verbleib in der Platte). )?Um dieses Problem zu lösen, müssen Sie die POWER_BLOCK-Konstante so ändern, dass die Größe jeder Platte genau einem ganzzahligen Vielfachen der Blocklänge entspricht, sodass eine Platte in n Blöcke unterteilt werden kann. Wenn dieser Wert zu groß ist, führt dies zu Redundanz. Wählen Sie entsprechend der Chunk-Länge 1000000 Auf diese Weise ist eine Platte 1 Million Bytes groß, nicht 1048576. Alle drei Redundanzprobleme sind gelöst und die Raumnutzung wird erheblich verbessert.
Ändern Sie die Funktion slabs_clsid so, dass sie direkt einen festen Wert (z. B. 1) zurückgibt:
CODE:[In Zwischenablage kopieren]unsigned int slabs_clsid(size_t size) {
Rückgabe 1;
}
Ändern Sie die Funktion slabs_init, entfernen Sie den Teil, der eine Schleife erstellt, um alle Classid-Attribute zu erstellen, und fügen Sie slabclass[1] direkt hinzu:
CODE:[In Zwischenablage kopieren]slabclass[1].size = 200; //200 Bytes pro Block
slabclass[1].perslab = 5000; //1000000/200
◎Memcached-Client
Memcached ist ein Dienstprogramm. Wenn Sie es verwenden, können Sie gemäß seinem Protokoll eine Verbindung zum Memcached-Server herstellen, Befehle an den Dienstprozess senden und dann die oben genannten Daten verarbeiten. Zur Vereinfachung der Verwendung stehen in memcached viele Client-Programme für verschiedene Sprachen zur Verfügung, und es gibt Clients in verschiedenen Sprachen. Zu denen, die auf der C-Sprache basieren, gehören libmemcache und APR_Memcache; zu denen, die auf Perl basieren, gehört Cache::Memcached; außerdem werden Python, Ruby, Java, C# und andere Sprachen unterstützt. PHP hat die meisten Clients, nicht nur die beiden Erweiterungen mcache und PECL memcache, sondern auch eine große Anzahl von von PHP geschriebenen Kapselungsklassen. Hier finden Sie eine Einführung in die Verwendung von memcached in PHP:
Die mcache-Erweiterung wird basierend auf libmemcache neu gekapselt . libmemcache hat keine stabile Version veröffentlicht. Die aktuelle Version ist 1.4.0-rc2, die hier zu finden ist. Eine sehr schlechte Funktion von libmemcache ist, dass es viele Fehlermeldungen in stderr schreibt. Wenn stderr als Bibliothek verwendet wird, wird es normalerweise an andere Orte weitergeleitet, z. B. in das Fehlerprotokoll von Apache, und libmemcache begeht Selbstmord, was zu Abnormalitäten führen kann , aber die Leistung ist immer noch sehr gut.
Die Mcache-Erweiterung wurde zuletzt auf 1.2.0-beta10 aktualisiert. Der Autor hat nicht nur die Aktualisierung abgebrochen, sondern konnte auch die Website nicht öffnen (~_~). Er musste woanders hingehen, um diese unverantwortliche Erweiterung zu erhalten . Nach der Dekomprimierung ist die Installationsmethode wie gewohnt: phpize & configure & make & make install Stellen Sie sicher, dass Sie zuerst libmemcache installieren. Die Verwendung dieser Erweiterung ist einfach:
CODE:[In Zwischenablage kopieren]<?php
$mc = memcache(); // Erstelle ein Memcache-Verbindungsobjekt.
$mc->add_server('localhost', 11211); // Einen Dienstprozess hinzufügen
$mc->add_server('localhost', 11212); // Einen zweiten Dienstprozess hinzufügen
$mc->set('key1', 'Hello'); // Schreibe key1 => Hallo
$mc->set('key2', 'World', 10); // Schreibe key2 => World, läuft in 10 Sekunden ab
$mc->set('arr1', array('Hello', 'World')); // Ein Array schreiben
$key1 = $mc->get('key1'); // Den Wert von 'key1' abrufen und ihn $key1 zuweisen
$key2 = $mc->get('key2'); // Den Wert von 'key2' abrufen und ihn $key2 zuweisen. Wenn er 10 Sekunden überschreitet, ist er nicht verfügbar.
$arr1 = $mc->get('arr1'); // Holen Sie sich das Array 'arr1'
$mc->delete('arr1'); // 'arr1' löschen
$mc->flush_all(); // Alle Daten löschen
$stats = $mc->stats(); // Serverinformationen abrufen
var_dump($stats); // Serverinformationen sind ein Array
?>
Der Vorteil dieser Erweiterung besteht darin, dass sie problemlos verteilten Speicher und Lastausgleich implementieren kann, da sie beim Speichern von Daten auf einem bestimmten Server basierend auf dem Hash-Ergebnis gespeichert werden kann. Dies ist auch eine Funktion von libmemcache . libmemcache unterstützt zentralisierte Hashing-Methoden, einschließlich CRC32, ELF und Perl-Hash.
PECL Memcache ist eine von PECL veröffentlichte Erweiterung. Die neueste Version ist 2.1.0, die auf der Pecl-Website erhältlich ist. Die Verwendung der Memcache-Erweiterung ist in einigen neueren PHP-Handbüchern zu finden. Sie ist mcache sehr ähnlich, wirklich ähnlich:
CODE:[In Zwischenablage kopieren]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Verbindung konnte nicht hergestellt werden");
$version = $memcache->getVersion();
echo "Serverversion: ".$version."n";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'test';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) or die ("Fehler beim Speichern der Daten auf dem Server");
echo „Daten im Cache speichern (Daten verfallen in 10 Sekunden)n“
;
echo "Daten aus dem Cache:n";
var_dump($get_result)
;
Diese Erweiterung nutzt den PHP-Stream, um eine direkte Verbindung zum zwischengespeicherten Server herzustellen und Befehle über den Socket zu senden. Es ist nicht so vollständig wie libmemcache und unterstützt auch keine verteilten Vorgänge wie add_server. Da es jedoch nicht auf andere externe Programme angewiesen ist, ist es besser kompatibel und relativ stabil. Was die Effizienz betrifft, ist der Unterschied nicht groß.
Darüber hinaus sind viele PHP-Klassen verfügbar, beispielsweise MemcacheClient.inc.php, und viele sind auf phpclasses.org zu finden. Sie sind im Allgemeinen eine Neukapselung der Perl-Client-API und werden auf ähnliche Weise verwendet.
◎BSM_Memcache
Aus Sicht eines C-Clients ist APR_Memcache ein sehr ausgereiftes und stabiles Client-Programm, das Thread-Sperren und Operationen auf atomarer Ebene unterstützt, um die Betriebsstabilität sicherzustellen. Es basiert jedoch auf APR (APR wird im letzten Abschnitt eingeführt) und hat nicht so eine breite Palette von Anwendungen wie libmemcache. Weil es nicht außerhalb der APR -Umgebung laufen kann. APR kann jedoch getrennt von Apache installiert werden.
BSM_MEMCache ist eine PHP -Erweiterung, die auf APR_MEMCache basiert, die ich im Bs.Magic -Projekt entwickelt habe. Dieses Programm ist sehr einfach und macht nicht zu viele Funktionen.
Unter anderem von der Mcache-Erweiterung, die den verteilten Speicher mit mehreren Server unterstützt, unterstützt BSM_Memcache mehrere Servergruppen. Die heiße Sicherung wird implementiert. Natürlich sind die Kosten für die Implementierung dieser Funktion das Opfer der Leistung. Normalerweise können Sie es das nächste Mal bekommen.
BSM_Memcache unterstützt nur diese Funktionen:
Code: [Kopieren Sie in die Clipboard] Zend_Function_entry BSM_MEMCache_Functions [] =
{
PHP_FE (MC_GET, NULL)
PHP_FE (MC_SET, NULL)
PHP_FE (MC_DEL, NULL)
PHP_FE (MC_ADD_GROUP, NULL)
PHP_FE (MC_ADD_SERVER, NULL)
PHP_FE (MC_SHUTDOWN, NULL)
{Null, null, null}
};
Die Funktion mc_add_group gibt eine Ganzzahl zurück (tatsächlich sollte es ein Objekt sein, ich war faul ~ _ ~) als Gruppen -ID. Addrort).
Code: [Kopieren Sie in die Zwischenablage]/**
*Fügen Sie eine Servergruppe hinzu
*/
Php_function (MC_ADD_GROUP)
{
Apr_int32_t Group_id;
APR_STATUS_T
RV;
{
WACKE_PARAM_COUNT;
Return_null ();
}
Group_id = free_group_id ();
if (-1 == Group_id)
{
Return_false;
}
apr_memcache_t *MC;
rv = apr_memcache_create (p, max_g_server, 0, & mc
)
;
}
Code: [Kopieren Sie in die Zwischenablage]/**
* Fügen Sie einen Server in die Gruppe hinzu
*/
Php_function (MC_ADD_SERVER)
{
APR_STATUS_T RV;
Apr_int32_t Group_id;
doppelte G;
char *srv_str;
int srv_str_l
;
{
WACKE_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g, & srv_str, & srv_str_l) == Failure)
{
Return_false;
}
Group_id = (Apr_int32_t) g
;
{
Return_false;
}
char *host, *Scope;
PORT
;
if (apr_success == RV)
{
// dieses Serverobjekt erstellen
Apr_Memcache_Server_t *st;
rv = apr_memcache_server_create (p, host, port, 0, 64, 1024, 600, & st);
if (apr_success == RV)
{
if (null == mc_groups [gruppe_id])
{
Return_false;
}
// Server hinzufügen
(
mc_groups [Group_id], st);
{
Return_true;
}
}
}
Return_false;
}
Schalten Sie beim Einstellen und Legen von Daten alle Gruppen durch:
Code: [Kopieren Sie in die Zwischenablage]/**
* Lagern Sie Artikel in alle Gruppen
*/
Php_function (MC_SET)
{
char *key, *Wert;
int key_l, value_l;
double ttl = 0;
double set_ct = 0
;
{
WACKE_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == Fehler)
{
Return_false;
}
// Daten in jedes Objekt schreiben
Apr_int32_t i = 0;
if (ttl <0)
{
ttl = 0;
}
Apr_status_t RV
;
{
if (0 == is_validate_group (i))
{
// schreibe es!
rv = apr_memcache_add (mc_groups [i], key, value, value_l, (apr_uint32_t) ttl, 0);
if (apr_success == RV)
{
set_ct ++;
}
}
}
Return_double (set_ct);
}
In MC_get wählen Sie zuerst zufällig eine Gruppe aus und beginnen dann mit der Umfrage aus dieser Gruppe:
Code: [Kopieren Sie in die Zwischenablage]/**
* Holen Sie einen Artikel aus einer zufälligen Gruppe
*/
Php_function (MC_get)
{
char *key, *value = null;
int key_l;
Apr_size_t value_l
;
{
WACKE_PARAM_COUNT;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == Fehler)
{
Return_mull ();
}
// Ich werde versuchen...
// zufällig gelesen
Apr_int32_t curr_group_id = random_group ();
Apr_int32_t i = 0;
Apr_int32_t try = 0;
APR_UINT32_T Flag;
Apr_Memcache_t *Oper;
Apr_status_t RV
;
{
try = i + curr_group_id;
try = prover % max_group;
if (0 == is_validate_group (try))
{
// einen Wert erhalten
Oper = mc_groups [try];
rv = apr_memcache_getP (MC_GROUPS [try], p, (const char *) Key, & value, & value_l, 0);
if (apr_success == RV)
{
Return_string (Wert, 1);
}
}
}
Return_false;
}
Code: [Kopieren Sie in die Zwischenablage]/**
* Zufällige Gruppen -ID
* Für mc_get ()
*/
Apr_int32_t random_group ()
{
Struct Timeval TV;
Struct Timezone TZ;
int
usec
;
}
Die Verwendung von BSM_Memcache ähnelt anderen Clients:
CODE: [Kopieren Sie in die Zwischenablage] <? Php
$ g1 = mc_add_group ();
$ g2 = mc_add_group ();
mc_add_server ($ g1, 'localhost: 11211');
mc_add_server ($ g1, 'localhost: 11212');
MC_ADD_SERVER ($ G2, '10 .0.0.16: 11211 ');
MC_ADD_SERVER ($ G2, '10 .0.0.17: 11211 ')
;
$ key = mc_get ('key');
mc_del ('key');
mc_shutdown ();
?>
Relevante Informationen zu Apr_Memcache finden Sie hier und BSM_MEMCache können von dieser Website heruntergeladen werden.
◎ APR -Umgebung Einführung
Der vollständige Name von APR: Apache Tragbare Laufzeit. Es handelt sich um eine Reihe von plattformübergreifenden C-Sprachbibliotheken, die von der Apache Software Foundation erstellt und verwaltet werden. Es wird aus Apache httpd1.x extrahiert und ist unabhängig von HTTPD. APR bietet viele bequeme API -Schnittstellen für die Verwendung, einschließlich praktischer Funktionen wie Speicherpools, String -Operationen, Netzwerke, Arrays, Hash -Tabellen usw. Die Entwicklung von Apache2 -Modul erfordert die Exposition gegenüber vielen APR -Funktionen.
◎ PostScript
Dies ist mein letzter Artikel im Bingxu -Jahr des Mondkalenders (mein Geburtsjahr). Vielen Dank an Sina.com für die Bereitstellung von Forschungsmöglichkeiten und Kollegen in der Abteilung für ihre Hilfe.
Dr. NP02-13-2007