Post invitado: Eric Lambert
El cliente Java Spymemcached es el principal cliente Java utilizado por las aplicaciones basadas en Java que necesitan hacer uso de los servidores Memcached. La razón por la que Spy se ha hecho tan popular en la comunidad Java es que ha sido altamente optimizado y que proporciona un rendimiento excepcional, pero al mismo tiempo lo hace sin cargar al usuario con una complejidad excesiva.
Para los casos más típicos, los usuarios sólo tienen que utilizar la siguiente "plantilla List servers = new ArrayList(); servers.add(new InetSocketAddress("192.168.0.9",11211); MemcachedClient client = new MemcachedClient(new BinaryConnectionFactory();, servers);
En ese momento están listos para empezar a realizar operaciones en su cluster de servidores Memcached. Aunque Spy proporciona una interfaz muy sencilla y fácil de usar, no lo hace a expensas de la flexibilidad necesaria para ajustar el sistema. Bajo las cubiertas, Spy le proporciona muchas maneras de ajustar el sistema. En este blog, profundizaré en algunos de los mandos e interruptores que ofrece Spy. Espero ayudarle a comprender mejor cómo se comporta Spy y cómo puede ajustarlo para que se adapte mejor a sus necesidades cuando el comportamiento predeterminado no sea suficiente. Pero antes de entrar en detalles, quiero empezar con un tutorial de Spymemcached muy simple, sencillo y algo ingenuo y utilizarlo para ilustrar un punto bastante significativo.
Digamos que estoy evaluando Spymemcached por primera vez y quiero entender cómo se comporta mi sistema al hacer una serie de sets, gets y deletes. Para ello, puedo crear un programa sencillo que primero establezca una serie de entradas en la caché, luego recupere cada una de estas entradas y finalmente vaya y borre cada una de estas entradas. Puedo instrumentar el código para hacer esto de tal manera que rastree el tiempo total que toma hacer todos los sets, gets, y deletes. El código para esto podría ser algo como lo siguiente // realiza sets long start = System.nanoTime(); for (int i = 0; i < numberOfOps; i++) { client.set(KEY_PREFIX + i, 3600, PAY_LOAD).get(); } long end = System.nanoTime(); long setTime = end - start; // realiza gets start = System.nanoTime(); for (int = 0; i < numberOfOps; i++) { client.get(KEY_PREFIX + i); } end = System.nanoTime(); long getTime = end - start; // realiza borrados start = System.nanoTime(); for (int i = 0; i < numberOfOps; i++) { client.delete(KEY_PREFIX + i).get(); } end = System.nanoTime(); long delTime = end - start; System.out.println(numberOfOps + " Set: " + setTime / nanosPerSec + " Promedio: " + setTime / numberOfOps); System.out.println(numberOfOps + " Get: " + getTime / nanosPerSec + " Promedio: " + getTime / numberOfOps); System.out.println(numberOfOps + " Delete: " + delTime / nanosPerSec + " Promedio: " + delTime / numberOfOps);
Ahora bien, como he mencionado antes, se trata de un caso de uso bastante ingenuo y poco realista. No obstante, este burdo ejemplo tiene una importante lección que enseñarnos.
Veamos qué ocurre cuando ejecutamos esta prueba utilizando diferentes valores para el parámetro numberOfOps
. Si fijamos numberOfOps
a 10.000, nuestro rendimiento podría ser algo parecido a lo siguiente: 10000 Set: 3.008377 Promedio: 300837 10000 Obtener: 1.730886 Promedio: 173088 10000 Borrar: 1.172679 Media: 117267
Ahora, subamos un poco la apuesta y veamos qué ocurre cuando aumentamos el númeroDeOperaciones
por un factor de 10 y ajústalo a 100.000: 100000 Conjunto: 10.710224 Promedio: 107102 100000 Obtener: 9,992544 Media: 99925 100000 Borrar: 9,876984 Media: 98769
Lo interesante es que, aunque aumentamos la carga de trabajo, el tiempo medio que tardó en completarse cada operación se redujo significativamente. Vemos un descenso de casi 66% en la latencia de las operaciones de establecimiento, de casi 40% en la latencia de las operaciones de obtención y de 15% en la latencia de las operaciones de borrado. Veamos ahora qué ocurre cuando lanzamos 1.000.000 de operaciones a la prueba. 1000000 Conjunto: 106.15172 Promedio: 106151 1000000 Obtener: 101.086393 Media: 101086 1000000 Borrar: 99,747647 Media: 99747
Como podemos ver, el rendimiento a 1.000.000 de operaciones se parece mucho más al rendimiento a 100.000 operaciones que el rendimiento a 100.000 operaciones se parece al rendimiento que vemos a 10.000 operaciones.
Dejemos a un lado por el momento cuál es realmente la causa de esta discrepancia y centrémonos en la lección más importante. Cuando intentamos comprender el comportamiento y el rendimiento de un sistema, los atributos y los datos que utilizamos para probarlo son fundamentales. Debemos derivar empíricamente estos valores siempre que sea posible o basarlos en una restricción o suposición explícita. Si realmente hubiera estado utilizando la prueba anterior para entender el comportamiento de mi sistema basado en Spymemcached, simplemente utilizando el escenario de 10.000 operaciones no habría resultado en la comprensión de cuál es el rendimiento máximo del sistema. Si sólo hubiera ejecutado los escenarios de 100.000 o 1.000.000 de operaciones, habría pasado por alto el hecho de que el sistema parece requerir cierto tiempo de calentamiento. Pero al explorar un poco para ver cuáles son los valores interesantes de esta prueba sencilla e ingenua, hemos aprendido algunas lecciones significativas sobre nuestro sistema.
Traigo esto a colación porque con demasiada frecuencia veo discusiones sobre el rendimiento y el comportamiento de un sistema que utilizan lo que parecen ser valores y suposiciones arbitrarios. Y cuando los veo, me pregunto por qué el autor ha elegido un valor concreto. Si es un valor importante, debe explicarse su significado. Si se trata de un valor arbitrario, debemos desconfiar de los resultados y conclusiones que se presentan. (Nota, para que conste, los valores registrados anteriormente se lograron utilizando Spymemcached 2.5 ejecutándose contra memcached 1.4.4, con el cliente y el servidor ejecutándose en el mismo host).
Algunos >br ;<.
Por cierto, parece que este blog es vulnerable a cross site scripting.
<script>alert(\’!\’)</script>