Escribir su propio motor de almacenamiento para Memcached

Trabajo a tiempo completo en membranaque utilizan el "interfaz del motor" que estamos añadiendo a Memcached. Siendo yo quien diseñó la API y escribió la documentación, puedo decir que necesitamos más (y mejor) documentación sin insultar a nadie. Esta entrada de blog será la primera entrada de un mini-tutorial sobre cómo escribir tu propio motor de almacenamiento. Intentaré cubrir todos los aspectos de la interfaz del motor mientras construimos un motor que almacene todas las claves de los archivos en el servidor.

Esta entrada cubrirá los pasos básicos para configurar tu entorno de desarrollo y cubrirá el ciclo de vida del motor.

Configurar el entorno de desarrollo

La forma más sencilla de ponerlo en marcha es instalar mi rama de desarrollo de la interfaz del motor. Sólo tienes que ejecutar los siguientes comandos:

$ git clone git://github.com/trondn/memcached.git
$ cd memcached
$ git -b motor origen/motor
$ ./config/autorun.sh
$ ./configure -prefix=/opt/memcached
$ make all install
     

Verifiquemos que el servidor funciona ejecutando los siguientes comandos:

$ /opt/memcached/bin/memcached -E default_engine.so &
$ echo version | nc localhost 11211
VERSION 1.3.3_433_g82fb476 ≶- puede obtener otra cadena de salida....
$ fg
$ ctrl-C

Creación del motor del sistema de archivos

Es posible que desee utilizar autoconf para construir su motor, pero la configuración de autoconf está mucho más allá del alcance de este tutorial. Usemos lo siguiente Makefile en su lugar.

ROOT=/opt/memcached
INCLUDE=-I${ROOT}/include

#CC = gcc
#CFLAGS=-std=gnu99 -g -DNDEBUG -fno-strict-aliasing -Wall
# -Wprototipos-estrictos -Wprototipos-faltantes -Wdeclaraciones-faltantes
# -Decls redundantes
# ${INCLUDE} -DHAVE_CONFIG_H
#LDFLAGS=-compartido

CC=cc
CFLAGS=-I${ROOT}/include -m64 -xldscope=oculto -mt -g
      -errfmt=error -errwarn -errshort=etiquetas -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)      

Estoy haciendo la mayor parte de mi desarrollo en Solaris utilizando los compiladores de Sun Studio, pero he añadido una sección con la configuración para gcc allí si usted está usando gcc. Sólo tienes que comentar las líneas para CC, CFLAGS y LDFLAGS y retire el # para las alternativas de gcc.

Para que memcached utilice tu motor de almacenamiento necesita cargar primero tu módulo, y luego crear una instancia del motor. Utiliza el módulo -E a memcached para especificar el nombre del módulo que memcached debe cargar. Con el módulo cargado memcached buscará un símbolo llamado crear_instancia en el módulo para crear un handle que memcached pueda usar para comunicarse con el motor. Esta es la primera función que necesitamos crear, y debe tener la siguiente firma:

MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface, GET_SERVER_API get_server_api, ENGINE_HANDLE **handle);
     

El propósito de esta función es proporcionar al servidor un handle de nuestro módulo, pero deberíamos no realizar ningún tipo de inicialización de nuestro motor todavía. La razón es que el servidor de memcached puede no soportar la versión de la API que proporcionamos. La intención es que el servidor notifique al motor con la versión de interfaz "más alta" que soporte a través de interfazy el motor debe devolver un descriptor a una de esas interfaces a través del handle. Si el motor no soporta alguna de esas interfaces debe devolver ENGINE_ENOTSUP.

Así que vamos a seguir adelante y definir un descriptor de motor para nuestro motor de ejemplo y crear una implementación para crear_instancia:

struct fs_engine {
  motor ENGINE_HANDLE_V1;
  /* Vamos a ampliar esta estructura más adelante */
};

MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE crear_instancia(uint64_t interfaz,
                                 GET_SERVER_API get_server_api,
                                 ENGINE_HANDLE **mango) {
  /*
   * Compruebe que la interfaz del servidor es compatible. Ahora mismo
   * sólo hay una interfaz, por lo que aceptaríamos todas (y sería
   * dependerá del servidor que nos rechace... Añado la prueba aquí para que
   * get the picture..
   */
  si (interfaz == 0) {
     return ENGINE_ENOTSUP;
  }

  /*
   * Asignar memoria para el descriptor del motor. No soy un gran fan de usar
   * variables globales, porque eso podría crear problemas más adelante si
   * más adelante decidimos crear varias instancias del mismo motor.
   * Mejor estar en el lado seguro desde el primer día...
   */
  struct fs_engine *h = calloc(1, sizeof(*h));
  if (h == NULL) {
     return ENGINE_ENOMEM;
  }

  /*
   * Vamos a implementar la primera versión de la API del motor, por lo que
   * necesitamos informar al núcleo de memcached qué tipo de estructura debe
   * esperar
   */
  h->interfaz.motor = 1;

  /*
   * Asigna los puntos de entrada de la API a las funciones que los implementan.
   */
  h->engine.initialize = fs_initialize;
  h->engine.destroy = fs_destroy;

  /* Devuelve el asa al núcleo */
  *handle = (ENGINE_HANDLE*)h;

  return ÉXITO_INGENIO;
}
     

Si la interfaz que proporcionamos en crear_instancia se elimina de las interfaces soportadas en memcached, el núcleo llamará a destruir() inmediatamente. El núcleo de memcached garantiza que nunca utilizará cualquier punteros devueltos por el motor cuando destruir() se llama.

Así que vamos a seguir adelante y poner en práctica nuestra destruir() función. Si nos fijamos en nuestra implementación de crear_instancia verá que hemos trazado destruir() a una función llamada fs_destroy():

static void fs_destroy(ENGINE_HANDLE* handle) {
  /* Liberar la memoria asignada al descriptor del motor */
  free(handle);
}
     

Si el núcleo implementa la interfaz que especifiquemos, el núcleo llamará a la función inicializar() método. Este es el momento en el que debes hacer todo tipo de inicialización en tu motor (como conectar a una base de datos, inicializar mutexes, etc). El inicializar sólo se invoca una vez por cada instancia devuelta por crear_instancia (incluso si el núcleo de memcached utiliza varios hilos). El núcleo no llamar a cualquier otra función de la api antes de que retorne el método de inicialización.

No necesitamos ningún tipo de inicialización en este momento, por lo que podemos utilizar el siguiente código de inicialización:

static ENGINE_ERROR_CODE fs_initialize(ENGINE_HANDLE* handle,
                                      const char* config_str) {
  return ÉXITO_INGENIO;
}
     

Si el motor devuelve algo más que ENGINE_SUCCESSel núcleo de memcached se negará a utilizar el motor y llamará a destruir()

En la próxima entrada del blog empezaremos a añadir funcionalidad para que podamos cargar nuestro motor y manejar comandos desde el cliente.

 
Comparte este artículo
Recibe actualizaciones del blog de Couchbase en tu bandeja de entrada
Este campo es obligatorio.

Autor

Publicado por Trond Norbye, Desarrollador Senior, Couchbase

Trond Norbye es Arquitecto de Software en Couchbase. Colaborador principal de los proyectos Couchbase y Memcached. Creó las bibliotecas de cliente C/C++ y node.js de Couchbase.

Deja un comentario

¿Listo para empezar con Couchbase Capella?

Empezar a construir

Consulte nuestro portal para desarrolladores para explorar NoSQL, buscar recursos y empezar con tutoriales.

Utilizar Capella gratis

Ponte manos a la obra con Couchbase en unos pocos clics. Capella DBaaS es la forma más fácil y rápida de empezar.

Póngase en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítanos ayudarle.