Memcached es un sistema de almacenamiento en caché de objetos de memoria distribuida desarrollado por danga.com (el equipo técnico que opera LiveJournal) para reducir la carga de la base de datos y mejorar el rendimiento en sistemas dinámicos. Con respecto a esto, creo que mucha gente lo ha usado. El propósito de este artículo es obtener una comprensión más profunda de este excelente software de código abierto a través de la implementación y el análisis de código de Memcached, y optimizarlo aún más de acuerdo con nuestras necesidades. Finalmente, a través del análisis de la extensión BSM_Memcache, profundizaremos nuestra comprensión del uso de memcached.
Parte del contenido de este artículo puede requerir una mejor base matemática como ayuda.
◎¿Qué es Memcached?
Antes de profundizar en este tema, primero debemos entender qué "no es". Mucha gente lo usa como un medio de almacenamiento como SharedMemory. Aunque Memcached usa el mismo método "Clave => Valor" para organizar los datos, es muy diferente de los cachés locales como la memoria compartida y APC. Memcached se distribuye, lo que significa que no es local. Completa el servicio en función de la conexión de red (por supuesto, también puede utilizar localhost). Es un programa o proceso demonio independiente de la aplicación (modo demonio).
Memcached utiliza la biblioteca libevent para implementar servicios de conexión de red y, en teoría, puede manejar un número ilimitado de conexiones. Sin embargo, a diferencia de Apache, suele estar orientado a conexiones continuas estables, por lo que sus capacidades de concurrencia reales son limitadas. En circunstancias conservadoras, el número máximo de conexiones simultáneas para Memcached es 200, lo que está relacionado con la capacidad del subproceso de Linux. Este valor se puede ajustar. Para obtener información sobre libevent, consulte la documentación relevante. El uso de la memoria de Memcached también es diferente al de APC. APC se basa en memoria compartida y MMAP tiene su propio algoritmo de asignación de memoria y método de administración. No tiene nada que ver con la memoria compartida y no tiene restricciones en la memoria compartida. Normalmente, cada proceso de Memcached puede administrar 2 GB de espacio de memoria. Si se necesita más espacio, se puede aumentar el número de procesos.
◎¿Para qué ocasiones es adecuado Memcached?
En muchos casos, se ha abusado de Memcached, lo que, por supuesto, conduce inevitablemente a quejas al respecto. A menudo veo personas que publican en foros cosas similares a "cómo mejorar la eficiencia" y la respuesta es "usar memcached". En cuanto a cómo usarlo, dónde usarlo y para qué sirve, no hay ninguna oración. Memcached no es una panacea ni es adecuado para todas las situaciones.
Memcached es un sistema de almacenamiento en caché de objetos de memoria "distribuida", es decir, para aquellas aplicaciones que no necesitan ser "distribuidas", no necesitan ser compartidas o simplemente son lo suficientemente pequeñas como para tener un solo servidor, memcached no lo hará. No trae ningún beneficio. Por el contrario, también ralentiza la eficiencia del sistema porque las conexiones de red también requieren recursos, incluso las conexiones locales UNIX. Los datos de mis pruebas anteriores mostraron que las velocidades de lectura y escritura locales de Memcached son docenas de veces más lentas que las matrices de memoria PHP directas, mientras que los métodos APC y de memoria compartida son similares a las matrices directas. Se puede ver que si se trata solo de un caché a nivel local, usar Memcached es muy antieconómico.
Memcached se utiliza a menudo como caché frontal de base de datos. Debido a que tiene muchos menos análisis de SQL, operaciones de disco y otros gastos generales que una base de datos, y utiliza memoria para administrar los datos, puede proporcionar un mejor rendimiento que la lectura directa de la base de datos. En sistemas grandes, es muy difícil acceder a los mismos datos. Con frecuencia, Memcached puede reducir en gran medida la presión de la base de datos y mejorar la eficiencia de ejecución del sistema. Además, Memcached se utiliza a menudo como medio de almacenamiento para compartir datos entre servidores. Por ejemplo, los datos que guardan el estado de inicio de sesión único del sistema en un sistema SSO se pueden guardar en Memcached y compartir con varias aplicaciones.
Cabe señalar que Memcached usa memoria para administrar datos, por lo que es volátil. Cuando se reinicia el servidor o finaliza el proceso de Memcached, los datos se perderán, por lo que no se puede usar Memcached para conservar los datos. Mucha gente no entiende que el rendimiento de Memcached es muy bueno, tan bueno como la comparación entre la memoria y el disco duro. De hecho, Memcached no obtendrá cientos o miles de mejoras en la velocidad de lectura y escritura mediante el uso de la memoria. Conexión, que está relacionada con el uso de memoria. En comparación con el sistema de base de datos en disco, la ventaja es que es muy "ligero". Debido a que no requiere una sobrecarga excesiva y tiene métodos de lectura y escritura directos, puede manejar fácilmente una gran cantidad. del intercambio de datos, por lo que a menudo hay dos anchos de banda de red gigabit. Todos están completamente cargados y el proceso de Memcached en sí no ocupa muchos recursos de CPU.
◎Cómo funciona Memcached
En las siguientes secciones, es mejor que los lectores preparen una copia del código fuente de memcached.
Memcached es un programa de servicio de red tradicional. Si se utiliza el parámetro -d al iniciar, se ejecutará como un proceso demonio. La creación de un proceso de demonio se completa con daemon.c. Este programa tiene solo una función de demonio, que es muy simple (si no se dan instrucciones especiales, el código estará sujeto a 1.2.1):
CÓDIGO:[Copiar al portapapeles]#include <fcntl.h>
#incluir <stdlib.h>
#incluir <unistd.h>
int
demonio(nochdir, noclose)
int nochdir, nocerrar;
{
int fd;
cambiar (bifurcación()) {
caso-1:
devolver (-1);
caso 0:
romper;
por defecto:
_salir(0);
}
si (setsid() == -1)
devolver (-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)
(nulo)cerrar(fd);
}
devolver (0);
}
Después de que esta función bifurca todo el proceso, el proceso principal sale y luego reubica STDIN, STDOUT y STDERR en dispositivos vacíos y el demonio se establece exitosamente.
El proceso de inicio de Memcached en sí es el siguiente en la función principal de memcached.c:
1. Llame a settings_init() para configurar los parámetros de inicialización.
2. Lea los parámetros del comando de inicio para establecer el valor de configuración
3. Establecer el parámetro LÍMITE
4. Inicie la supervisión del socket de red (si existe una ruta que no sea el socket) (el modo UDP es compatible después de 1.2)
5. Verifique la identidad del usuario (Memcached no permite que se inicie la identidad raíz)
6. Si existe una ruta de conexión, abra la conexión local UNIX (tubería Sock)
7. Si se inicia en modo -d, cree un proceso demonio (llame a la función demonio como se indica arriba)
8. Inicializar elemento, evento, información de estado, hash, conexión, losa
9. Si administrado tiene efecto en la configuración, cree una matriz de depósitos.
10. Compruebe si es necesario bloquear la página de memoria.
11. Inicializar señal, conexión, eliminar cola
12. Si está en modo demonio, procesa la ID del proceso.
13. El evento comienza, el proceso de inicio finaliza y la función principal ingresa al ciclo.
En modo demonio, debido a que stderr ha sido dirigido al agujero negro, no se enviarán mensajes de error visibles durante la ejecución.
La función de bucle principal de memcached.c es drive_machine. El parámetro entrante es un puntero de estructura que apunta a la conexión actual y la acción se determina en función del estado del miembro del estado.
Memcached utiliza un conjunto de protocolos personalizados para completar el intercambio de datos. Puede consultar su documento de protocolo: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
En la API, los símbolos de nueva línea. están unificados como rn
◎Método de administración de memoria de Memcached
Memcached tiene un método de administración de memoria único. Para mejorar la eficiencia, utiliza métodos de agrupación y aplicación previa para administrar el espacio de memoria, en lugar de malloc cada vez que necesita escribir datos. , libera un puntero al eliminar datos. Memcached utiliza el método de organización slab->chunk para administrar la memoria.
Existen algunas diferencias en los algoritmos de división del espacio de losa en slabs.c en 1.1 y 1.2, que se presentarán por separado más adelante.
Losa puede entenderse como un bloque de memoria. Una losa es la unidad más pequeña que Memcached solicita memoria al mismo tiempo. En Memcached, el tamaño predeterminado de una losa es 1048576 bytes (1 MB), por lo que Memcached usa todo el MB de memoria. Cada losa se divide en varios fragmentos y cada fragmento almacena un elemento. Cada elemento también contiene la estructura, la clave y el valor del elemento (tenga en cuenta que el valor en Memcached es solo una cadena). Las losas forman listas vinculadas según sus propios ID, y estas listas vinculadas se cuelgan en una matriz de clase de losa según sus ID. Toda la estructura se parece un poco a una matriz bidimensional. La longitud de la clase de losa es 21 en 1,1 y 200 en 1,2.
la losa tiene un tamaño de fragmento inicial, que es 1 byte en 1.1 y 80 bytes en 1.2. Hay un valor de factor en 1.2, que por defecto es 1.25.
En 1.1, el tamaño del fragmento se expresa como tamaño inicial * 2^n, n es. classid, es decir: la losa con id 0 tiene un tamaño de fragmento de 1 byte, la losa con id 1 tiene un tamaño de fragmento de 2 bytes, la losa con id 2 tiene un tamaño de fragmento de 4 bytes... la losa con id 20 tiene un tamaño de fragmento de 4 bytes. El tamaño es de 1 MB, lo que significa que solo hay un fragmento en la losa con ID 20:
CÓDIGO:[Copiar al portapapeles]void slabs_init(size_t limit) {
ent i;
int tamaño=1;
mem_limit = límite;
for(i=0; i<=POWER_LARGEST; i++, tamaño*=2) {
clase de losa [i]. tamaño = tamaño;
claselosa[i].perslab = POWER_BLOCK / tamaño;
clase de losa[i].slots = 0;
slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;
clase de losa[i].end_page_ptr = 0;
clase de losa[i].end_page_free = 0;
claselosa[i].slab_list = 0;
clase de losa[i].list_size = 0;
clase de losa [i]. matanza = 0;
}
/* para el conjunto de pruebas: fingir cuánto hemos mallocado */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
si (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* preasignar losas de forma predeterminada, a menos que la variable de entorno
para las pruebas se establece en algo distinto de cero */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
si (!pre_alloc || atoi(pre_alloc)) {
losas_preallocate(límite/POWER_BLOCK);
}
}
}
En 1.2, el tamaño del fragmento se expresa como el tamaño inicial * f^n, f es el factor, que se define en memcached.c, y n es classid. Al mismo tiempo, no es necesario inicializar todos los 201 cabezales debido al factor. es variable y la inicialización solo se repite hasta El tamaño calculado alcanza la mitad del tamaño de la losa y comienza desde id1, es decir: losa con id 1, cada tamaño de fragmento es de 80 bytes, losa con id 2, cada tamaño de fragmento es 80* f, la identificación es 3 losas, el tamaño de cada fragmento es 80*f^2 y el tamaño de inicialización tiene un valor de corrección CHUNK_ALIGN_BYTES para garantizar la alineación de n bytes (lo que garantiza que el resultado sea un múltiplo integral de CHUNK_ALIGN_BYTES). De esta manera, en circunstancias estándar, memcached1.2 se inicializará en id40. El tamaño de cada fragmento en esta losa es 504692 y hay dos fragmentos en cada losa. Finalmente, la función slab_init agregará un id41 al final, que es un bloque completo, es decir, solo hay un fragmento de 1 MB en esta losa:
CÓDIGO:[Copiar al portapapeles]void slabs_init(size_t limit, double factor) {
int i = PODER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* El factor 2.0 significa usar el comportamiento predeterminado de Memcached */
si (factor == 2.0 && tamaño < 128)
tamaño = 128;
mem_limit = límite;
memset(slabclass, 0, sizeof(slabclass));
mientras (++i < POWER_LARGEST && tamaño <= POWER_BLOCK / 2) {
/* Asegúrate de que los elementos siempre estén alineados en n bytes */
si (tamaño % CHUNK_ALIGN_BYTES)
tamaño += CHUNK_ALIGN_BYTES - (tamaño % CHUNK_ALIGN_BYTES
clase de losa[i].tamaño = tamaño);
claselosa[i].perslab = POWER_BLOCK / claselosa[i].tamaño;
tamaño *= factor;
si (configuración.verbose > 1) {
fprintf(stderr, "clase de losa %3d: tamaño del fragmento %6d perslab %5dn",
i, clase de losa[i].tamaño, clase de losa[i].perslab);
}
}
potencia_más grande = i;
clase de losa [potencia_más grande]. tamaño = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
/* para el conjunto de pruebas: fingir cuánto hemos mallocado */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
si (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
#ifndef DONT_PREALLOC_SLABS
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
si (!pre_alloc || atoi(pre_alloc)) {
losas_preallocate(límite/POWER_BLOCK);
}
}
#endif
}
Como se puede ver en lo anterior, la asignación de memoria de Memcached es redundante. Cuando una losa no es divisible por el tamaño del fragmento que posee, el espacio restante al final de la losa se descarta. Por ejemplo, en id40, ocupan dos fragmentos. 1009384 Bytes, esta losa tiene un total de 1 MB, por lo que se desperdician 39192 bytes.
Memcached utiliza este método para asignar memoria para localizar rápidamente el classid de la losa a través de la longitud del elemento. Es un poco similar al hash, porque la longitud del elemento se puede calcular. Por ejemplo, la longitud de un elemento es 300 bytes. Puede obtener que debe almacenarse en la losa de id7, porque de acuerdo con el método de cálculo anterior, el tamaño del fragmento de id6 es 252 bytes, el tamaño del fragmento de id7 es 316 bytes y el tamaño del fragmento de id8 es 396 bytes. lo que significa que todos los elementos de 252 a 316 bytes deben almacenarse en id7. De manera similar, en 1.1, también se puede calcular que está entre 256 y 512, y debe colocarse en id9 con un tamaño de fragmento de 512 (sistema de 32 bits).
Cuando se inicializa Memcached, las losas también se inicializarán (como puede ver antes, se llama a slabs_init() en la función principal). Verificará una constante DONT_PREALLOC_SLABS en slabs_init(). Si esto no está definido, significa que la losa se inicializa usando memoria preasignada, de modo que se crea una losa para cada ID entre todas las clases de losa que se han definido. Esto significa que 1.2 asignará 41 MB de espacio de losa después de iniciar el proceso en el entorno predeterminado. Durante este proceso, se produce la segunda redundancia de memoria de Memcached, porque es posible que una identificación no se haya utilizado en absoluto, pero también es A. La losa se solicita de forma predeterminada y cada losa utilizará 1 MB de memoria.
Cuando se agote una losa y sea necesario insertar un nuevo elemento con esta ID, se volverá a solicitar una nueva losa. losa, el ID correspondiente La lista vinculada de losa crecerá exponencialmente. En la función grow_slab_list, la longitud de esta cadena cambia de 1 a 2, de 2 a 4, de 4 a 8...
CÓDIGO: [Copiar al portapapeles] static int grow_slab_list (identificación int sin firmar) {
slabclass_t *p = &slabclass[id];
if (p->losas == p->list_size) {
size_t nuevo_tamaño = p->list_size ? p->list_size * 2 : 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
si (new_list == 0) devuelve 0;
p->list_size = nuevo_tamaño;
p->lista_losa = lista_nueva;
}
devolver 1;
}
Al ubicar el elemento, se utiliza la función slabs_clsid. El parámetro entrante es el tamaño del elemento y el valor de retorno es el classid. De este proceso, se puede ver que la tercera redundancia de memoria de memcached ocurre en el proceso de guardar el elemento. El elemento siempre es más pequeño que O igual al tamaño del fragmento. Cuando el elemento es más pequeño que el tamaño del fragmento, se desperdicia espacio nuevamente.
◎ Algoritmo NewHash de Memcached
El almacenamiento de elementos de Memcached se basa en una tabla hash grande. Su dirección real es el desplazamiento del fragmento en la losa, pero su posicionamiento depende del resultado del hash de la clave, que se encuentra en la tabla hash primaria. Todas las operaciones hash y de elementos se definen en assoc.c y items.c.
Memcached utiliza un algoritmo llamado NewHash, que es muy eficaz y eficiente. Existen algunas diferencias entre NewHash en 1.1 y 1.2. El método de implementación principal sigue siendo el mismo. La función hash de 1.2 se ha organizado y optimizado y su adaptabilidad es mejor.
Referencia del prototipo NewHash: http://burtleburtle.net/bob/hash/evahash.html . Los matemáticos siempre son un poco extraños, jaja ~
Para facilitar la conversión, se definen dos tipos de datos, u4 y u1, u4 es un entero largo sin signo y u1 es un carácter sin signo (0-255).
Para códigos específicos, consulte los paquetes de código fuente 1.1 y 1.2.
Preste atención a la longitud de la tabla hash aquí. También hay una diferencia entre 1.1 y 1.2. En 1.1, la constante HASHPOWER se define como 20 y la longitud de la tabla hash es hashsize (HASHPOWER), que es 4 MB (hashsize es una macro, lo que indica). (es decir, 1 se desplaza hacia la derecha n bits). En 1.2, la variable es 16, es decir, la longitud de la tabla hash es 65536:
CÓDIGO:[Copiar al portapapeles]typedef unsigned long int ub4 /* cantidades de 4 bytes sin firmar */
typedef unsigned char ub1; /* cantidades de 1 byte sin firmar */
#define hashsize(n) ((ub4)1<<(n))
#definir máscara hash(n) (tamaño hash(n)-1)
En assoc_init (), se inicializará la tabla primaria_hash. Las operaciones hash correspondientes incluyen: assoc_find (), assoc_expand (), assoc_move_next_bucket (), assoc_insert (), assoc_delete (), correspondientes a las operaciones de lectura y escritura del elemento. Entre ellas, assoc_find() es una función que encuentra la dirección del elemento correspondiente en función de la clave y la longitud de la clave (tenga en cuenta que en C, muchas veces la cadena y la longitud de la cadena se pasan directamente al mismo tiempo en lugar de ejecutar strlen dentro de la función ), y lo que se devuelve es un puntero de estructura del elemento, su dirección de datos está en un fragmento de la losa.
items.c es el programa de operación para elementos de datos. Cada elemento completo incluye varias partes, que se definen en item_make_header() como:
clave: clave.
nkey: longitud de la clave
flags: indicador definido por el usuario (de hecho, este indicador no está habilitado en Memcached)
nbytes: longitud del valor (incluido el símbolo de nueva línea rn)
sufijo: sufijo Buffer
nsuffix: la
longitud del sufijo de un elemento completo es la longitud de la clave + longitud del valor + longitud del sufijo + tamaño de la estructura del elemento (32 bytes). La operación del elemento se basa en esta longitud para calcular el classid de la losa.
Cada depósito en la tabla hash está colgado con una lista de doble enlace. Durante item_init(), las tres matrices de cabezas, colas y tamaños se han inicializado a 0. Los tamaños de estas tres matrices son la constante LARGEST_ID (el valor predeterminado es 255, este valor requiere modificarse con un factor), cada vez que se llama a item_assoc(), primero intentará obtener un fragmento libre de la losa. Si no hay un fragmento disponible, escaneará la lista vinculada 50 veces para obtener un fragmento que estaba. iniciado por el elemento LRU, desvincúlelo y luego inserte el elemento que se insertará en la lista vinculada.
Preste atención al miembro de refcount del artículo. Después de desvincular el elemento, solo se elimina de la lista vinculada. No se libera inmediatamente. Simplemente se coloca en la cola de eliminación (función item_unlink_q()).
El elemento corresponde a algunas operaciones de lectura y escritura, incluidas eliminar, actualizar y reemplazar. Por supuesto, la más importante es la operación de asignación.
Otra característica del elemento es que tiene un tiempo de vencimiento, que es una característica muy útil de Memcached. Muchas aplicaciones dependen del vencimiento del elemento de Memcached, como el almacenamiento de sesiones, bloqueos de operaciones, etc. La función item_flush_expired() escanea los elementos de la tabla y realiza una operación de desvinculación de los elementos caducados. Por supuesto, esto es solo una acción de reciclaje. De hecho, también se requiere juicio de tiempo al obtener:
CÓDIGO:[Copiar al portapapeles]/* caduca los elementos que son más recientes que la configuración más antigua_live */.
anular item_flush_expired() {
ent i;
elemento *iter, *siguiente;
si (! configuración.oldest_live)
devolver;
para (i = 0; i < LARGEST_ID; i++) {
/* La LRU se ordena en orden de tiempo decreciente y la marca de tiempo de un elemento
* nunca es más reciente que su última hora de acceso, por lo que solo necesitamos caminar
* retroceder hasta que lleguemos a un elemento anterior al tiempo más antiguo_en vivo.
* La verificación de old_live caducará automáticamente los elementos restantes.
*/
for (iter = cabezas[i]; iter != NULL; iter = siguiente) {
if (iter->tiempo >= configuración.oldest_live) {
siguiente = iter->siguiente;
si ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} demás {
/* Hemos llegado al primer elemento antiguo. Continuar a la siguiente cola */.
romper;
}
}
}
}
CÓDIGO:[Copiar al portapapeles]/* contenedor alrededor de assoc_find que realiza la lógica de vencimiento/eliminación diferida */
elemento *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
elemento *it = assoc_find(clave, nclave);
si (eliminar_bloqueado) *eliminar_bloqueado = 0;
si (it && (it->it_flags & ITEM_DELETED)) {
/* está marcado como eliminado bloqueado, veamos si esa condición.
está vencido y el temporizador de eliminación de 5 segundos simplemente no ha
He llegado a ello todavía... */
si (!item_delete_lock_over(es)) {
si (eliminar_bloqueado) *eliminar_bloqueado = 1;
es = 0;
}
}
si (it && settings.oldest_live && settings.oldest_live <= current_time &&
it->hora <= configuración.oldest_live) {
item_unlink(eso);
es = 0;
}
if (it && it->exptime && it->exptime <= tiempo_actual) {
item_unlink(eso);
es = 0;
}
devolverlo;
}
El método de administración de memoria de Memcached es muy sofisticado y eficiente. Reduce en gran medida la cantidad de asignaciones directas de memoria del sistema, reduce la sobrecarga de funciones y la probabilidad de fragmentación de la memoria. Aunque este método causará algún desperdicio redundante, este desperdicio es trivial en sistemas grandes. aplicaciones.
◎ El método de cálculo de parámetros teóricos de Memcached
tiene varios parámetros que afectan el trabajo de Memcached:
constante REALTIME_MAXDELTA 60*60*24*30
Tiempo de vencimiento máximo de 30 días
freetotal (=200) en conn_init()
Número máximo de conexiones simultáneas
constante KEY_MAX_LENGTH 250
de longitud máxima de clave.factor
(=1,25)
El factor afectará el tamaño del paso de
la configuración del fragmento.maxconns (=1024)
máxima de conexión suave.chunk_size
(=48)
Una longitud clave+valor estimada de forma conservadora, utilizada para generar la longitud del fragmento (1.2) en id1. La longitud del fragmento de id1 es igual a este valor más la longitud de la estructura del elemento (32), que son los 80 bytes predeterminados.
Constante POWER_SMALLEST 1
Classid mínimo (1.2)
constante POWER_LARGEST 200
Máximo classid (1.2)
constante POWER_BLOCK 1048576
de tamaño de losa predeterminada
CHUNK_ALIGN_BYTES (tamaño de (vacío *))
Asegúrese de que el tamaño del fragmento sea un múltiplo entero de este valor para evitar límites (la longitud de void * es diferente en diferentes sistemas, es 4 en sistemas estándar de 32 bits)
constante ITEM_UPDATE_INTERVAL 60
de intervalo de actualización de cola
GRANDE_ID 255
Número máximo de elementos en la lista vinculada (este valor no puede ser menor que el classid más grande)
variable hashpower (HASHPOWER constante en 1.1)
Determinando el tamaño de la tabla hash
Según el contenido y la configuración de parámetros presentados anteriormente, se pueden calcular algunos resultados:
1. No existe un límite superior de software para la cantidad de elementos que se pueden guardar en Memcached. Mi declaración anterior era 1 millón. equivocado.
2. Suponiendo que el algoritmo NewHash tiene colisiones uniformes, el número de ciclos para encontrar un elemento es el número total de elementos dividido por el tamaño de la tabla hash (determinado por el poder de hash), que es lineal.
3. Memcached limita el elemento máximo aceptable a 1 MB y se ignorarán los datos superiores a 1 MB.
4. La utilización del espacio de Memcached tiene una gran relación con las características de los datos y también está relacionada con la constante DONT_PREALLOC_SLABS. En el peor de los casos, se desperdiciarán 198 losas (todos los elementos se concentran en una losa y los 199 ID están completamente asignados).
◎Optimización de longitud fija de Memcached
Según las descripciones de las secciones anteriores, tengo una comprensión más profunda de Memcached. Solo se puede optimizar basándose en una comprensión profunda.
Memcached en sí está diseñado para datos de longitud variable. Según las características de los datos, se puede decir que es un diseño "orientado al público". Sin embargo, muchas veces, nuestros datos no son tan "universales". distribución no uniforme, es decir, la longitud de los datos se concentra en varias áreas (como guardar sesiones de usuario), el otro estado más extremo son datos de igual longitud (como valores clave de longitud fija, datos de longitud fija, en su mayoría). visto en accesos, estadísticas online o bloqueos de ejecución).
Aquí estudiamos principalmente la solución de optimización para datos de longitud fija (1.2). Los datos de longitud variable distribuidos centralizados son solo de referencia y son fáciles de implementar.
Para resolver datos de longitud fija, lo primero que hay que resolver es el problema de asignación de losas. Lo primero que hay que confirmar es que no necesitamos tantas losas con diferentes longitudes de trozos para maximizar el uso. de recursos, es mejor que los fragmentos y los elementos tengan la misma longitud, por lo que primero Para calcular la longitud del elemento.
Ha existido un algoritmo para calcular la longitud del elemento anteriormente. Cabe señalar que además de la longitud de la cadena, se debe agregar la longitud de la estructura del elemento de 32 bytes.
Supongamos que hemos calculado que necesitamos guardar 200 bytes de datos de igual longitud.
El siguiente paso es modificar la relación entre el classid de la losa y la longitud del fragmento. En la versión original, existe una relación correspondiente entre la longitud del fragmento y el classid. Ahora, si todos los fragmentos están configurados en 200 bytes, entonces esta relación no existe. Necesitamos volver a determinar la relación entre los dos. Un método es usar solo una ID fija para toda la estructura de almacenamiento, es decir, usar solo 1 de las 199 ranuras. Bajo esta condición, se debe definir DONT_PREALLOC_SLABS para evitar un desperdicio adicional de preasignación. Otro método es establecer una relación hash para determinar el classid del elemento. No puede usar la longitud como clave. Puede usar datos variables como el resultado NewHash de la clave o hacer el hash directamente en función de la clave. Las claves de datos de longitud fija también deben tener la misma longitud). En aras de la simplicidad aquí, elegimos el primer método. La desventaja de este método es que solo se utiliza una ID. Cuando la cantidad de datos es muy grande, la cadena de losa será muy larga (porque todos los datos están abarrotados). una cadena). El costo de atravesar es relativamente alto.
Los tres tipos de redundancia de espacio se introdujeron anteriormente. Establecer la longitud del fragmento igual a la longitud del elemento resuelve el primer problema de desperdicio de espacio. No solicitar espacio por adelantado resuelve el segundo problema de desperdicio de espacio. Entonces, ¿qué pasa con el primer problema (permanecer en la losa)? )? Para resolver esto, necesita modificar la constante POWER_BLOCK para que el tamaño de cada losa sea exactamente igual a un múltiplo entero de la longitud del fragmento, de modo que una losa se pueda dividir en n fragmentos. Este valor debe estar más cerca de 1 MB. Si es demasiado grande, también provocará redundancia. Si es demasiado pequeño, provocará demasiadas asignaciones. Según la longitud del fragmento de 200, elija 1000000 como valor de POWER_BLOCK. De esta manera, una losa tendrá 1 millón de bytes, no 1048576. Los tres problemas de redundancia se resuelven y la utilización del espacio mejorará enormemente.
Modifique la función slabs_clsid para que devuelva directamente un valor fijo (como 1):
CÓDIGO:[Copiar al portapapeles]unsigned int slabs_clsid(size_t size) {
devolver 1;
}
Modifique la función slabs_init, elimine la parte que se repite para crear todos los atributos classid y agregue directamente slabclass [1]:
CÓDIGO:[Copiar al portapapeles]slabclass[1].size = 200; //200 bytes por fragmento
clase de losa[1].perslab = 5000; //1000000/200
◎ Cliente Memcached
Memcached es un programa de servicio. Al usarlo, puede conectarse al servidor Memcached de acuerdo con su protocolo, enviar comandos al proceso de servicio y luego operar los datos anteriores. Para facilitar su uso, memcached tiene muchos programas cliente disponibles, correspondientes a varios idiomas, y hay clientes en varios idiomas. Los basados en lenguaje C incluyen libmemcache y APR_Memcache; los basados en Perl incluyen Cache::Memcached; también hay soporte para Python, Ruby, Java, C# y otros lenguajes. PHP tiene la mayor cantidad de clientes, no solo las dos extensiones mcache y PECL memcache, sino también una gran cantidad de clases de encapsulación escritas por PHP. Aquí hay una introducción sobre cómo usar memcached en PHP:
La extensión mcache se vuelve a encapsular según libmemcache. . libmemcache no ha lanzado una versión estable. La versión actual es 1.4.0-rc2, que se puede encontrar aquí. Una característica muy mala de libmemcache es que escribe muchos mensajes de error en stderr. Generalmente, cuando se usa como lib, stderr generalmente se dirige a otros lugares, como el registro de errores de Apache, y libmemcache se suicidará, lo que puede causar Anormalidad. , pero su rendimiento sigue siendo muy bueno.
La extensión Mcache se actualizó por última vez a 1.2.0-beta10. El autor probablemente renunció. No sólo dejó de actualizar, sino que tampoco pudo abrir el sitio web (~_~). . Después de la descompresión, el método de instalación es el habitual: phpize & configure & make & make install. Asegúrese de instalar libmemcache primero. Usar esta extensión es simple:
CÓDIGO:[Copiar al portapapeles]<?php
$mc = memcache(); // Crea un objeto de conexión de Memcache. ¡Ten en cuenta que aquí no se utiliza new!
$mc->add_server('localhost', 11211); // Agregar un proceso de servicio
$mc->add_server('localhost', 11212); // Agregar un segundo proceso de servicio
$mc->set('key1', 'Hola'); // Escribir clave1 => Hola
$mc->set('key2', 'World', 10); // Escribe key2 => World, caduca en 10 segundos
$mc->set('arr1', array('Hola', 'Mundo') // Escribe una matriz
$key1 = $mc->get('key1'); // Obtener el valor de 'key1' y asignarlo a $key1
$key2 = $mc->get('key2'); // Obtenga el valor de 'key2' y asígnelo a $key2. Si excede los 10 segundos, no estará disponible.
$arr1 = $mc->get('arr1'); // Obtener la matriz 'arr1'
$mc->delete('arr1'); // Eliminar 'arr1'
$mc->flush_all(); // Eliminar todos los datos
$stats = $mc->stats(); // Obtener información del servidor
var_dump($stats); // La información del servidor es una matriz
?>
La ventaja de esta extensión es que puede implementar fácilmente almacenamiento distribuido y equilibrio de carga, porque puede agregar múltiples direcciones de servicio al guardar datos, que se ubicarán en un servidor determinado según el resultado del hash. Esta también es una característica de libmemcache. . libmemcache admite métodos de hash centralizados, incluidos CRC32, ELF y Perl hash.
PECL memcache es una extensión lanzada por PECL. La última versión es 2.1.0, que se puede obtener en el sitio web de pecl. El uso de la extensión Memcache se puede encontrar en algunos manuales de PHP más nuevos. Es muy similar a Mcache, muy similar:
CÓDIGO:[Copiar al portapapeles]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) o morir ("No se pudo conectar");
$version = $memcache->getVersion();
echo "Versión del servidor: ".$version."n";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'prueba';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) o die ("Error al guardar los datos en el servidor");
echo "Almacenar datos en el caché (los datos caducan en 10 segundos) n"
$get_result = $memcache->get('key');
echo "Datos del caché:n";
var_dump($get_result
?>);
Esta extensión utiliza la secuencia de PHP para conectarse directamente al servidor Memcached y enviar comandos a través del socket. No es tan completo como libmemcache ni admite operaciones distribuidas como add_server, pero como no depende de otros programas externos, tiene mejor compatibilidad y es relativamente estable. En cuanto a eficiencia, la diferencia no es enorme.
Además, hay muchas clases PHP disponibles, como MemcacheClient.inc.php, y muchas se pueden encontrar en phpclasses.org. Generalmente son reencapsulaciones de la API del cliente Perl y se usan de manera similar.
◎BSM_Memcache
Desde la perspectiva del cliente C, APR_Memcache es un programa cliente muy maduro y estable que admite bloqueos de subprocesos y operaciones de nivel atómico para garantizar la estabilidad operativa. Sin embargo, se basa en APR (APR se introducirá en la última sección) y no tiene una variedad tan amplia de aplicaciones como LibMemcache. Porque no puede correr fuera del entorno APR. Sin embargo, APR se puede instalar por separado de Apache.
BSM_MEMCACHE es una extensión PHP basada en APR_MEMCACHE que desarrollé en el proyecto Bs.magic. Este programa es muy simple y no hace demasiadas funciones.
A diferencia de la extensión de McACHE que admite el almacenamiento distribuido de múltiples servidores, BSM_MEMCACHE admite múltiples grupos de servidores. , se implementa la copia de seguridad caliente. Por supuesto, el costo de implementar esta función es el sacrificio del rendimiento. Normalmente puedes obtenerlo la próxima vez.
BSM_MEMCACHE solo admite estas funciones:
Código: [Copiar al portapapeles] Zend_Function_Entry BSM_MEMCACHE_FUBTIONS [] =
{
Php_fe (mc_get, nulo)
Php_fe (mc_set, nulo)
Php_fe (MC_DEL, NULL)
Php_fe (mc_add_group, nulo)
Php_fe (mc_add_server, nulo)
Php_fe (MC_SHUTDOWN, NULL)
{NULL, NULL, NULL}
};
La función MC_ADD_GROUP devuelve un entero (en realidad debería ser un objeto, era flojo ~ _ ~) como la ID de grupo. Adjedrort).
Código: [Copiar al portapapeles]/**
*Agregar un grupo de servidores
*/
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
)
;
}
Código: [Copiar al portapapeles]/**
* Agregar un servidor al grupo
*/
Php_function (mc_add_server)
{
APR_STATUS_T RV;
APR_INT32_T GROUP_ID;
doble 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) == falla)
{
Return_false;
}
group_id = (APR_INT32_T) G
;
{
Return_false;
}
char *host, *alcance;
APR_PORT_T PORT
;
if (apr_success == rv)
{
// Crear este objeto del servidor
APR_MEMCACHE_SERVER_T *ST;
RV = APR_MEMCACHE_SERVER_CREATE (P, host, puerto, 0, 64, 1024, 600 y st);
if (apr_success == rv)
{
if (null == mc_groups [group_id])
{
Return_false;
}
// Agregar servidor
RV = APR_MEMCACHE_ADD_SERVER (MC_GROUPS [GROUP_ID], ST)
;
{
Return_true;
}
}
}
Return_false;
}
Al configurar y delertar datos, realice todos los grupos:
Código: [Copiar al portapapeles]/**
* Almacene el artículo en todos los grupos
*/
Php_function (mc_set)
{
Char *clave, *valor;
int key_l, value_l;
doble 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) == falla)
{
Return_false;
}
// Escribir datos en cada objeto
APR_INT32_T i = 0;
if (ttl <0)
{
ttl = 0;
}
APR_STATUS_T RV
;
{
if (0 == is_validate_group (i))
{
// escríbalo!
RV = APR_MEMCACHE_ADD (MC_GROUPS [i], clave, valor, value_l, (apr_uint32_t) ttl, 0);
if (apr_success == rv)
{
set_ct ++;
}
}
}
Return_double (set_ct);
}
En MC_Get, primero selecciona aleatoriamente un grupo y luego comienza a encuestar desde este grupo:
Código: [Copiar al portapapeles]/**
* Obtener un artículo de un grupo aleatorio
*/
Php_function (mc_get)
{
Char *clave, *valor = nulo;
int key_l;
APR_SIZE_T VALOR_L
;
{
Wrong_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == falla)
{
Return_mull ();
}
// Intentaré...
// lectura aleatoria
APR_INT32_T CURR_GROUP_ID = ROUNTER_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;
prueba = prueba % max_group;
if (0 == is_validate_group (intent))
{
// obtener un valor
oper = mc_groups [try];
RV = APR_MEMCACHE_GETP (MC_GROUPS [try], p, (const char *) clave, y valor, & value_l, 0);
if (apr_success == rv)
{
Return_string (valor, 1);
}
}
}
Return_false;
}
Código: [Copiar al portapapeles]/**
* ID de grupo aleatorio
* Para mc_get ()
*/
APR_INT32_T Random_Group ()
{
Struct Timeval TV;
estructura horaria tz;
int
usec
;
}
El uso de bsm_memcache es similar a otros clientes:
Código: [Copiar al portapapeles] <? 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 ();
?>
Se puede encontrar información relevante sobre APR_MEMCACHE, y se puede descargar BSM_MEMCACHE de este sitio.
◎ Introducción del entorno APR
El nombre completo de APR: Apache Portable Runtime. Es un conjunto de bibliotecas de lenguaje C multiplataforma creadas y mantenidas por Apache Software Foundation. Se extrae de Apache httpd1.x y es independiente de httpd. APR proporciona muchas interfaces API convenientes para su uso, incluidas funciones prácticas como piscinas de memoria, operaciones de cadenas, redes, matrices, tablas hash, etc. El desarrollo del módulo Apache2 requiere la exposición a muchas funciones APR.
Postscript
Este es mi último artículo en el año Bingxu del calendario lunar (mi año de nacimiento). Gracias a Sina.com por brindar oportunidades de investigación y colegas en el departamento por su ayuda.
Dr. NP02-13-2007