Sem categoria

Criando seu próprio mecanismo de armazenamento para o Memcached, parte 2

Na postagem anterior do blog, descrevi a inicialização e a destruição do mecanismo. Esta postagem do blog abordará o modelo de alocação de memória na interface do mecanismo.

O núcleo do memcached é responsável por alocar toda a memória necessária para suas conexões (buffers de envio/recebimento etc.), e o mecanismo é responsável por alocar (e liberar) toda a memória necessária para manter o controle dos itens. Os mecanismos não devem se preocupar com a memória que o núcleo aloca (e usa), mas o núcleo acessará a memória gerenciada pelo mecanismo.

Quando o núcleo do memcached está prestes a armazenar um novo item, ele precisa obter um buffer (até o momento, contínuo) para armazenar os dados do item. O núcleo tentará alocar esse buffer chamando a função alocar na API. Portanto, vamos começar a estender nosso código de exemplo adicionando nossa própria implementação da função alocar função. A primeira coisa que precisamos fazer é adicioná-la ao nosso descritor de mecanismo que retornamos de create_instance. Vamos adicionar várias funções na entrada de hoje, portanto, vamos mapear todas elas enquanto fazemos isso:

MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface,
                                  GET_SERVER_API get_server_api,
                                  ENGINE_HANDLE **handle)
{
   [ ... cortar ... ]
  /*
   * Mapear os pontos de entrada da API para nossas funções que os implementam.
   */
   h->engine.initialize = fs_initialize;
   h->engine.destroy = fs_destroy;
   h->engine.get_info = fs_get_info;
   h->engine.allocate = fs_allocate;
   h->engine.remove = fs_item_delete;
   h->engine.release = fs_item_release;
   h->engine.get = fs_get;
   h->engine.get_stats = fs_get_stats;
   h->engine.reset_stats = fs_reset_stats;
   h->engine.store = fs_store;
   h->engine.flush = fs_flush;
   h->engine.unknown_command = fs_unknown_command;
   h->engine.item_set_cas = fs_item_set_cas;
   h->engine.get_item_info = fs_get_item_info;

     

A próxima coisa que precisamos fazer é criar uma estrutura de dados para manter as informações de que precisamos. O objetivo deste tutorial não é criar uma implementação eficiente em termos de memória, mas exercitar a API. Portanto, vamos criar a seguinte estrutura:

struct fs_item {
   void *key;
   size_t nkey;
   void *data;
   size_t ndata;
   int flags;
   rel_time_t exptime;
};
     

Nossa implementação de alocar teria a seguinte aparência:

 

static ENGINE_ERROR_CODE fs_allocate(ENGINE_HANDLE* handle,
                                     const void* cookie,
                                     item **item,
                                     const void* key,
                                     const size_t nkey,
                                     const size_t nbytes,
                                     const int flags,
                                     const rel_time_t exptime)
{
   struct fs_item *it = malloc(sizeof(struct fs_item));
   Se (it == NULL) {
      return ENGINE_ENOMEM;
   }
   it->flags = flags;
   it->exptime = exptime;
   it->nkey = nkey;
   it->ndata = nbytes;
   it->key = malloc(nkey);
   it->data = malloc(nbytes);
   Se (it->key == NULL || it->data == NULL) {
      free(it->key);
      free(it->data);
      free(it);
      return ENGINE_ENOMEM;
   }
   memcpy(it->key, key, nkey);
   *item = it;
   retornar ENGINE_SUCCESS;
}
     

Se você observar a implementação acima, verá que não retornamos o ponteiro para a memória real do armazenamento de dados para o núcleo do memcached. Para obter esse endereço, o memcached chamará get_item_info na API. Então, vamos implementar isso:

static bool fs_get_item_info(ENGINE_HANDLE *handle, const void *cookie,
                             const item* item, item_info *item_info)
{
   struct fs_item* it = (struct fs_item*)item;
   if (item_info->nvalue < 1) {
      retornar falso;
   }

   item_info->cas = 0; /* Não suportado */
   item_info->clsid = 0; /* Não suportado */
   item_info->exptime = it->exptime;
   item_info->flags = it->flags;
   item_info->key = it->key;
   item_info->nkey = it->nkey;
   item_info->nbytes = it->ndata; /* Comprimento total dos dados do item */
   item_info->nvalue = 1; /* Número de fragmentos usados */
   item_info->value[0].iov_base = it->data; /* ponteiro para o fragmento 1 */
   item_info->value[0].iov_len = it->ndata; /* Comprimento do fragmento 1 */

   retornar verdadeiro;
}
     

O get_item_info é importante e merece mais informações. Se você observar a API do mecanismo, o "item" é definido como um ponteiro vazio, e nós definimos nossa própria estrutura de item para manter o controle das informações de que precisamos por item. No entanto, o núcleo do memcached precisará saber
onde ler/gravar a memória para a chave e os dados que vão/vem de um clinet. Para isso, invocaremos get_item_info. Se você observar atentamente nossa implementação do fs_get_item_info Você verá que a primeira coisa que estou fazendo é verificar se item_info->nvalue contém pelo menos 1
elemento. No momento, ele irá sempre mas a intenção é que apoiemos a IO dispersa.

Quando o núcleo terminar de mover os dados que recebeu pelo cabo para o item, ele tentará armazenar o item em nosso mecanismo chamando store. Portanto, vamos criar uma implementação simples (vamos ampliá-la mais adiante no tutorial):

static ENGINE_ERROR_CODE fs_store(ENGINE_HANDLE* handle,
                                  const void *cookie,
                                  item* item,
                                  uint64_t *cas,
                                  Operação ENGINE_STORE_OPERATION,
                                  uint16_t vbucket)
{
   struct fs_item* it = item;
   char fname[it->nkey + 1];
   memcpy(fname, it->key, it->nkey);
   fname[it->nkey] = '
   FILE *fp = fopen(fname, "w");
   Se (fp == NULL) {
      return ENGINE_NOT_STORED;
   }
   size_t nw = fwrite(it->data, 1, it->ndata, fp);
   fclose(fp);
   se (nw != it->ndata) {
      remove(fname);
      return ENGINE_NOT_STORED;
   }

   *cas = 0;
   retornar ENGINE_SUCCESS;
}
     

Se você observar a implementação acima, verá que ela não implementa a semântica correta para adicionar/substituir/definir etc., e isso bloqueará o memcached enquanto estivermos fazendo o IO do arquivo. Não se preocupe com isso agora, pois voltaremos a esse assunto.

Quando o núcleo terminar de usar o item alocado, ele liberará o item chamando a função liberação na API. O mecanismo pode reutilizar o armazenamento de itens para outra coisa nesse momento. Então, vamos conectar nosso liberação implementação:

static void fs_item_release(ENGINE_HANDLE* handle,
                            const void *cookie,
                            item* item)
{
   struct fs_item *it = item;
   free(it->key);
   free(it->data);
   free(it);
}
     

Agora criamos todo o código para armazenar itens com sucesso em nosso mecanismo, mas não podemos ler nenhum deles de volta. Então, vamos implementar obter

static ENGINE_ERROR_CODE fs_get(ENGINE_HANDLE* handle,
                                const void* cookie,
                                item** item,
                                const void* key,
                                const int nkey,
                                uint16_t vbucket)
{

   char fname[nkey + 1];
   memcpy(fname, key, nkey);
   fname[nkey] = '

   struct stat st;
   se (stat(fname, &st) == -1) {
      return ENGINE_NOT_STORED;
   }

   struct fs_item* it = NULL;
   ENGINE_ERROR_CODE ret = fs_allocate(handle, cookie, (void**)&it, key, nkey,
                                       st.st_size, 0, 0);
   Se (ret != ENGINE_SUCCESS) {
      return ENGINE_ENOMEM;
   }

   FILE *fp = fopen(fname, "r");
   Se (fp == NULL) {
      fs_release(handle, cookie, it);
      retornar ENGINE_FAILED;
   }

   size_t nr = fread(it->data, 1, it->ndata, fp);
   fclose(fp);
   se (nr != it->ndata) {
      fs_release(handle, cookie, it);
      retornar ENGINE_FAILED;
   }

   *item = it;
   retornar ENGINE_SUCCESS;
}
     

Vamos adicionar uma implementação fictícia para o restante da API e tentar carregar e testar o mecanismo:

static const engine_info* fs_get_info(ENGINE_HANDLE* handle)
{
   static engine_info info = {
      .description = "Mecanismo do sistema de arquivos v0.1",
      .num_features = 0
   };

   return &info;
}

static ENGINE_ERROR_CODE fs_item_delete(ENGINE_HANDLE* handle,
                                        const void* cookie,
                                        const void* key,
                                        const size_t nkey,
                                        uint64_t cas,
                                        uint16_t vbucket)
{
   return ENGINE_KEY_ENOENT;
}

static ENGINE_ERROR_CODE fs_get_stats(ENGINE_HANDLE* handle,
                                      const void* cookie,
                                      const char* stat_key,
                                      int nkey,
                                      ADD_STAT add_stat)
{
   retornar ENGINE_SUCCESS;
}

static ENGINE_ERROR_CODE fs_flush(ENGINE_HANDLE* handle,
                                  const void* cookie, time_t when)
{

   retornar ENGINE_SUCCESS;
}

static void fs_reset_stats(ENGINE_HANDLE* handle, const void *cookie)
{

}

static ENGINE_ERROR_CODE fs_unknown_command(ENGINE_HANDLE* handle,
                                            const void* cookie,
                                            protocol_binary_request_header *request,
                                            Resposta ADD_RESPONSE)
{
   retornar ENGINE_ENOTSUP;
}

static void fs_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
                            item* item, uint64_t val)
{
}

     

Então, vamos testar nosso mecanismo:

trond@opensolaris> /opt/memcached/bin/memcached -E .libs/fs_engine.so
     

De outro terminal em que estou digitando:

trond@opensolaris> telnet localhost 11211
Tentando ::1...
Conectado ao opensolaris.
O caractere de escape é '^]'.
adicionar teste 0 0 4
teste
ARMAZENADO
obter teste
VALUE test 0 4
teste
FIM
sair
Conexão com a tempestade fechada pelo host externo.
     

Encerre o memcached pressionando ctrl-ce procurar no diretório atual:

trond@opensolaris> ls -l test
-rw-r-r- 1 trond users 6 Oct 8 12:56 test
trond@opensolaris> cat test
teste
     

Isso é tudo por enquanto.

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

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.

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.