Memcached (pronunciado de diversas formas mem-cash-dee o mem-cashed ) es un sistema de almacenamiento en caché de memoria distribuido de propósito general . A menudo se utiliza para acelerar los sitios web dinámicos basados en bases de datos mediante el almacenamiento en caché de datos y objetos en la RAM para reducir la cantidad de veces que se debe leer una fuente de datos externa (como una base de datos o API). Memcached es un software gratuito y de código abierto , con licencia BSD revisada . [2] Memcached se ejecuta en sistemas operativos tipo Unix ( Linux y macOS ) y en Microsoft Windows . Depende de la biblioteca libevent .
Las API de Memcached proporcionan una tabla hash muy grande distribuida en varias máquinas. Cuando la tabla está llena, las inserciones posteriores hacen que los datos más antiguos se eliminen en el orden de los menos utilizados recientemente (LRU). [3] [4] Las aplicaciones que utilizan Memcached suelen colocar las solicitudes y las adiciones en la RAM antes de recurrir a un almacenamiento de respaldo más lento, como una base de datos.
Memcached no tiene ningún mecanismo interno para realizar un seguimiento de los errores que puedan ocurrir. Sin embargo, algunas utilidades de terceros ofrecen esta funcionalidad.
Memcached fue desarrollado por primera vez por Brad Fitzpatrick para su sitio web LiveJournal , el 22 de mayo de 2003. [5] [6] Originalmente fue escrito en Perl , luego reescrito en C por Anatoly Vorobey, luego empleado por LiveJournal. [7] Memcached ahora es utilizado por muchos otros sistemas, incluidos YouTube , [8] Reddit , [9] Facebook , [10] [11] Pinterest , [12] [13] Twitter , [14] Wikipedia , [15] y Method Studios . [16] Google App Engine , Google Cloud Platform , Microsoft Azure , IBM Bluemix y Amazon Web Services también ofrecen un servicio Memcached a través de una API. [17] [18] [19] [20]
El sistema utiliza una arquitectura cliente-servidor . Los servidores mantienen una matriz asociativa de clave-valor ; los clientes llenan esta matriz y la consultan por clave. Las claves tienen una longitud de hasta 250 bytes y los valores pueden tener un tamaño máximo de 1 megabyte .
Los clientes utilizan bibliotecas del lado del cliente para contactar con los servidores que, de forma predeterminada, exponen su servicio en el puerto 11211. Se admiten tanto TCP como UDP. Cada cliente conoce todos los servidores; los servidores no se comunican entre sí. Si un cliente desea establecer o leer el valor correspondiente a una clave determinada, la biblioteca del cliente primero calcula un hash de la clave para determinar qué servidor utilizar. Esto proporciona una forma sencilla de fragmentación y una arquitectura escalable de no compartir nada entre los servidores. El servidor calcula un segundo hash de la clave para determinar dónde almacenar o leer el valor correspondiente. Los servidores mantienen los valores en la RAM; si un servidor se queda sin RAM, descarta los valores más antiguos. Por lo tanto, los clientes deben tratar Memcached como un caché transitorio; no pueden asumir que los datos almacenados en Memcached todavía están allí cuando los necesitan. Otras bases de datos, como MemcacheDB , Couchbase Server , proporcionan almacenamiento persistente al tiempo que mantienen la compatibilidad del protocolo Memcached.
Si todas las bibliotecas cliente utilizan el mismo algoritmo hash para determinar servidores, entonces los clientes pueden leer los datos almacenados en caché de los demás.
Una implementación típica tiene varios servidores y muchos clientes. Sin embargo, es posible utilizar Memcached en una sola computadora, actuando simultáneamente como cliente y servidor. El tamaño de su tabla hash suele ser muy grande. Está limitada a la memoria disponible en todos los servidores del clúster de servidores de un centro de datos. Cuando lo requiere la publicación web de gran volumen y para una amplia audiencia, esta capacidad puede llegar a muchos gigabytes. Memcached puede ser igualmente valioso para situaciones en las que la cantidad de solicitudes de contenido es alta o el costo de generar un contenido en particular es alto.
La mayoría de las implementaciones de Memcached se realizan en redes confiables donde los clientes pueden conectarse libremente a cualquier servidor. Sin embargo, a veces Memcached se implementa en redes que no son confiables o donde los administradores desean ejercer control sobre los clientes que se conectan. Para este propósito, Memcached se puede compilar con soporte de autenticación SASL opcional . El soporte SASL requiere el protocolo binario.
Una presentación en BlackHat USA 2010 reveló que varios sitios web públicos grandes habían dejado Memcached abierto a la inspección, análisis, recuperación y modificación de datos. [21]
Incluso dentro de una organización confiable, el modelo de confianza plana de Memcached puede tener implicaciones de seguridad. Para simplificar, todas las operaciones de Memcached se tratan por igual. Los clientes con una necesidad válida de acceso a entradas de baja seguridad dentro de la caché obtienen acceso a todas las entradas dentro de la caché, incluso cuando estas son de mayor seguridad y ese cliente no tiene una necesidad justificable de ellas. Si la clave de la caché se puede predecir, adivinar o encontrar mediante una búsqueda exhaustiva, se puede recuperar su entrada de caché.
En situaciones como la publicación web de gran volumen, se pueden hacer algunos intentos de aislar los datos de configuración y lectura. Una granja de servidores de contenido orientados al exterior tiene acceso de lectura a memcached que contiene páginas publicadas o componentes de página, pero no acceso de escritura. Cuando se publica contenido nuevo (y aún no está en memcached), se envía una solicitud a los servidores de generación de contenido que no son de acceso público para crear la unidad de contenido y agregarla a memcached. Luego, el servidor de contenido vuelve a intentar recuperarla y enviarla al exterior.
En febrero de 2018, CloudFlare informó que se utilizaron servidores memcached mal configurados para lanzar ataques DDoS a gran escala. [22] El protocolo memcached sobre UDP tiene un enorme factor de amplificación , de más de 51000. [23] Entre las víctimas de los ataques DDoS se incluye GitHub , que se inundó con un tráfico entrante máximo de 1,35 Tbit/s. [24]
Este problema se mitigó en la versión 1.5.6 de Memcached, que deshabilitó el protocolo UDP de forma predeterminada. [25]
Tenga en cuenta que todas las funciones descritas en esta página son solo pseudocódigo . Las llamadas a Memcached y los lenguajes de programación pueden variar según la API utilizada.
La conversión de consultas de creación de objetos o bases de datos para utilizar Memcached es sencilla. Normalmente, cuando se utilizan consultas de base de datos directas, el código de ejemplo sería el siguiente:
función get_foo ( int userid ) datos = db_select ( "SELECT * FROM users WHERE userid = ?" , userid ) devolver datos
Después de la conversión a Memcached, la misma llamada podría verse así
función get_foo ( int userid ) /* primero prueba el caché */ datos = memcached_fetch ( "userrow:" + userid ) si no datos /* no encontrado: solicitar base de datos */ datos = db_select ( "SELECT * FROM users WHERE userid = ?" , userid ) /* luego almacena en caché hasta la próxima obtención */ memcached_add ( "userrow:" + userid , datos ) fin devolver datos
El cliente primero verificaría si existe un valor de Memcached con la clave única "userrow:userid", donde userid es un número. Si el resultado no existe, seleccionaría de la base de datos como de costumbre y establecería la clave única mediante la llamada a la función add de la API de Memcached.
Sin embargo, si solo se modificara esta llamada API, el servidor terminaría obteniendo datos incorrectos después de cualquier acción de actualización de la base de datos: la "vista" de los datos de Memcached quedaría obsoleta. Por lo tanto, además de crear una llamada "add", también sería necesaria una llamada update utilizando la función set de Memcached.
function update_foo ( int userid , string dbUpdateString ) /* primera actualización de la base de datos */ result = db_execute ( dbUpdateString ) if result /* actualización de la base de datos exitosa: obtener los datos que se almacenarán en la memoria caché */ data = db_select ( "SELECT * FROM users WHERE userid = ?" , userid ) /* la línea anterior también podría verse como data = createDataFromDBString(dbUpdateString) */ /* luego almacenar en la memoria caché hasta la próxima obtención */ memcached_set ( "userrow:" + userid , data )
Esta llamada actualizaría los datos almacenados actualmente en caché para que coincidan con los nuevos datos en la base de datos, suponiendo que la consulta de la base de datos tenga éxito. Un enfoque alternativo sería invalidar la caché con la función de eliminación de Memcached, de modo que las recuperaciones posteriores generen un error de caché. Sería necesario tomar una acción similar cuando se eliminaran registros de la base de datos, para mantener una caché correcta o incompleta.
Una estrategia alternativa de invalidación de caché es almacenar un número aleatorio en una entrada de caché acordada e incorporar este número en todas las claves que se utilizan para almacenar un tipo particular de entrada. Para invalidar todas esas entradas a la vez, cambie el número aleatorio. Las entradas existentes (que se almacenaron utilizando el número anterior) ya no serán referenciadas y, por lo tanto, eventualmente caducarán o serán recicladas.
function store_xyz_entry ( int key , string value ) /* Recuperar el número aleatorio - usar cero si no existe ninguno todavía. * El nombre de clave usado aquí es arbitrario. */ seed = memcached_fetch ( ":xyz_seed:" ) if not seed seed = 0 /* Construir la clave usada para almacenar la entrada y almacenarla. * El nombre de clave usado aquí también es arbitrario. Observe que la "semilla" y la "clave" del usuario * se almacenan como partes separadas de la cadena hashKey construida: ":xyz_data:(seed):(key)." * Esto no es obligatorio, pero se recomienda. */ string hashKey = sprintf ( ":xyz_data:%d:%d" , seed , key ) memcached_set ( hashKey , value ) /* "fetch_entry", no se muestra, sigue una lógica idéntica a la anterior. */ function invalidate_xyz_cache () existing_seed = memcached_fetch ( ":xyz_seed:" ) /* Acuñar una semilla aleatoria diferente */ do seed = rand () till seed != existing_seed /* Ahora almacenarla en el lugar acordado. Todas las solicitudes futuras usarán este número. * Por lo tanto, todas las entradas existentes dejan de tener referencias y eventualmente expirarán. */ memcached_set ( ":xyz_seed:" , seed )