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:
$ 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:
$ 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.
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:
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 interfaz
y 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
:
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()
:
/* 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:
const char* config_str) {
return ÉXITO_INGENIO;
}
Si el motor devuelve algo más que ENGINE_SUCCESS
el 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.