Estou trabalhando em tempo integral em base de membranaque utilizam o "interface do motor" que estamos adicionando a Memcached. Como fui eu quem projetou a API e escreveu a documentação, posso dizer que precisamos de mais (e melhor) documentação sem insultar ninguém. Esta entrada do blog será a primeira entrada em um minitutorial sobre como criar seu próprio mecanismo de armazenamento. Tentarei abordar todos os aspectos da interface do mecanismo enquanto criamos um mecanismo que armazena todas as chaves em arquivos no servidor.
Esta entrada abordará as etapas básicas de configuração de seu ambiente de desenvolvimento e o ciclo de vida do mecanismo.
Configurar o ambiente de desenvolvimento
A maneira mais fácil de começar a funcionar é instalar minha ramificação de desenvolvimento da interface do mecanismo. Basta executar os seguintes comandos:
$ cd memcached
$ git -b engine origin/engine
$ ./config/autorun.sh
$ ./configure -prefix=/opt/memcached
$ make all install
Vamos verificar se o servidor funciona executando os seguintes comandos:
$ echo version | nc localhost 11211
VERSION 1.3.3_433_g82fb476 ≶- você pode obter outra string de saída....
$ fg
$ ctrl-C
Criação do mecanismo do sistema de arquivos
Talvez você queira usar o autoconf para criar seu mecanismo, mas a configuração do autoconf está muito além do escopo deste tutorial. Vamos usar apenas o seguinte Makefile
Em vez disso.
INCLUDE=-I${ROOT}/include
#CC = gcc
#CFLAGS=-std=gnu99 -g -DNDEBUG -fno-strict-aliasing -Wall
# -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
# -Wredundant-decls
# ${INCLUDE} -DHAVE_CONFIG_H
#LDFLAGS=-compartilhado
CC=cc
CFLAGS=-I${ROOT}/include -m64 -xldscope=hidden -mt -g
-errfmt=error -errwarn -errshort=tags -KPIC
LDFLAGS=-G -z defs -m64 -mt
todos: .libs/fs_engine.so
instalar: todos
${CP} .libs/fs_engine.so ${ROOT}/lib
SRC = fs_engine.c
OBJS = ${SRC:%.c=.libs/%.o}
.libs/fs_engine.so: .libs $(OBJS)
${LINK.c} -o $@ ${OBJS}
.libs:; -@mkdir $@
.libs/%.o: %.c
${COMPILE.c} $< -o $@ clean: $(RM) .libs/fs_engine.so $(OBJS)
Estou fazendo a maior parte do meu desenvolvimento no Solaris usando os compiladores Sun Studio, mas adicionei uma seção com configurações para o gcc se você estiver usando o gcc. Basta comentar as linhas para CC
, CFLAGS
e LDFLAGS
e remova o #
para as alternativas do gcc.
Para que o memcached utilize seu mecanismo de armazenamento, ele precisa primeiro carregar seu módulo e, em seguida, criar uma instância do mecanismo. Você usa o módulo -E
ao memcached para especificar o nome do módulo que o memcached deve carregar. Com o módulo carregado, o memcached procurará um símbolo chamado create_instance
no módulo para criar um identificador que o memcached possa usar para se comunicar com o mecanismo. Essa é a primeira função que precisamos criar e deve ter a seguinte assinatura:
ENGINE_ERROR_CODE create_instance(uint64_t interface, GET_SERVER_API get_server_api, ENGINE_HANDLE **handle);
O objetivo dessa função é fornecer ao servidor um identificador para o nosso módulo, mas devemos não ainda não realizamos nenhum tipo de inicialização do nosso mecanismo. O motivo disso é que o servidor memcached pode não ser compatível com a versão da API que fornecemos. A intenção é que o servidor notifique o mecanismo com a versão "mais alta" da interface que ele suporta por meio de interface
e o motor deve retorna um descritor para uma dessas interfaces por meio do handle
. Se o motor não suportar qualquer uma dessas interfaces, ele deve retornar ENGINE_ENOTSUP
.
Portanto, vamos definir um descritor de mecanismo para o nosso mecanismo de exemplo e criar uma implementação para create_instance
:
Motor ENGINE_HANDLE_V1;
/* Vamos estender essa estrutura mais tarde */
};
MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface,
GET_SERVER_API get_server_api,
ENGINE_HANDLE **handle) {
/*
* Verificar se a interface do servidor é compatível com a nossa empresa. Agora mesmo
* há apenas uma interface, portanto, aceitaríamos todas elas (e isso seria
* cabe ao servidor nos recusar... Estou adicionando o teste aqui para que você
* Entendi...
*/
Se (interface == 0) {
retornar ENGINE_ENOTSUP;
}
/*
* Alocar memória para o descritor do mecanismo. Não sou um grande fã do uso de
* variáveis globais, pois isso pode criar problemas mais tarde se
* Mais tarde, decidimos criar várias instâncias do mesmo mecanismo.
* É melhor estar seguro desde o primeiro dia...
*/
struct fs_engine *h = calloc(1, sizeof(*h));
Se (h == NULL) {
return ENGINE_ENOMEM;
}
/*
* Vamos implementar a primeira versão da API do mecanismo, portanto
* Precisamos informar ao núcleo do memcached que tipo de estrutura ele deve
* Esperar
*/
h->engine.interface.interface = 1;
/*
* Mapear os pontos de entrada da API para nossas funções que os implementam.
*/
h->engine.initialize = fs_initialize;
h->engine.destroy = fs_destroy;
/* Passar o identificador de volta para o núcleo */
*handle = (ENGINE_HANDLE*)h;
retornar ENGINE_SUCCESS;
}
Se a interface que fornecemos em create_instance
for excluído das interfaces suportadas no memcached, o núcleo chamará destroy()
imediatamente. O núcleo do memcached garante que nunca usará qualquer retornados do mecanismo quando destroy()
é chamado.
Então, vamos em frente e implementar nosso destroy()
função. Se você observar nossa implementação de create_instance
você verá que mapeamos destroy()
para uma função chamada fs_destroy()
:
/* Liberar a memória alocada para o descritor do mecanismo */
free(handle);
}
Se o núcleo implementar a interface que especificamos, o núcleo chamará a função initialize()
método. Esse é o momento em que você deve fazer todo tipo de inicialização em seu mecanismo (como conectar-se a um banco de dados, inicializar mutexes etc.). O método inicializar
é chamada apenas uma vez por instância retornada de create_instance
(mesmo que o núcleo do memcached use vários threads). O núcleo irá não chamar quaisquer outras funções na API antes que o método de inicialização retorne.
Não precisamos de nenhum tipo de inicialização neste momento, portanto, podemos usar o seguinte código de inicialização:
const char* config_str) {
retornar ENGINE_SUCCESS;
}
Se o motor retornar algo diferente de ENGINE_SUCCESS
o núcleo do memcached se recusará a usar o mecanismo e chamará destroy()
Na próxima entrada do blog, começaremos a adicionar funcionalidades para que possamos carregar nosso mecanismo e manipular comandos do cliente.