Memcached é um sistema de cache de objetos de memória distribuída desenvolvido pela danga.com (a equipe técnica que opera o LiveJournal) para reduzir a carga do banco de dados e melhorar o desempenho em sistemas dinâmicos. Em relação a isso, acredito que muitas pessoas o tenham utilizado. O objetivo deste artigo é obter uma compreensão mais profunda deste excelente software de código aberto por meio da implementação e análise de código do memcached, e otimizá-lo ainda mais de acordo com nossas necessidades. Por fim, através da análise da extensão BSM_Memcache, aprofundaremos nosso entendimento sobre o uso do memcached.
Parte do conteúdo deste artigo pode exigir uma base matemática melhor como assistência.
◎O que é Memcached?
Antes de aprofundar esta questão, devemos primeiro entender o que ele “não é”. Muitas pessoas o usam como meio de armazenamento como o SharedMemory. Embora o memcached use o mesmo método "Key=>Value" para organizar os dados, ele é muito diferente dos caches locais, como memória compartilhada e APC. O Memcached é distribuído, o que significa que não é local. Ele completa o serviço com base na conexão de rede (é claro que também pode usar localhost. É um programa independente de aplicativo ou processo daemon (modo Daemon).
Memcached usa a biblioteca libevent para implementar serviços de conexão de rede e pode, teoricamente, lidar com um número ilimitado de conexões. No entanto, ao contrário do Apache, ele é mais frequentemente orientado para conexões contínuas estáveis, portanto, seus recursos reais de simultaneidade são limitados. Em circunstâncias conservadoras, o número máximo de conexões simultâneas para o memcached é 200, o que está relacionado à capacidade do thread do Linux. Esse valor pode ser ajustado. Para obter informações sobre libevent, consulte a documentação relevante. O uso de memória Memcached também é diferente do APC. APC é baseado em memória compartilhada e o MMAP tem seu próprio algoritmo de alocação de memória e método de gerenciamento. Não tem nada a ver com memória compartilhada e não tem restrições de memória compartilhada. Normalmente, cada processo memcached pode gerenciar 2 GB de espaço de memória. é necessário mais espaço, o número de processos pode ser aumentado.
◎Para quais ocasiões o Memcached é adequado?
Em muitos casos, o memcached foi abusado, o que obviamente leva a reclamações sobre ele. Muitas vezes vejo pessoas postando em fóruns semelhantes a "como melhorar a eficiência" e a resposta é "usar memcached". Quanto a como usá-lo, onde usá-lo e para que é usado, não há frase. Memcached não é uma panacéia nem é adequado para todas as situações.
Memcached é um sistema de cache de objetos de memória “distribuído”, ou seja, para aqueles aplicativos que não precisam ser “distribuídos”, não precisam ser compartilhados ou são simplesmente pequenos o suficiente para ter apenas um servidor, o memcached não. traz quaisquer benefícios. Pelo contrário, também diminui a eficiência do sistema porque as conexões de rede também exigem recursos, até mesmo conexões locais UNIX. Meus dados de teste anteriores mostraram que as velocidades de leitura e gravação locais do memcached são dezenas de vezes mais lentas do que os arrays de memória PHP diretos, enquanto os métodos APC e de memória compartilhada são semelhantes aos arrays diretos. Pode-se observar que se for apenas um cache de nível local, usar o memcached é muito antieconômico.
Memcached é frequentemente usado como cache front-end de banco de dados. Por ter muito menos análise SQL, operações de disco e outras despesas gerais do que um banco de dados, e usar memória para gerenciar dados, pode fornecer melhor desempenho do que a leitura direta do banco de dados. Em sistemas grandes, é muito difícil acessar os mesmos dados. Freqüentemente, o memcached pode reduzir bastante a pressão do banco de dados e melhorar a eficiência de execução do sistema. Além disso, o memcached é frequentemente usado como meio de armazenamento para compartilhamento de dados entre servidores. Por exemplo, os dados que salvam o status de logon único do sistema em um sistema SSO podem ser salvos no memcached e compartilhados por vários aplicativos.
Deve-se observar que o memcached usa memória para gerenciar dados, por isso é volátil. Quando o servidor é reiniciado ou o processo do memcached é encerrado, os dados serão perdidos, portanto, o memcached não pode ser usado para persistir dados. Muitas pessoas entendem mal que o desempenho do memcached é muito bom, tão bom quanto a comparação entre memória e disco rígido. Na verdade, o memcached não obterá centenas ou milhares de melhorias na velocidade de leitura e gravação usando a memória. conexão, que está relacionada ao uso de memória Em comparação com o sistema de banco de dados em disco, a vantagem é que ele é muito "leve", pois não há sobrecarga excessiva e métodos diretos de leitura e gravação, ele pode facilmente lidar com uma quantidade muito grande. de troca de dados, portanto, geralmente há larguras de banda de rede de dois gigabits. Eles estão todos totalmente carregados e o processo memcached em si não ocupa muitos recursos da CPU.
◎Como funciona o Memcached
Nas seções a seguir, é melhor que os leitores preparem uma cópia do código-fonte do memcached.
Memcached é um programa de serviço de rede tradicional. Se o parâmetro -d for usado na inicialização, ele será executado como um processo daemon. A criação de um processo daemon é concluída por daemon.c Este programa possui apenas uma função daemon, que é muito simples (se nenhuma instrução especial for fornecida, o código estará sujeito a 1.2.1):
CÓDIGO:[Copiar para a área de transferência]#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int
daemon(nochdir, noclose)
int nochdir, noclose;
{
int fd;
switch (garfo()) {
caso-1:
retorno (-1);
caso 0:
quebrar;
padrão:
_sair(0);
}
if (setsid() == -1)
retornar (-1);
se (!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);
se (fd > STDERR_FILENO)
(vazio)fechar(fd);
}
retornar (0);
}
Depois que essa função bifurca todo o processo, o processo pai é encerrado e, em seguida, realoca STDIN, STDOUT e STDERR para dispositivos vazios e o daemon é estabelecido com êxito.
O processo de inicialização do Memcached em si é o seguinte na função principal do memcached.c:
1. Chame settings_init() para definir os parâmetros de inicialização.
2. Leia os parâmetros do comando de inicialização para definir o valor da configuração
3. Defina o parâmetro LIMIT
4. Inicie o monitoramento de soquete de rede (se não existir um caminho de soquete) (o modo UDP é suportado após 1.2)
5. Verifique a identidade do usuário (o Memcached não permite que a identidade raiz seja iniciada)
6. Se o socketpath existir, abra a conexão local UNIX (Sock pipe)
7. Se iniciado no modo -d, crie um processo daemon (chame a função daemon conforme acima)
8. Inicialize item, evento, informações de status, hash, conexão, laje
9. Se gerenciado entrar em vigor nas configurações, crie uma matriz de bucket
10. Verifique se a página de memória precisa ser bloqueada
11. Inicialize o sinal, conexão, exclua a fila
12. Se estiver no modo daemon, processe o ID do processo
13. O evento é iniciado, o processo de inicialização termina e a função principal entra no loop.
No modo daemon, como o stderr foi direcionado para o buraco negro, nenhuma mensagem de erro visível durante a execução será realimentada.
A função de loop principal do memcached.c é drive_machine. O parâmetro de entrada é um ponteiro de estrutura que aponta para a conexão atual e a ação é determinada com base no status do membro do estado.
Memcached usa um conjunto de protocolos personalizados para completar a troca de dados. Seu documento de protocolo pode ser consultado: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
Na API, os símbolos de nova linha. são unificados como rn
◎Método de gerenciamento de memória do Memcached
O Memcached possui um método de gerenciamento de memória muito exclusivo. Para melhorar a eficiência, ele usa métodos de pré-aplicação e agrupamento para gerenciar o espaço de memória, em vez de malloc sempre que precisa gravar dados. ., libere um ponteiro ao excluir dados. Memcached usa o método de organização de blocos-> pedaços para gerenciar a memória.
Existem algumas diferenças nos algoritmos de divisão do espaço da laje em lajes.c em 1.1 e 1.2, que serão introduzidas separadamente posteriormente.
Slab pode ser entendido como um bloco de memória. Um bloco é a menor unidade para o memcached aplicar memória de uma vez. No memcached, o tamanho padrão de um bloco é 1.048.576 bytes (1 MB), portanto, o memcached usa todo o MB de memória. Cada bloco é dividido em vários pedaços, e cada pedaço armazena um item. Cada item também contém a estrutura, a chave e o valor do item (observe que o valor no memcached é apenas uma string). As lajes formam listas vinculadas de acordo com seus próprios IDs, e essas listas vinculadas são penduradas em uma matriz de classes de placas de acordo com seus IDs. A estrutura inteira se parece um pouco com uma matriz bidimensional. O comprimento da classe de laje é 21 em 1,1 e 200 em 1,2.
laje tem um tamanho de bloco inicial, que é 1 byte em 1.1 e 80 bytes em 1.2. Há um valor de fator em 1.2, cujo padrão é 1.25,
o tamanho do bloco é expresso como tamanho inicial * 2 ^ n, n é. classid, ou seja: a laje com id 0 tem um tamanho de pedaço de 1 byte, a laje com id 1 tem um tamanho de pedaço de 2 bytes, a laje com id 2 tem um tamanho de pedaço de 4 bytes... a laje com id 20 tem um tamanho de bloco de 4 bytes. O tamanho é 1 MB, o que significa que há apenas um pedaço no bloco com ID 20:
CÓDIGO:[Copiar para a área de transferência]void lajes_init(tamanho_t limite) {
int eu;
int tamanho = 1;
mem_limit = limite;
for(i=0; i<=POWER_LARGEST; i++, tamanho*=2) {
Slabclass[i].size = tamanho;
Slabclass[i].perslab = POWER_BLOCK / tamanho;
classe de laje[i].slots = 0;
lajeclass[i].sl_curr = lajeclass[i].sl_total = lajeclass[i].lajes = 0;
lajeclass[i].end_page_ptr = 0;
lajeclass[i].end_page_free = 0;
Slabclass[i].slab_list = 0;
Slabclass[i].list_size = 0;
classe de laje[i].killing = 0;
}
/* para o conjunto de testes: fingindo o quanto já fizemos malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
se (t_initial_malloc) {
mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));
}
}
/* pré-alocar blocos por padrão, a menos que a variável de ambiente
para teste está definido como algo diferente de zero */
{
char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
if (!pre_alloc || atoi(pre_alloc)) {
lajes_preallocate(limite / POWER_BLOCK);
}
}
}
Em 1.2, o tamanho do pedaço é expresso como o tamanho inicial * f ^ n, f é o fator, que é definido em memcached.c, e n é classid. Ao mesmo tempo, nem todos os 201 cabeçotes precisam ser inicializados porque o fator. é variável e a inicialização apenas faz um loop para O tamanho calculado atinge metade do tamanho da placa e começa em id1, ou seja: placa com id 1, cada tamanho de bloco é 80 bytes, placa com id 2, tamanho de cada bloco é 80* f, id é 3 placas, cada tamanho de bloco é 80*f^2 e o tamanho de inicialização tem um valor de correção CHUNK_ALIGN_BYTES para garantir o alinhamento de n bytes (garantindo que o resultado seja um múltiplo integral de CHUNK_ALIGN_BYTES). Dessa forma, em circunstâncias padrão, o memcached1.2 será inicializado para id40. O tamanho de cada pedaço nesta placa é 504692 e há dois pedaços em cada placa. Por fim, a função Slab_init irá adicionar um id41 no final, que é um bloco inteiro, ou seja, só existe um chunk de 1MB neste Slab:
CÓDIGO:[Copiar para a área de transferência]void lajes_init(limite de tamanho_t, fator duplo) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
/* Fator de 2,0 significa usar o comportamento padrão do memcached */
if (fator == 2,0 && tamanho <128)
tamanho = 128;
mem_limit = limite;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && tamanho <= POWER_BLOCK / 2) {
/* Certifique-se de que os itens estejam sempre alinhados com n bytes */
se (tamanho% CHUNK_ALIGN_BYTES)
tamanho += CHUNK_ALIGN_BYTES - (tamanho % CHUNK_ALIGN_BYTES)
;
Slabclass[i].perslab = POWER_BLOCK / Slabclass[i].size;
tamanho *= fator;
if (configurações.verbose > 1) {
fprintf(stderr, "classe de laje %3d: tamanho do pedaço %6d perslab %5dn",
i, Slabclass[i].size, Slabclass[i].perslab);
}
}
poder_maior = i;
classe de laje[power_largest].size = POWER_BLOCK;
Slabclass[power_largest].perslab = 1;
/* para o conjunto de testes: fingindo o quanto já fizemos o malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
se (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)) {
lajes_preallocate(limite / POWER_BLOCK);
}
}
#endif
}
Como pode ser visto acima, a alocação de memória do memcached é redundante. Quando um bloco não é divisível pelo tamanho do bloco que possui, o espaço restante no final do bloco é descartado. Por exemplo, no id40, dois blocos ocupam. 1009384 Bytes, este bloco tem um total de 1MB, então 39192 bytes são desperdiçados.
Memcached usa este método para alocar memória a fim de localizar rapidamente o classid do bloco através do comprimento do item. É um pouco semelhante ao hash, porque o comprimento do item pode ser calculado. Por exemplo, o comprimento de um item é de 300 bytes. Você pode perceber que ele deve ser armazenado na placa de id7, porque de acordo com o método de cálculo acima, o tamanho do pedaço de id6 é 252 bytes, o tamanho do pedaço de id7 é 316 bytes e o tamanho do pedaço de id8 é 396 bytes, o que significa que todos os itens de 252 a 316 bytes devem ser armazenados em id7. Da mesma forma, em 1.1, também pode ser calculado que está entre 256 e 512, e deve ser colocado em id9 com chunk_size de 512 (sistema de 32 bits).
Quando o Memcached é inicializado, os Slabs serão inicializados (como você pode ver anteriormente, Slabs_init() é chamado na função principal). Irá verificar uma constante DONT_PREALLOC_SLABS em Slabs_init(). Se não for definida, significa que o Slab é inicializado utilizando memória pré-alocada, de forma que um Slab é criado para cada ID entre todas as Slabclasses que foram definidas. Isso significa que 1.2 alocará 41 MB de espaço de laje após iniciar o processo no ambiente padrão. Durante esse processo, ocorre a segunda redundância de memória do memcached, pois é possível que um id não tenha sido utilizado, mas também é A. a laje é solicitada por padrão e cada laje usará 1 MB de memória.
Quando uma laje for usada e um novo item precisar ser inserido com esse ID, ele será solicitado novamente para uma nova laje. laje, o ID correspondente A lista vinculada da laje crescerá exponencialmente Na função grow_slab_list, o comprimento desta cadeia muda de 1 para 2, de 2 para 4, de 4 para 8...:
CÓDIGO:[Copiar para a área de transferência]static int grow_slab_list (unsigned int id) {
lajeclass_t *p = &slabclass[id];
if (p->lajes == p->list_size) {
tamanho_t novo_tamanho = p->tamanho_da_lista ? p->tamanho_da_lista * 2: 16;
void *new_list = realloc(p->slab_list, new_size*sizeof(void*));
if (nova_lista == 0) retornar 0;
p->tamanho_lista = novo_tamanho;
p->lista_laje = nova_lista;
}
retornar 1;
}
Ao localizar o item, é utilizada a função Slabs_clsid. O parâmetro de entrada é o tamanho do item e o valor de retorno é o classid. A partir desse processo, pode-se observar que a terceira redundância de memória do memcached ocorre no processo de salvamento do item. O item é sempre menor ou igual ao tamanho do pedaço. Quando o item é menor que o tamanho do pedaço, o espaço é desperdiçado novamente.
◎Algoritmo NewHash do Memcached
O armazenamento de itens do Memcached é baseado em uma grande tabela hash. Seu endereço real é o deslocamento do pedaço na laje, mas seu posicionamento depende do resultado do hash da chave, que é encontrado na tabela primária_hash. Todas as operações de hash e item são definidas em assoc.c e items.c.
Memcached usa um algoritmo chamado NewHash, que é muito eficaz e eficiente. Existem algumas diferenças entre NewHash em 1.1 e 1.2. O método de implementação principal ainda é o mesmo. A função hash de 1.2 foi organizada e otimizada e sua adaptabilidade é melhor.
Referência do protótipo NewHash: http://burtleburtle.net/bob/hash/evahash.html . Os matemáticos são sempre um pouco estranhos, haha ~
Para facilitar a conversão, dois tipos de dados, u4 e u1, são definidos. u4 é um número inteiro longo não assinado e u1 é um caractere não assinado (0-255).
Para códigos específicos, consulte os pacotes de código-fonte 1.1 e 1.2.
Preste atenção ao comprimento da tabela hash aqui. Também há uma diferença entre 1.1 e 1.2. Em 1.1, a constante HASHPOWER é definida como 20 e o comprimento da tabela hash é hashsize (HASHPOWER), que é 4 MB (hashsize é uma macro, indicando. (que 1 é deslocado para a direita em n bits). Em 1.2 é a variável 16, ou seja, o comprimento da tabela hash é 65536:
CÓDIGO:[Copiar para a área de transferência]typedef unsigned long int ub4; /* quantidades de 4 bytes não assinadas */
typedef unsigned char ub1; /* quantidades de 1 byte não assinadas */
#define hashsize(n) ((ub4)1<<(n))
#define máscara de hash(n) (hashsize(n)-1)
Em assoc_init(), o Primary_hashtable será inicializado. As operações de hash correspondentes incluem: assoc_find(), assoc_expand(), assoc_move_next_bucket(), assoc_insert(), assoc_delete(), correspondentes às operações de leitura e gravação do item. Entre eles, assoc_find() é uma função que encontra o endereço do item correspondente com base na chave e no comprimento da chave (observe que em C, muitas vezes a string e o comprimento da string são passados diretamente ao mesmo tempo, em vez de fazer strlen dentro da função ), e o que é retornado é o ponteiro da estrutura do item, seu endereço de dados está em um pedaço da placa.
items.c é o programa de operação para itens de dados. Cada item completo inclui várias partes, que são definidas em item_make_header() como:
key: key.
nkey: comprimento da chave
flags: sinalizador definido pelo usuário (na verdade, esse sinalizador não está habilitado no memcached)
nbytes: comprimento do valor (incluindo símbolo de nova linha rn)
sufixo: sufixo Buffer
nsuffix: O
comprimento do sufixo de um item completo é o comprimento da chave + comprimento do valor + comprimento do sufixo + tamanho da estrutura do item (32 bytes). A operação do item é baseada neste comprimento para calcular o classid da placa.
Cada bucket na tabela hash é suspenso com uma lista duplamente vinculada. Durante item_init(), as três matrizes de cara, coroa e tamanhos foram inicializadas como 0. Os tamanhos dessas três matrizes são a constante LARGEST_ID (o padrão é 255, este valor requer modificação com fator), cada vez que item_assoc() for chamado, ele primeiro tentará obter um pedaço livre da laje. Se não houver nenhum pedaço disponível, ele verificará a lista vinculada 50 vezes para obter um pedaço que estava. iniciado pelo item LRU, desvincule-o e insira o item a ser inserido na lista vinculada.
Preste atenção ao membro refcount do item. Após o item ser desvinculado, ele só é removido da lista vinculada. Ele não é liberado imediatamente, apenas colocado na fila de exclusão (função item_unlink_q()).
O item corresponde a algumas operações de leitura e gravação, incluindo remoção, atualização e substituição. Obviamente, a mais importante é a operação de alocação.
Outra característica do item é que ele possui um tempo de expiração, que é um recurso muito útil do memcached. Muitas aplicações dependem da expiração do item do memcached, como armazenamento de sessão, bloqueios de operação, etc. A função item_flush_expired() verifica os itens da tabela e executa uma operação de desvinculação em itens expirados. Claro, esta é apenas uma ação de reciclagem. Na verdade, o julgamento do tempo também é necessário ao obter:
CODE:[Copiar para a área de transferência]/* expira itens mais recentes que a configuração mais antiga */.
void item_flush_expired() {
int eu;
item *iter, *próximo;
se (! configurações.oldest_live)
retornar;
para (i = 0; i <LARGEST_ID; i++) {
/* O LRU é classificado em ordem decrescente de tempo e o carimbo de data/hora de um item
* nunca é mais recente que seu último horário de acesso, então só precisamos caminhar
* voltar até atingirmos um item mais antigo que o tempo mais antigo.
* A verificação mais antiga_live expirará automaticamente os itens restantes.
*/
for (iter = cabeças[i]; iter != NULL; iter = próximo) {
if (iter->tempo >= settings.oldest_live) {
próximo = iter->próximo;
if ((iter->it_flags & ITEM_SLABBED) == 0) {
item_unlink(iter);
}
} outro {
/* Atingimos o primeiro item antigo. Continue para a próxima fila */.
quebrar;
}
}
}
}
CÓDIGO:[Copiar para a área de transferência]/* wrapper em torno de assoc_find que faz a lógica de expiração/exclusão lenta */
item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {
item *it = assoc_find(chave, nchave);
if (delete_locked) *delete_locked = 0;
if (it && (it->it_flags & ITEM_DELETED)) {
/* está sinalizado como delete-locked, vamos ver se essa condição.
está vencido e o delete_timer de 5 segundos simplesmente não
ainda não consegui... */
if (!item_delete_lock_over(it)) {
if (delete_locked) *delete_locked = 1;
isto = 0;
}
}
if (it && settings.oldest_live && settings.oldest_live <= current_time &&
it->hora <= settings.oldest_live) {
item_unlink(isso);
isto = 0;
}
if (it && it->exptime && it->exptime <= current_time) {
item_unlink(isso);
isto = 0;
}
devolva-o;
}
O método de gerenciamento de memória do Memcached é muito sofisticado e eficiente. Ele reduz bastante o número de alocações diretas de memória do sistema, reduz a sobrecarga de funções e a probabilidade de fragmentação de memória. Embora esse método cause algum desperdício redundante, esse desperdício é trivial em sistemas grandes. aplicações.
◎O método de cálculo de parâmetros teóricos do Memcached
possui vários parâmetros que afetam o trabalho do memcached:
constante REALTIME_MAXDELTA 60*60*24*30
Tempo máximo de expiração de 30 dias
freetotal (=200) em conn_init()
Número máximo de conexões simultâneas
constante KEY_MAX_LENGTH 250
de comprimento máximo da chave.fator
(=1,25)
fator afetará o tamanho do passo do pedaço
settings.maxconns (=1024)
máximas de conexão suave.chunk_size
(=48)
Um comprimento de chave+valor estimado de forma conservadora, usado para gerar o comprimento do pedaço (1.2) em id1. O comprimento do pedaço de id1 é igual a esse valor mais o comprimento da estrutura do item (32), que é o padrão de 80 bytes.
Constante POWER_SMALLEST 1
Mínimo classid (1.2)
constante POWER_LARGEST 200
máxima de classid (1.2)
POWER_BLOCK 1048576
de tamanho de laje padrão
CHUNK_ALIGN_BYTES (sizeof(void *))
Certifique-se de que o tamanho do bloco seja um múltiplo inteiro deste valor para evitar limites (o comprimento de void * é diferente em sistemas diferentes, é 4 em sistemas padrão de 32 bits)
constante ITEM_UPDATE_INTERVAL 60
de intervalo de atualização da fila
LARGEST_ID 255
Número máximo de itens na lista vinculada (este valor não pode ser menor que o maior classid)
variável hashpower (constante HASHPOWER em 1.1)
Determinando o tamanho da tabela hash
Com base no conteúdo e nas configurações de parâmetros apresentados acima, alguns resultados podem ser calculados:
1. Não há limite máximo de software para o número de itens que podem ser salvos no memcached. errado.
2. Supondo que o algoritmo NewHash tenha colisões uniformes, o número de ciclos para encontrar um item é o número total de itens dividido pelo tamanho da hashtable (determinado pelo hashpower), que é linear.
3. O Memcached limita o item máximo aceitável a 1 MB e dados maiores que 1 MB serão ignorados.
4. A utilização do espaço do Memcached tem uma ótima relação com as características dos dados e também está relacionada à constante DONT_PREALLOC_SLABS. Na pior das hipóteses, 198 placas serão desperdiçadas (todos os itens estão concentrados em uma placa e todos os 199 IDs estão totalmente alocados).
◎Otimização de comprimento fixo do Memcached
Com base nas descrições das seções acima, tenho uma compreensão mais aprofundada do memcached. Ele só pode ser otimizado com base em um entendimento profundo.
O próprio Memcached é projetado para dados de comprimento variável. De acordo com as características dos dados, pode-se dizer que é um design "orientado ao público", mas muitas vezes nossos dados não são tão "universais". distribuição não uniforme, ou seja, o comprimento dos dados está concentrado em várias áreas (como salvar sessões do usuário); o outro estado mais extremo são dados de comprimento igual (como valores de chave de comprimento fixo, dados de comprimento fixo, principalmente); visto em acessos, estatísticas online ou bloqueios de execução).
Aqui estudamos principalmente a solução de otimização para dados de comprimento fixo (1.2). Os dados centralizados distribuídos de comprimento variável são apenas para referência e são fáceis de implementar.
Para resolver dados de comprimento fixo, a primeira coisa que precisa ser resolvida é o problema de alocação de lajes. A primeira coisa que precisa ser confirmada é que não precisamos de tantas lajes com comprimentos de pedaços diferentes para maximizar o uso. de recursos, é melhor que pedaços e itens tenham o mesmo comprimento, então primeiro calcule o comprimento do item.
Já existia um algoritmo para calcular o comprimento do item. Deve-se observar que, além do comprimento da string, o comprimento da estrutura do item de 32 bytes deve ser adicionado.
Suponha que calculamos que precisamos salvar 200 bytes de dados de igual comprimento.
A próxima etapa é modificar a relação entre o classid da laje e o comprimento do pedaço. Na versão original, existe um relacionamento correspondente entre o comprimento do pedaço e o classid. Agora, se todos os pedaços estiverem definidos como 200 bytes, esse relacionamento não existe. Um método é utilizar apenas um ID fixo para toda a estrutura de armazenamento, ou seja, utilizar apenas 1 dos 199 slots. Sob esta condição, DONT_PREALLOC_SLABS deve ser definido para evitar desperdício adicional de pré-alocação. Outro método é estabelecer um relacionamento de hash para determinar o classid do item. Você não pode usar o comprimento como chave. Você pode usar dados variáveis, como o resultado NewHash da chave, ou fazer o hash diretamente com base na chave. chaves de dados de comprimento fixo também devem ter o mesmo comprimento). Por uma questão de simplicidade, escolhemos o primeiro método. A desvantagem deste método é que apenas um ID é usado. Quando a quantidade de dados é muito grande, a cadeia de blocos será muito longa (porque todos os dados estão lotados). uma cadeia). O custo de travessia é relativamente alto.
Os três tipos de redundância de espaço foram introduzidos anteriormente. Definir o comprimento do pedaço igual ao comprimento do item resolve o primeiro problema de desperdício de espaço. Não solicitar espaço antecipadamente resolve o segundo problema de desperdício de espaço. )? Para resolver isso, você precisa modificar a constante POWER_BLOCK para que o tamanho de cada placa seja exatamente igual a um múltiplo inteiro do comprimento do pedaço, para que uma placa possa ser dividida em n pedaços. Este valor deve estar próximo de 1 MB. Se for muito grande, também causará redundância. Se for muito pequeno, causará muitas alocações. De acordo com o comprimento do bloco de 200, escolha 1000000 como o valor de POWER_BLOCK. dessa forma, um bloco terá 1 milhão de bytes, e não 1.048.576. Todos os três problemas de redundância serão resolvidos e a utilização do espaço será bastante melhorada.
Modifique a função Slabs_clsid para que ela retorne diretamente um valor fixo (como 1):
CÓDIGO:[Copiar para a área de transferência]unsigned int lajes_clsid(tamanho_t tamanho) {
retornar 1;
}
Modifique a função Slabs_init, remova a parte que faz o loop para criar todos os atributos classid e adicione diretamente Slabclass[1]:
CODE:[Copiar para área de transferência]slabclass[1].size = 200; //200 bytes por bloco
lajeclass[1].perslab = 5000; //1000000/200
◎Cliente Memcached
Memcached é um programa de serviço. Ao usá-lo, você pode se conectar ao servidor memcached de acordo com seu protocolo, enviar comandos ao processo de serviço e operar os dados acima. Para facilitar o uso, o memcached possui diversos programas clientes disponíveis, correspondentes a vários idiomas, e existem clientes em vários idiomas. Aqueles baseados na linguagem C incluem libmemcache e APR_Memcache; aqueles baseados em Perl incluem Cache::Memcached, também há suporte para Python, Ruby, Java, C# e outras linguagens. PHP tem o maior número de clientes, não apenas as duas extensões mcache e PECL memcache, mas também um grande número de classes de encapsulamento escritas por PHP. Aqui está uma introdução sobre como usar memcached em PHP:
A extensão mcache é reencapsulada com base em libmemcache. . libmemcache não lançou uma versão estável. A versão atual é 1.4.0-rc2, que pode ser encontrada aqui. Um recurso muito ruim do libmemcache é que ele grava muitas mensagens de erro no stderr. Geralmente, quando usado como lib, o stderr geralmente é direcionado para outros lugares, como o log de erros do Apache, e o libmemcache cometerá suicídio, o que pode causar Anormal. , mas seu desempenho ainda é muito bom.
A extensão mcache foi atualizada pela última vez para 1.2.0-beta10. O autor provavelmente não apenas parou de atualizar, mas também não conseguiu abrir o site (~_~). . Após a descompactação, o método de instalação é o usual: phpize & configure & make & make install Certifique-se de instalar o libmemcache primeiro. Usar esta extensão é simples:
CÓDIGO:[Copiar para a área de transferência]<?php
$mc = memcache(); // Cria um objeto de conexão memcache.
$mc->add_server('localhost', 11211); // Adiciona um processo de serviço
$mc->add_server('localhost', 11212); // Adiciona um segundo processo de serviço
$mc->set('key1', 'Hello'); // Escreva key1 => Olá
$mc->set('key2', 'World', 10); // Escreve key2 => World, expira em 10 segundos
$mc->set('arr1', array('Hello', 'World')); // Escreve um array
$key1 = $mc->get('key1'); // Obtenha o valor de 'key1' e atribua-o a $key1
$key2 = $mc->get('key2'); // Obtenha o valor de 'key2' e atribua-o a $key2.
$arr1 = $mc->get('arr1'); // Obtém o array 'arr1'
$mc->delete('arr1'); // Excluir 'arr1'
$mc->flush_all(); // Exclui todos os dados
$stats = $mc->stats(); // Obtém informações do servidor
var_dump($stats); // As informações do servidor são um array
?>
A vantagem desta extensão é que ela pode implementar facilmente armazenamento distribuído e balanceamento de carga, pois pode adicionar vários endereços de serviço. Ao salvar dados, eles serão localizados em um determinado servidor com base no resultado do hash. . libmemcache suporta métodos de hash centralizados, incluindo CRC32, ELF e hash Perl.
PECL memcache é uma extensão lançada pela PECL. A versão mais recente é a 2.1.0, que pode ser obtida no site do pecl. O uso da extensão memcache pode ser encontrado em alguns manuais PHP mais recentes. É muito semelhante ao mcache, muito semelhante:
CÓDIGO:[Copiar para área de transferência]<?php
$memcache = new Memcache;
$memcache->connect('localhost', 11211) ou die ("Não foi possível conectar");
$version = $memcache->getVersion();
echo "Versão do servidor: ".$version."n";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'teste';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) ou die ("Falha ao salvar dados no servidor");
echo "Armazenar dados no cache (os dados expirarão em 10 segundos)n";
$get_result = $memcache->get('key');
echo "Dados do cache:n
"
;
Esta extensão usa stream do PHP para conectar-se diretamente ao servidor memcached e enviar comandos através do soquete. Não é tão completo quanto o libmemcache, nem suporta operações distribuídas como add_server, mas por não depender de outros programas externos, tem melhor compatibilidade e é relativamente estável. Quanto à eficiência, a diferença não é grande.
Além disso, existem muitas classes PHP disponíveis, como MemcacheClient.inc.php, e muitas podem ser encontradas em phpclasses.org. Elas geralmente são reencapsulamento da API do cliente Perl e são usadas de maneira semelhante.
◎BSM_Memcache
Da perspectiva do cliente C, APR_Memcache é um programa cliente muito maduro e estável que suporta bloqueios de thread e operações de nível atômico para garantir estabilidade operacional. No entanto, ele é baseado no APR (APR será introduzido na última seção) e não possui um intervalo de aplicativos tão amplos quanto o LibMemCache. porque não pode correr para fora do ambiente APR. No entanto, o APR pode ser instalado separadamente do Apache.
O BSM_MEMCACHE é uma extensão PHP com base no APR_MEMCACH que eu desenvolvi no projeto BS.Magic. Este programa é muito simples e não faz muitas funções.
Diferente da extensão do McAche que suporta armazenamento distribuído multi-servidor, o BSM_MEMCACHE suporta vários grupos de servidores. , o backup quente é implementado. Obviamente, o custo da implementação dessa função é o sacrifício de desempenho. Normalmente você pode obtê -lo na próxima vez.
Bsm_memcache suporta apenas essas funções:
Código: [copie para a área de transferência] zend_function_entry bsm_memcache_functions [] =
{
Php_fe (mc_get, nulo)
Php_fe (mc_set, nulo)
Php_fe (mc_del, nulo)
Php_fe (mc_add_group, null)
Php_fe (mc_add_server, nulo)
Php_fe (mc_shutdown, nulo)
{NULL, NULL, NULL}
};
A função mc_add_group retorna um número inteiro (na verdade, deve ser um objeto, eu era preguiçoso ~ _ ~) como o ID do grupo. AddRort).
Código: [cópia para a área de transferência]/**
*Adicione um grupo de servidor
*/
Php_function (mc_add_group)
{
apr_int32_t group_id;
APR_STATUS_T
RV;
{
Errado_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: [cópia para a área de transferência]/**
* Adicione um servidor no grupo
*/
Php_function (mc_add_server)
{
APR_STATUS_T RV;
apr_int32_t group_id;
duplo g;
char *srv_str;
int srv_str_l
;
{
Errado_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ds", & g, & srv_str, & srv_str_l) == falha)
{
Return_false;
}
group_id = (APR_INT32_T) G
;
{
Return_false;
}
char *host, *escopo;
APR_PORT_T PORT
;
if (APR_SUCCESS == RV)
{
// Crie este objeto de servidor
apr_memcache_server_t *st;
rv = APR_MEMCACHE_SERVER_CREATE (p, host, porta, 0, 64, 1024, 600 e st);
if (APR_SUCCESS == RV)
{
if (null == mc_groups [group_id])
{
Return_false;
}
// Adicionar servidor
APR_MEMCACHE_ADD_SERVER
(MC_GROUPS [GRUPO_ID], ST);
{
Return_true;
}
}
}
Return_false;
}
Ao definir e deleitar dados, atravesse todos os grupos:
Código: [cópia para a área de transferência]/**
* Armazene o item em todos os grupos
*/
Php_function (mc_set)
{
CHAR *chave, *valor;
int key_l, value_l;
duplo ttl = 0;
duplo set_ct = 0
;
{
Errado_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "ss | d", & key, & key_l, & value, & value_l, ttl) == falha)
{
Return_false;
}
// Escreva dados em cada objeto
APR_INT32_T I = 0;
if (ttl <0)
{
ttl = 0;
}
APR_STATUS_T RV
;
{
if (0 == is_validate_group (i))
{
// Escreva!
rv = APR_MEMCACHE_ADD (MC_GROUPS [i], chave, value, value_l, (APR_UINT32_T) TTL, 0);
if (APR_SUCCESS == RV)
{
set_ct ++;
}
}
}
Return_double (set_ct);
}
Em Mc_Get, você primeiro seleciona aleatoriamente um grupo e depois inicia a pesquisa deste grupo:
Código: [cópia para a área de transferência]/**
* Buscar um item de um grupo aleatório
*/
Php_function (mc_get)
{
char *chave, *value = null;
int key_l;
APR_SIZE_T VALOR_L
;
{
Errado_param_count;
}
if (zend_parse_parameters (zend_num_args () tsrmls_cc, "s", & key, & key_l) == falha)
{
Return_mull ();
}
// Vou tentar...
// leitura aleatória
apr_int32_t curr_group_id = random_group ();
APR_INT32_T I = 0;
APR_INT32_T TRY = 0;
APR_UINT32_T Sinalizador;
APR_MEMCACHE_T *OPER;
APR_STATUS_T RV
;
{
tente = i + curr_group_id;
tente = tente % max_group;
if (0 == is_validate_group (tente))
{
// Obtenha um valor
oper = mc_groups [tentativa];
rv = APR_MEMCACHE_GETP (MC_GROUPS [TRY], P, (const char *) Key, & value, & value_l, 0);
if (APR_SUCCESS == RV)
{
Return_string (valor, 1);
}
}
}
Return_false;
}
Código: [cópia para a área de transferência]/**
* ID de grupo aleatório
* Para mc_get ()
*/
APR_INT32_T RANDOM_GROUP ()
{
TV TIMVAL STRUT;
Fuzo -horário de estrutura TZ;
Int
USEC
;
}
O uso do BSM_MEMCACHE é semelhante a outros clientes:
Código: [cópia para a área de transferência] <? 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 ');
(
$ G2, '10 .0.0.17: 11211 ');
$ key = mc_get ('key');
mc_del ('chave');
mc_shutdown ();
?>
Informações relevantes sobre APR_MEMCache podem ser encontradas aqui, e o BSM_MEMCACHE pode ser baixado neste site.
◎ APR IMPORMENTO Introdução
O nome completo de APR: Apache Portable Run Runtime. É um conjunto de bibliotecas de idiomas C de plataforma cruzada criadas e mantidas pela Apache Software Foundation. Ele é extraído do apache httpd1.x e é independente do httpd. A APR fornece muitas interfaces de API convenientes para uso, incluindo funções práticas, como pools de memória, operações de cordas, redes, matrizes, tabelas de hash etc. O desenvolvimento do módulo Apache2 requer exposição a muitas funções APR.
◎ PostScript
Este é o meu último artigo no ano do Bingxu do calendário lunar (meu ano de nascimento). Agradecemos ao SINA.com por oferecer oportunidades de pesquisa e colegas do departamento por sua ajuda.
Dr. NP02-13-2007