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:

$ git clone git://github.com/trondn/memcached.git
$ 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:

$ /opt/memcached/bin/memcached -E default_engine.so &
$ 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.

ROOT=/opt/memcached
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:

MEMCACHED_PUBLIC_API
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 interfacee 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:

struct fs_engine {
  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():

static void fs_destroy(ENGINE_HANDLE* handle) {
  /* 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:

static ENGINE_ERROR_CODE fs_initialize(ENGINE_HANDLE* handle,
                                      const char* config_str) {
  retornar ENGINE_SUCCESS;
}
     

Se o motor retornar algo diferente de ENGINE_SUCCESSo 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.

 

Autor

Postado por Trond Norbye, desenvolvedor sênior, Couchbase

Trond Norbye é arquiteto de software na Couchbase. Principal colaborador dos projetos Couchbase e Memcached. Criou as bibliotecas de clientes C/C++ e node.js do Couchbase.

Deixar uma resposta