Memcached est un système de mise en cache d'objets de mémoire distribuée développé par danga.com (l'équipe technique qui exploite LiveJournal) pour réduire la charge de la base de données et améliorer les performances des systèmes dynamiques. Concernant cette chose, je pense que beaucoup de gens l'ont utilisé. Le but de cet article est d'acquérir une compréhension plus approfondie de cet excellent logiciel open source grâce à l'implémentation et à l'analyse du code de memcached, et de l'optimiser davantage en fonction de nos besoins. Enfin, à travers l'analyse de l'extension BSM_Memcache, nous approfondirons notre compréhension de l'utilisation de memcached.
Une partie du contenu de cet article peut nécessiter une meilleure base mathématique comme aide.
◎Qu'est-ce que Memcached ?
Avant d'élaborer sur cette question, nous devons d'abord comprendre ce qu'il « n'est pas ». De nombreuses personnes l'utilisent comme support de stockage comme SharedMemory. Bien que memcached utilise la même méthode "Key=>Value" pour organiser les données, elle est très différente des caches locaux tels que la mémoire partagée et APC. Memcached est distribué, ce qui signifie qu'il n'est pas local. Il complète le service basé sur la connexion réseau (bien sûr, il peut également utiliser localhost). Il s'agit d'un programme ou d'un processus démon indépendant de l'application (mode démon).
Memcached utilise la bibliothèque libevent pour implémenter des services de connexion réseau et peut théoriquement gérer un nombre illimité de connexions. Cependant, contrairement à Apache, il est plus souvent orienté vers des connexions continues stables, ses capacités de concurrence réelles sont donc limitées. Dans des circonstances prudentes, le nombre maximum de connexions simultanées pour memcached est de 200, ce qui est lié à la capacité du thread Linux. Cette valeur peut être ajustée. Pour plus d'informations sur libevent, veuillez vous référer à la documentation pertinente. L'utilisation de la mémoire Memcached est également différente de celle d'APC. APC est basé sur la mémoire partagée et MMAP a son propre algorithme d'allocation de mémoire et sa propre méthode de gestion. Cela n'a rien à voir avec la mémoire partagée et n'a aucune restriction sur la mémoire partagée. Normalement, chaque processus memcached peut gérer 2 Go d'espace mémoire. plus d'espace est nécessaire, le nombre de processus peut être augmenté.
◎Pour quelles occasions Memcached est-il adapté ?
Dans de nombreux cas, Memcached a été utilisé de manière abusive, ce qui entraîne évidemment inévitablement des plaintes à son sujet. Je vois souvent des gens publier sur des forums, du genre "comment améliorer l'efficacité", et la réponse est "utiliser memcached". Quant à savoir comment l'utiliser, où l'utiliser et à quoi il sert, il n'y a pas de phrase. Memcached n’est pas une panacée et ne convient pas non plus à toutes les situations.
Memcached est un système de mise en cache d'objets de mémoire "distribué", c'est-à-dire que pour les applications qui n'ont pas besoin d'être "distribuées", qui n'ont pas besoin d'être partagées, ou qui sont simplement suffisamment petites pour n'avoir qu'un seul serveur, Memcached ne le fera pas. apporter des avantages. Au contraire, cela ralentit également l'efficacité du système car les connexions réseau nécessitent également des ressources, même les connexions locales UNIX. Mes données de test précédentes ont montré que les vitesses de lecture et d'écriture locales Memcached sont des dizaines de fois plus lentes que les matrices de mémoire PHP directes, tandis que les méthodes APC et de mémoire partagée sont similaires aux matrices directes. On peut voir que s'il ne s'agit que d'un cache au niveau local, l'utilisation de memcached n'est pas du tout rentable.
Memcached est souvent utilisé comme cache frontal de base de données. Parce qu'il nécessite beaucoup moins d'analyse SQL, d'opérations de disque et autres frais généraux qu'une base de données, et qu'il utilise de la mémoire pour gérer les données, il peut offrir de meilleures performances que la lecture directe de la base de données. Dans les grands systèmes, il est très difficile d'accéder aux mêmes données. Souvent, Memcached peut réduire considérablement la pression sur la base de données et améliorer l'efficacité de l'exécution du système. De plus, Memcached est souvent utilisé comme support de stockage pour le partage de données entre serveurs. Par exemple, les données qui enregistrent l'état d'authentification unique du système dans un système SSO peuvent être enregistrées dans Memcached et partagées par plusieurs applications.
Il convient de noter que memcached utilise la mémoire pour gérer les données, elle est donc volatile. Lorsque le serveur est redémarré ou que le processus memcached est terminé, les données seront perdues, donc memcached ne peut pas être utilisé pour conserver les données. Beaucoup de gens comprennent à tort que les performances de Memcached sont très bonnes, aussi bonnes que la comparaison entre la mémoire et le disque dur. En fait, Memcached n'obtiendra pas des centaines ou des milliers d'améliorations de la vitesse de lecture et d'écriture en utilisant la mémoire. Son véritable goulot d'étranglement réside dans le réseau. connexion, qui est liée à l'utilisation de la mémoire. Par rapport au système de base de données sur disque, l'avantage est qu'il est très « léger ». Parce qu'il n'y a pas de surcharge excessive et de méthodes de lecture et d'écriture directes, il peut facilement gérer une très grande quantité. d'échange de données, il y a donc souvent deux bandes passantes réseau Gigabit. Elles sont toutes entièrement chargées et le processus memcached lui-même n'occupe pas beaucoup de ressources CPU.
◎Comment fonctionne Memcached
Dans les sections suivantes, il est préférable que les lecteurs préparent une copie du code source de memcached.
Memcached est un programme de service réseau traditionnel Si le paramètre -d est utilisé lors du démarrage, il sera exécuté en tant que processus démon. La création d'un processus démon est réalisée par daemon.c. Ce programme n'a qu'une seule fonction démon, qui est très simple (si aucune instruction spéciale n'est donnée, le code doit être soumis à 1.2.1) :
CODE : [Copier dans le presse-papiers]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
démon (nochdir, noclose)
int nochdir, noclose;
{
int fd;
commutateur (fourche()) {
cas-1 :
retour (-1);
cas 0 :
casser;
défaut:
_exit(0);
}
si (setsid() == -1)
retourner (-1);
si (!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);
si (fd > STDERR_FILENO)
(vide)fermer(fd);
}
retourner (0);
}
Une fois que cette fonction a terminé l'ensemble du processus, le processus parent se termine, puis déplace STDIN, STDOUT et STDERR vers des périphériques vides, et le démon est établi avec succès.
Le processus de démarrage de Memcached lui-même est le suivant dans la fonction principale de memcached.c :
1. Appelez settings_init() pour définir les paramètres d'initialisation.
2. Lisez les paramètres de la commande de démarrage pour définir la valeur de réglage
3. Définir le paramètre LIMIT
4. Démarrez la surveillance des sockets réseau (s'il n'existe pas de chemin de socket) (le mode UDP est pris en charge après la version 1.2)
5. Vérifiez l'identité de l'utilisateur (Memcached ne permet pas de démarrer l'identité root)
6. Si socketpath existe, ouvrez la connexion locale UNIX (Sock pipe)
7. S'il est démarré en mode -d, créez un processus démon (appelez la fonction démon comme ci-dessus)
8. Initialiser l'élément, l'événement, les informations d'état, le hachage, la connexion, la dalle
9. Si la gestion prend effet dans les paramètres, créez un tableau de compartiments
10. Vérifiez si la page mémoire doit être verrouillée
11. Initialiser le signal, la connexion, supprimer la file d'attente
12. Si vous êtes en mode démon, traitez l'ID du processus
13. L'événement démarre, le processus de démarrage se termine et la fonction principale entre dans la boucle.
En mode démon, comme stderr a été dirigé vers le trou noir, aucun message d'erreur visible lors de l'exécution ne sera renvoyé.
La fonction de boucle principale de memcached.c est drive_machine. Le paramètre entrant est un pointeur de structure pointant vers la connexion actuelle et l'action est déterminée en fonction de l'état du membre d'état.
Memcached utilise un ensemble de protocoles personnalisés pour effectuer l'échange de données. Son document de protocole peut être consulté : http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
Dans l'API, les symboles de nouvelle ligne. sont unifiés en tant que rn
◎Méthode de gestion de la mémoire de Memcached
Memcached a une méthode de gestion de la mémoire tout à fait unique. Afin d'améliorer l'efficacité, il utilise des méthodes de pré-application et de regroupement pour gérer l'espace mémoire, au lieu de malloc à chaque fois qu'il a besoin d'écrire des données. . , libère un pointeur lors de la suppression de données. Memcached utilise la méthode d'organisation slab->chunk pour gérer la mémoire.
Il existe quelques différences dans les algorithmes de division spatiale des dalles dans slabs.c dans les versions 1.1 et 1.2, qui seront présentées séparément plus tard.
La dalle peut être comprise comme un bloc de mémoire. Une dalle est la plus petite unité que Memcached peut demander en même temps. Dans Memcached, la taille par défaut d'une dalle est de 1 048 576 octets (1 Mo), donc Memcached utilise la totalité de la mémoire. Chaque dalle est divisée en plusieurs morceaux, et chaque morceau stocke un élément. Chaque élément contient également la structure, la clé et la valeur de l'élément (notez que la valeur dans memcached n'est qu'une chaîne). Les dalles forment des listes chaînées en fonction de leurs propres identifiants, et ces listes chaînées sont accrochées à un tableau slabclass en fonction de leurs identifiants. La structure entière ressemble un peu à un tableau bidimensionnel. La longueur de la classe de dalle est de 21 en 1.1 et de 200 en 1.2.
la dalle a une taille de bloc initiale, qui est de 1 octet en 1.1 et de 80 octets en 1.2. Il y a une valeur de facteur dans 1.2, qui est par défaut de 1.25.
Dans 1.1, la taille du bloc est exprimée sous la forme de taille initiale * 2^n, n est. classid, c'est-à-dire : la dalle avec l'identifiant 0 a une taille de bloc de 1 octet, la dalle avec l'identifiant 1 a une taille de bloc de 2 octets, la dalle avec l'identifiant 2 a une taille de bloc de 4 octets... la dalle avec l'identifiant 20 a une taille de bloc de 4 octets. La taille est de 1 Mo, ce qui signifie qu'il n'y a qu'un seul bloc dans la dalle avec l'ID 20 :
CODE : [Copier dans le presse-papier]void slabs_init (limite de taille_t) {
int je;
int taille=1 ;
mem_limit = limite ;
pour(i=0; i<=POWER_LARGEST; i++, taille*=2) {
classe de dalle[i].size = taille;
slabclass[i].perslab = POWER_BLOCK / taille ;
classe de dalle[i].slots = 0 ;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
classe de dalle[i].end_page_ptr = 0;
classe de dalle[i].end_page_free = 0;
classe de dalle[i].slab_list = 0;
classe de dalle[i].list_size = 0;
classe de dalle[i].killing = 0;
}
/* pour la suite de tests : simuler combien nous avons déjà mallocé */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
si (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* pré-allouer les dalles par défaut, sauf si la variable d'environnement
pour les tests est défini sur quelque chose de non nul */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
si (!pre_alloc || atoi(pre_alloc)) {
slabs_preallocate(limite / POWER_BLOCK);
}
}
}
Dans 1.2, la taille du bloc est exprimée comme la taille initiale * f^n, f est le facteur défini dans memcached.c et n est classid. En même temps, les 201 têtes n'ont pas besoin d'être initialisées car le facteur. est variable et l'initialisation boucle uniquement sur La taille calculée atteint la moitié de la taille de la dalle, et elle commence à partir de l'id1, c'est-à-dire : dalle avec l'identifiant 1, chaque taille de morceau est de 80 octets, dalle avec l'identifiant 2, chaque taille de morceau est de 80* f, l'identifiant est de 3 dalles, chaque taille de morceau est de 80*f^2 et la taille d'initialisation a une valeur de correction CHUNK_ALIGN_BYTES pour garantir l'alignement sur n octets (garantissant que le résultat est un multiple intégral de CHUNK_ALIGN_BYTES). De cette façon, dans des circonstances standard, memcached1.2 sera initialisé à id40. La taille de chaque morceau de cette dalle est de 504 692 et il y a deux morceaux dans chaque dalle. Enfin, la fonction slab_init ajoutera un id41 à la fin, qui est un bloc entier, c'est-à-dire qu'il n'y a qu'un seul morceau de 1 Mo dans cette dalle :
CODE :[Copier dans le presse-papiers]void slabs_init (limite de taille_t, facteur double) {
int je = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* Un facteur de 2,0 signifie utiliser le comportement memcached par défaut */
si (facteur == 2,0 && taille < 128)
taille = 128 ;
mem_limit = limite ;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* Assurez-vous que les éléments sont toujours alignés sur n octets */
si (taille % CHUNK_ALIGN_BYTES)
taille += CHUNK_ALIGN_BYTES - (taille % CHUNK_ALIGN_BYTES)
;
slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
taille *= facteur ;
if (settings.verbose > 1) {
fprintf(stderr, "classe de dalle %3d : taille de bloc %6d perslab %5dn",
je, classe de dalle[i].size, classe de dalle[i].perslab);
}
}
power_largest = je;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* pour la suite de tests : simuler combien nous avons déjà mallocé */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
si (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREELLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
si (!pre_alloc || atoi(pre_alloc)) {
dalles_preallocate(limite / POWER_BLOCK);
}
}
#endif
}
Comme le montre ce qui précède, l'allocation de mémoire de memcached est redondante. Lorsqu'une dalle n'est pas divisible par la taille du morceau qu'elle possède, l'espace restant à la fin de la dalle est supprimé. Par exemple, dans id40, deux morceaux occupent. 1009384 octets, cette dalle a un total de 1 Mo, donc 39 192 octets sont gaspillés.
Memcached utilise cette méthode pour allouer de la mémoire afin de localiser rapidement l'identifiant de classe de la dalle grâce à la longueur de l'élément. C'est un peu similaire au hachage, car la longueur de l'élément peut être calculée. Par exemple, la longueur d'un élément est de 300 octets. Vous pouvez obtenir qu'il doit être stocké dans la dalle de id7, car selon la méthode de calcul ci-dessus, la taille du bloc de id6 est de 252 octets, la taille du bloc de id7 est de 316 octets et la taille du bloc de id8 est de 396 octets, ce qui signifie que tous les éléments de 252 à 316 octets doivent être stockés dans id7. De même, dans la version 1.1, on peut également calculer qu'il est compris entre 256 et 512, et doit être placé dans id9 avec un chunk_size de 512 (système 32 bits).
Lorsque Memcached est initialisé, les slabs seront initialisés (comme vous pouvez le voir précédemment, slabs_init() est appelé dans la fonction principale). Il vérifiera une constante DONT_PREELOC_SLABS dans slabs_init(). Si celle-ci n'est pas définie, cela signifie que la dalle est initialisée en utilisant la mémoire pré-allouée, de sorte qu'une dalle est créée pour chaque ID parmi toutes les classes de dalle qui ont été définies. Cela signifie que 1.2 allouera 41 Mo d'espace de dalle après le démarrage du processus dans l'environnement par défaut. Au cours de ce processus, la deuxième redondance de mémoire de Memcached se produit, car il est possible qu'un identifiant n'ait pas été utilisé du tout, mais il s'agit également d'un A. la dalle est demandée par défaut, et chaque dalle utilisera 1 Mo de mémoire
lorsqu'une dalle est épuisée et qu'un nouvel élément doit être inséré avec cet ID, elle fera une nouvelle demande pour une nouvelle dalle lors de la demande d'une nouvelle dalle. slab, l'ID correspondant La liste chaînée des dalles va s'agrandir. Cette liste chaînée va croître de façon exponentielle. Dans la fonction grow_slab_list, la longueur de cette chaîne passe de 1 à 2, de 2 à 4, de 4 à 8... :
CODE : [Copier dans le presse-papiers]static int grow_slab_list (identifiant int non signé) {
slabclass_t *p = &slabclass[id];
if (p->dalles == 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) renvoie 0 ;
p->list_size = new_size;
p->slab_list = new_list;
}
renvoyer 1 ;
}
Lors de la localisation de l'élément, la fonction slabs_clsid est utilisée. Le paramètre entrant est la taille de l'élément et la valeur de retour est l'identifiant de classe. À partir de ce processus, on peut voir que la troisième redondance de mémoire de memcached se produit lors du processus de sauvegarde de l'élément. L'élément est toujours plus petit ou égal à la taille du morceau. Lorsque l'élément est plus petit que la taille du morceau, de l'espace est à nouveau gaspillé.
◎Algorithme NewHash de Memcached
Le stockage des éléments de Memcached est basé sur une grande table de hachage. Son adresse réelle est le décalage du bloc dans la dalle, mais son positionnement dépend du résultat du hachage de la clé, qui se trouve dans la table de hachage primaire. Toutes les opérations de hachage et d'élément sont définies dans assoc.c et items.c.
Memcached utilise un algorithme appelé NewHash, qui est très efficace et efficient. Il existe quelques différences entre NewHash dans 1.1 et 1.2. La méthode d'implémentation principale est toujours la même. La fonction de hachage de 1.2 a été organisée et optimisée, et son adaptabilité est meilleure.
Référence du prototype NewHash : http://burtleburtle.net/bob/hash/evahash.html . Les mathématiciens sont toujours un peu étranges, haha~
Afin de faciliter la conversion, deux types de données, u4 et u1, sont définis. u4 est un entier long non signé et u1 est un caractère non signé (0-255).
Pour des codes spécifiques, veuillez vous référer aux packages de codes sources 1.1 et 1.2.
Faites attention à la longueur de la table de hachage ici. Il y a également une différence entre 1.1 et 1.2. Dans 1.1, la constante HASHPOWER est définie sur 20 et la longueur de la table de hachage est la taille de hachage (HASHPOWER), qui est de 4 Mo (la taille de hachage est une macro, indiquant que 1 est décalé vers la droite de n bits). Dans 1.2, il s'agit de la variable 16, c'est-à-dire que la longueur de la table de hachage est de 65536 :
CODE : [Copier dans le presse-papiers]typedef unsigned long int ub4 /* quantités non signées de 4 octets */
typedef unsigned char ub1; /* quantités non signées de 1 octet */
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)
Dans assoc_init(), la Primary_hashtable sera initialisée. Les opérations de hachage correspondantes incluent : assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete(), correspondant aux opérations de lecture et d'écriture de l'élément. Parmi eux, assoc_find() est une fonction qui trouve l'adresse de l'élément correspondant en fonction de la clé et de la longueur de la clé (notez qu'en C, plusieurs fois la chaîne et la longueur de la chaîne sont directement transmises en même temps au lieu de faire strlen à l'intérieur de la fonction ), et ce qui est renvoyé est un pointeur de structure d'élément, son adresse de données se trouve sur un morceau de la dalle.
items.c est le programme d'exploitation pour les éléments de données. Chaque élément complet comprend plusieurs parties, qui sont définies dans item_make_header() comme :
clé : clé.
nkey : longueur de la clé
flags : indicateur défini par l'utilisateur (en fait, cet indicateur n'est pas activé dans memcached)
nbytes : longueur de la valeur (y compris le symbole de nouvelle ligne rn)
suffixe : suffixe tampon
nsuffixe : La
longueur du suffixe d'un élément complet est la longueur de la clé + la longueur de la valeur + la longueur du suffixe + la taille de la structure de l'élément (32 octets). L'opération de l'élément est basée sur cette longueur pour calculer l'identifiant de classe de la dalle.
Chaque compartiment de la table de hachage est accompagné d'une liste à double chaînage. Lors de item_init(), les trois tableaux de têtes, de queues et de tailles ont été initialisés à 0. Les tailles de ces trois tableaux sont la constante LARGEST_ID (la valeur par défaut est 255, cette valeur doit être modifiée avec un facteur), chaque fois que item_assoc() est appelé, il essaiera d'abord d'obtenir un morceau libre de la dalle. S'il n'y a pas de morceau disponible, il analysera la liste chaînée 50 fois pour obtenir un morceau qui était. lancé par l'élément LRU, dissociez-le, puis insérez l'élément à insérer dans la liste chaînée.
Faites attention au membre de refcount de l'article. Une fois que l'élément est dissocié, il est uniquement supprimé de la liste liée. Il n'est pas libéré immédiatement. Il est simplement placé dans la file d'attente de suppression (fonction item_unlink_q()).
L'élément correspond à certaines opérations de lecture et d'écriture, notamment la suppression, la mise à jour et le remplacement. Bien entendu, la plus importante est l'opération d'allocation.
Une autre caractéristique de l'élément est qu'il a un délai d'expiration, ce qui est une fonctionnalité très utile de memcached. De nombreuses applications dépendent de l'expiration des éléments de memcached, comme le stockage de session, les verrous d'opération, etc. La fonction item_flush_expired() analyse les éléments de la table et effectue une opération de dissociation sur les éléments expirés. Bien sûr, il ne s'agit que d'une action de recyclage. En fait, un jugement temporel est également nécessaire lors de l'obtention :
CODE : [Copier dans le presse-papiers]/* fait expirer les éléments plus récents que le paramètre plus ancien_live */.
void item_flush_expired() {
int je;
élément *iter, *suivant ;
si (! settings.oldest_live)
retour;
pour (i = 0; i < LARGEST_ID; i++) {
/* Le LRU est trié par ordre temporel décroissant et l'horodatage d'un élément
* n'est jamais plus récent que sa dernière heure d'accès, il suffit donc de marcher
* jusqu'à ce que nous atteignions un élément plus ancien que l'heure de diffusion la plus ancienne.
* La vérification Old_live fera expirer automatiquement les éléments restants.
*/
pour (iter = têtes[i]; iter != NULL; iter = next) {
if (iter->time >= settings.oldest_live) {
suivant = iter->suivant ;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} autre {
/* Nous avons atteint le premier ancien élément. Passons à la file d'attente suivante */.
casser;
}
}
}
}
CODE : [Copier dans le presse-papiers]/* wrapper autour d'assoc_find qui effectue la logique d'expiration/suppression paresseuse */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
élément *it = assoc_find(key, nkey);
si (delete_locked) *delete_locked = 0 ;
if (it && (it->it_flags & ITEM_DELETED)) {
/* il est marqué comme supprimé-verrouillé, voyons si cette condition est remplie.
est en retard, et le delete_timer de 5 secondes n'est tout simplement pas arrivé
J'y suis encore arrivé... */
si (!item_delete_lock_over(it)) {
si (delete_locked) *delete_locked = 1 ;
il = 0 ;
}
}
if (it && settings.oldest_live && settings.oldest_live <= current_time &&
it->heure <= settings.oldest_live) {
item_unlink(it);
il = 0 ;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink(it);
il = 0 ;
}
retournez-le ;
}
La méthode de gestion de la mémoire de Memcached est très sophistiquée et efficace. Elle réduit considérablement le nombre d'allocations directes de mémoire système, réduit la surcharge des fonctions et la probabilité de fragmentation de la mémoire. Bien que cette méthode entraîne un certain gaspillage redondant, ce gaspillage est trivial dans un grand système. candidatures.
◎La méthode de calcul des paramètres théoriques de Memcached
comporte plusieurs paramètres qui affectent le travail de memcached :
constante REALTIME_MAXDELTA 60*60*24*30
Délai d'expiration maximum de 30 jours
freetotal (=200) dans conn_init()
Nombre maximum de connexions simultanées
constante KEY_MAX_LENGTH 250
de longueur de clé maximale.facteur
(= 1,25)
le facteur affectera la taille du pas de chunk
settings.maxconns (= 1024)
de connexion logicielle maximum.chunk_size
(=48)
Une longueur clé+valeur estimée de manière prudente, utilisée pour générer la longueur du bloc (1.2) dans id1. La longueur du bloc de id1 est égale à cette valeur plus la longueur de la structure d'élément (32), qui est la valeur par défaut de 80 octets.
Constante POWER_SMALLEST 1
Classid minimum (1.2)
constante POWER_LARGEST 200
de classe maximale (1.2)
POWER_BLOCK 1048576
de taille de dalle par défaut
CHUNK_ALIGN_BYTES (sizeof(void *))
Assurez-vous que la taille du bloc est un multiple entier de cette valeur pour éviter les dépassements (la longueur de void * est différente selon les systèmes, elle est de 4 sur les systèmes 32 bits standard)
constante ITEM_UPDATE_INTERVAL 60
d'intervalle d'actualisation de la file d'attente
LARGEST_ID 255
Nombre maximum d'éléments dans la liste chaînée (cette valeur ne peut pas être inférieure au plus grand identifiant de classe)
puissance de hachage variable (HASHPOWER constante en 1.1)
Détermination de la taille de la table de hachage.
Sur la base du contenu et des paramètres présentés ci-dessus, certains résultats peuvent être calculés :
1. Il n'y a pas de limite supérieure logicielle pour le nombre d'éléments pouvant être enregistrés dans Memcached. faux.
2. En supposant que l'algorithme NewHash ait des collisions uniformes, le nombre de cycles pour trouver un élément est le nombre total d'éléments divisé par la taille de la table de hachage (déterminée par la puissance de hachage), qui est linéaire.
3. Memcached limite l'élément maximum acceptable à 1 Mo et les données supérieures à 1 Mo seront ignorées.
4. L'utilisation de l'espace par Memcached est étroitement liée aux caractéristiques des données et est également liée à la constante DONT_PREELLOC_SLABS. Dans le pire des cas, 198 dalles seront gaspillées (tous les éléments sont concentrés dans une seule dalle et les 199 identifiants sont entièrement attribués).
◎Optimisation de longueur fixe de Memcached
Sur la base des descriptions dans les sections ci-dessus, j'ai une compréhension plus approfondie de Memcached. Il ne peut être optimisé que sur la base d’une compréhension approfondie.
Memcached lui-même est conçu pour des données de longueur variable. Selon les caractéristiques des données, on peut dire qu'il s'agit d'une conception « orientée vers le public ». Cependant, dans de nombreux cas, nos données ne sont pas aussi « universelles ». distribution non uniforme, c'est-à-dire que la longueur des données est concentrée dans plusieurs domaines (comme la sauvegarde des sessions utilisateur), l'autre état plus extrême est celui des données de longueur égale (telles que les valeurs de clé de longueur fixe, les données de longueur fixe, principalement) ; vu dans l'accès, les statistiques en ligne ou les verrous d'exécution).
Ici, nous étudions principalement la solution d'optimisation pour les données de longueur fixe (1.2). Les données centralisées distribuées de longueur variable sont à titre de référence uniquement et sont faciles à mettre en œuvre.
Pour résoudre des données de longueur fixe, la première chose à résoudre est le problème d'allocation des dalles. La première chose qui doit être confirmée est que nous n'avons pas besoin d'autant de dalles avec des longueurs de morceaux différentes afin de maximiser l'utilisation. des ressources, il est préférable que les morceaux et les éléments soient de longueur égale, donc commencez par calculer la longueur de l'élément.
Il existe déjà un algorithme pour calculer la longueur des éléments. Il convient de noter qu'en plus de la longueur de la chaîne, la longueur de la structure des éléments de 32 octets doit être ajoutée.
Supposons que nous ayons calculé que nous devons sauvegarder 200 octets de données de longueur égale.
L'étape suivante consiste à modifier la relation entre l'identifiant de classe de la dalle et la longueur du bloc. Dans la version originale, il existe une relation correspondante entre la longueur du morceau et l'identifiant de classe. Maintenant, si tous les morceaux sont définis sur 200 octets, alors cette relation n'existe pas. Une méthode consiste à utiliser uniquement un ID fixe pour l'ensemble de la structure de stockage, c'est-à-dire à n'utiliser qu'un seul des 199 emplacements. Dans cette condition, DONT_PREELOC_SLABS doit être défini pour éviter un gaspillage supplémentaire de pré-allocation. Une autre méthode consiste à établir une relation de hachage pour déterminer l'ID de classe à partir de l'élément. Vous ne pouvez pas utiliser la longueur comme clé. Vous pouvez utiliser des données variables telles que le résultat NewHash de la clé, ou effectuer directement le hachage en fonction de la clé (la clé). les clés de données de longueur fixe doivent également être de la même longueur). Par souci de simplicité, nous choisissons ici la première méthode. L'inconvénient de cette méthode est qu'un seul identifiant est utilisé lorsque la quantité de données est très importante, la chaîne de dalles sera très longue (car toutes les données sont encombrées). une chaîne). Le coût de traversée est relativement élevé.
Les trois types de redondance d'espace ont été introduits précédemment. Définir la longueur du morceau égale à la longueur de l'élément résout le premier problème de perte d'espace. Ne pas demander d'espace à l'avance résout le deuxième problème de perte d'espace. Alors qu'en est-il du premier problème (restant dans la dalle). ) ? Pour résoudre ce problème, vous devez modifier la constante POWER_BLOCK afin que la taille de chaque dalle soit exactement égale à un multiple entier de la longueur du morceau, afin qu'une dalle puisse être divisée en n morceaux. Cette valeur doit être plus proche de 1 Mo. Si elle est trop grande, cela entraînera également une redondance. Si elle est trop petite, cela entraînera trop d'allocations. Selon la longueur du bloc de 200, choisissez 1 000 000 comme valeur de POWER_BLOCK. de cette façon, une dalle fera 1 million d'octets, et non 1048576. Les trois problèmes de redondance sont résolus et l'utilisation de l'espace sera grandement améliorée.
Modifiez la fonction slabs_clsid pour qu'elle renvoie directement une valeur fixe (telle que 1) :
CODE : [Copier dans le presse-papiers]unsigned int slabs_clsid(size_t size) {
renvoyer 1 ;
}
Modifiez la fonction slabs_init, supprimez la partie qui boucle pour créer tous les attributs classid et ajoutez directement slabclass[1] :
CODE : [Copier dans le presse-papier]slabclass[1].size = 200 ; //200 octets par bloc
classe de dalle[1].perslab = 5000; //1000000/200
◎Client Memcached Memcached
est un programme de service Lorsque vous l'utilisez, vous pouvez vous connecter au serveur Memcached selon son protocole, envoyer des commandes au processus de service, puis exploiter les données ci-dessus. Pour faciliter l'utilisation, memcached propose de nombreux programmes clients, correspondant à différentes langues, et il existe des clients dans différentes langues. Ceux basés sur le langage C incluent libmemcache et APR_Memcache ; ceux basés sur Perl incluent Cache::Memcached ; ils prennent également en charge Python, Ruby, Java, C# et d'autres langages. PHP possède le plus de clients, non seulement les deux extensions mcache et PECL memcache, mais aussi un grand nombre de classes d'encapsulation écrites par PHP. Voici une introduction à l'utilisation de memcache en PHP :
L'extension mcache est réencapsulée sur la base de libmemcache. . libmemcache n'a pas publié de version stable. La version actuelle est la 1.4.0-rc2, qui peut être trouvée ici. Une très mauvaise fonctionnalité de libmemcache est qu'il écrit beaucoup de messages d'erreur dans stderr. Généralement, lorsqu'il est utilisé comme bibliothèque, stderr est généralement dirigé vers d'autres endroits, tels que le journal des erreurs d'Apache, et libmemcache se suicidera, ce qui peut provoquer des anomalies. , mais ses performances restent très bonnes.
L'extension mcache a été mise à jour pour la dernière fois vers la version 1.2.0-beta10. L'auteur a probablement démissionné. Non seulement il a arrêté la mise à jour, mais il n'a pas non plus pu ouvrir le site Web (~_~). . Après décompression, la méthode d'installation est la suivante : phpize & configure & make & make install. Assurez-vous d'abord d'installer libmemcache. Utiliser cette extension est simple :
CODE :[Copier dans le presse-papier]<?php
$mc = memcache(); // Crée un objet de connexion memcache. Notez que new n'est pas utilisé ici !
$mc->add_server('localhost', 11211); // Ajouter un processus de service
$mc->add_server('localhost', 11212); // Ajouter un deuxième processus de service
$mc->set('key1', 'Bonjour'); // Écrire key1 => Bonjour
$mc->set('key2', 'World', 10); // Write key2 => World, expire dans 10 secondes
$mc->set('arr1', array('Hello', 'World')); // Écrit un tableau
$key1 = $mc->get('key1'); // Récupère la valeur de 'key1' et l'attribue à $key1
$key2 = $mc->get('key2'); // Récupère la valeur de 'key2' et attribue-la à $key2 Si elle dépasse 10 secondes, elle ne sera pas disponible.
$arr1 = $mc->get('arr1'); // Récupère le tableau 'arr1'
$mc->delete('arr1'); // Supprimer 'arr1'
$mc->flush_all(); // Supprime toutes les données
$stats = $mc->stats(); // Récupère les informations sur le serveur
var_dump($stats); // Les informations sur le serveur sont un tableau
?>
L'avantage de cette extension est qu'elle peut facilement implémenter le stockage distribué et l'équilibrage de charge, car elle peut ajouter plusieurs adresses de service, lors de la sauvegarde des données, elles seront localisées sur un certain serveur en fonction du résultat du hachage. C'est également une fonctionnalité de libmemcache. . libmemcache prend en charge les méthodes de hachage centralisées, notamment le hachage CRC32, ELF et Perl.
PECL memcache est une extension publiée par PECL. La dernière version est la 2.1.0, qui peut être obtenue sur le site Web de pecl. L'utilisation de l'extension memcache peut être trouvée dans certains manuels PHP plus récents. Elle est très similaire à mcache, vraiment similaire :
CODE :[Copier dans le presse-papiers]<?php
$memcache = new Memcache ;
$memcache->connect('localhost', 11211) ou die ("Impossible de se connecter");
$version = $memcache->getVersion();
echo "Version du serveur : ".$version."n" ;
$tmp_object = new stdClass ;
$tmp_object->str_attr = 'test';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) ou die ("Échec de la sauvegarde des données sur le serveur");
echo "Stocker les données dans le cache (les données expireront dans 10 secondes)n"
$get_result = $memcache->get('key');
echo "Données du cache :n";
var_dump($get_result
?>);
Cette extension utilise le flux PHP pour se connecter directement au serveur memcached et envoyer des commandes via le socket. Il n'est pas aussi complet que libmemcache et ne prend pas en charge les opérations distribuées telles que add_server, mais comme il ne s'appuie pas sur d'autres programmes externes, il a une meilleure compatibilité et est relativement stable. Quant à l'efficacité, la différence n'est pas énorme.
De plus, de nombreuses classes PHP sont disponibles, telles que MemcacheClient.inc.php, et beaucoup peuvent être trouvées sur phpclasses.org. Elles sont généralement une réencapsulation de l'API du client Perl et sont utilisées de manière similaire.
◎BSM_Memcache
Du point de vue d'un client C, APR_Memcache est un programme client très mature et stable qui prend en charge les verrouillages de thread et les opérations au niveau atomique pour garantir la stabilité opérationnelle. Cependant, il est basé sur APR (APR sera introduit dans la dernière section) et n'a pas une gamme d'applications aussi large que LibMemCache. car il ne peut pas fonctionner en dehors de l'environnement APR. Cependant, APR peut être installé séparément d'Apache.
BSM_MEMCACH est une extension PHP basée sur APR_MEMCACH que j'ai développé dans le projet Bs.Magic. Ce programme est très simple et ne fait pas trop de fonctions.
Différent de l'extension MCache qui prend en charge le stockage distribué multi-serveur, BSM_MEMCACH prend en charge plusieurs groupes de serveurs. , la sauvegarde chaude est implémentée. Bien sûr, le coût de la mise en œuvre de cette fonction est le sacrifice des performances. Normalement, vous pouvez l'obtenir la prochaine fois.
BSM_MEMCACH ne prend en charge que ces fonctions:
Code: [Copier dans le presse-papiers] 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}
} ;
La fonction MC_ADD_GROUP renvoie un entier (en fait, il devrait être un objet, j'étais paresseux ~ _ ~) comme l'ID de groupe. Addrort).
Code: [Copier dans le presse-papiers] / **
* Ajouter un groupe de serveurs
*/
Php_function (mc_add_group)
{
APR_INT32_T GROUP_ID;
APR_STATUS_T RV
;
{
WRONG_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: [Copier dans le presse-papiers] / **
* Ajouter un serveur dans le groupe
*/
Php_function (mc_add_server)
{
APR_STATUS_T RV;
APR_INT32_T GROUP_ID;
Double G;
char * srv_str;
int srv_str_l
;
{
WRONG_PARAM_COUNT ;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g, & srv_str, & srv_str_l) == échec)
{
Return_false;
}
group_id = (apr_int32_t) g
;
{
Return_false;
}
char * hôte, * scope;
APR_PORT_T PORT
;
if (APR_SUCCESS == RV)
{
// Créez cet objet serveur
APR_MEMCACHE_SERVER_T * ST;
RV = APR_MEMCACHE_SERVER_CREATE (p, hôte, port, 0, 64, 1024, 600, & st);
if (APR_SUCCESS == RV)
{
if (null == mc_groups [group_id])
{
Return_false;
}
// Ajouter un serveur
RV = APR_MEMCACHE_ADD_SERVER (MC_GROUPS [GROUPS_ID], ST)
;
{
Return_true;
}
}
}
Return_false;
}
Lors de la définition et de la suppression des données, parcourez tous les groupes:
Code: [Copier dans le presse-papiers] / **
* Stockez l'article dans tous les groupes
*/
Php_function (mc_set)
{
CHAR * KEY, * valeur;
int key_l, value_l;
double ttl = 0;
double set_ct = 0
;
{
WRONG_PARAM_COUNT ;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == échec)
{
Return_false;
}
// Écrivez des données dans chaque objet
APR_INT32_T I = 0;
if (ttl <0)
{
ttl = 0;
}
APR_STATUS_T RV
;
{
if (0 == is_validate_group (i))
{
// Écrivez-le!
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);
}
Dans MC_GET, vous sélectionnez d'abord un groupe au hasard, puis commencez à interroger ce groupe: vous
Code: [Copier dans le presse-papiers] / **
* Récupérer un élément d'un groupe aléatoire
*/
Php_function (mc_get)
{
CHAR * KEY, * valeur = null;
int key_l;
APR_SIZE_T Value_l
;
{
WRONG_PARAM_COUNT ;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == échec)
{
Return_mull ();
}
// J'essaierai...
// lecture aléatoire
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
;
{
essai = i + curr_group_id;
try = try% max_group;
if (0 == is_validate_group (try))
{
// Obtenez une valeur
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 (valeur, 1);
}
}
}
Return_false;
}
Code: [Copier dans le presse-papiers] / **
* ID de groupe aléatoire
* Pour mc_get ()
*/
APR_int32_t random_group ()
{
Struct TimeVal TV;
Strust Timezone TZ;
int usec;
(
& tv, & tz);
usec = tv.tv_usec
;
}
L'utilisation de BSM_MEMCACH est similaire à d'autres clients:
Code: [Copier dans le presse-papiers] <? 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 ();
?>
Des informations pertinentes sur APR_MEMCACHE peuvent être trouvées ici, et BSM_MEMCACH peut être téléchargé à partir de ce site.
◎ APR Environment Introduction
Le nom complet d'APR: APCAChe Portable Runtime. Il s'agit d'un ensemble de bibliothèques de langage C multiplateforme créées et maintenues par l'Apache Software Foundation. Il est extrait d'Apache httpd1.x et est indépendant de Httpd. APR fournit de nombreuses interfaces API pratiques à utiliser, y compris des fonctions pratiques telles que les pools de mémoire, les opérations de chaîne, les réseaux, les tableaux, les tables de hachage, etc. Le développement du module Apache2 nécessite une exposition à de nombreuses fonctions APR.
◎ PostScript
Ceci est mon dernier article dans l'année Bingxu du calendrier lunaire (mon année de naissance). Merci à Sina.com d'avoir offert des opportunités de recherche et des collègues du département pour leur aide.
Dr NP02-13-2007