Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Integrando Redis en aplicaciones Symfony2

Integrando Redis en aplicaciones Symfony2

deSymfony – Madrid 22 de junio de 2013

Sus múltiples casos de usos y su excepcional rendimiento hacen que Redis sea hoy una pieza clave en la arquitectura de aplicaciones altamente dinámicas.

En la charla se expone de forma práctica cómo puede integrarse Redis en una aplicación Symfoy y cómo pueden implementarse varias de las características de las aplicaciones usando Redis, como por ejemplo: Session storage, Monolog logging handlers, Doctrine caching, SwiftMailer spooling, Profiler storage, Data Collector for Symfony2 Profiler.

Además de estos casos de uso generales, se comentan otros casos de usos específicos de aplicaciones que por su naturaleza no pueden beneficiarse de una capa de cache y se requiere por tanto una herramienta eficiente y escalable para resolver ciertos tipos de problemas.

Ronny López

June 22, 2013
Tweet

More Decks by Ronny López

Other Decks in Programming

Transcript

  1. ¿Quién soy? • Backend Core Tech Lead @ Socialpoint Arquitectura

    y desarrollo de aplicaciones que eficientemente respondan peticiones de millones de usuarios cada día • Redis Fan • Made in Cuba • Poco original eligiendo memes • @ronnylt Monday, June 24, 13
  2. Tenemos cientos, miles, de usuarios concurrentes y necesitamos una solución

    escalable para almacenar las sesiones Monday, June 24, 13
  3. Agenda • Redis y sus características • Entendiendo Redis •

    Conectando desde PHP • Integrando Redis en Symfony2 • Casos de uso Monday, June 24, 13
  4. ¿Qué es Redis? • REmote DIctionary Server • Creado en

    2009 por Salvatore Sanfilipo (@antirez) • Open source Monday, June 24, 13
  5. Data Structure Server • Cadenas • Listas • Conjuntos •

    Conjuntos ordenados • Hashes (hash maps) Monday, June 24, 13
  6. In-memory Database • Datos deben caber en memoria • Persistencia

    configurable • “Memory is the new disc, disc is the new tape” Monday, June 24, 13
  7. Advanced key-value store database • Persistencia (snapshot, append-only file) •

    Replicación (master/slave) • Transacciones • Pipelining • Publisher/Subscriber (pub/sub) • Lua scripting Monday, June 24, 13
  8. Ideal para • Analíticas real-time • Tracking • Caching server

    (memcached on steroid) • Colas de trabajo • Escritura/Lectura intensiva (sesiones) Monday, June 24, 13
  9. Claves y valores • Los datos (values) son refereciados a

    través de claves (keys) • Los datos pueden ser recuperados solo si conocemos el nombre de la clave Monday, June 24, 13
  10. Claves (keys) • Únicas dentro de la BD • Binary

    safe string • Claves muy grandes pueden impactar en el rendimimiento • Claves muy pequeñas no aportan mucho (u:123:n vs user:123:name) Monday, June 24, 13
  11. No es un RMDBS • No hay consultas (queries) •

    No hay índices • No hay esquemas Monday, June 24, 13
  12. Comandos • Lenguaje de comandos fácil de usar y de

    aprender • Los comandos (en su mayoría) son aplicables a un tipo de datos específico Monday, June 24, 13
  13. Cadenas • Tipo de dato simple (cualquier cadena binary-safe) •

    Tamaño máximo de 512 MB key string GET, SET, STRLEN, APPEND, GETRANGE, SETRANGE http://redis.io/commands#string Monday, June 24, 13
  14. Casos de uso cadenas • Almacenamiento de cualquier dato (serializado):

    GET, SET • Vector de acceso aleatorio con GETRANGE, SETRANGE • Mapa de bits usando GETBIT, SETBIT, BITCOUNT Monday, June 24, 13
  15. Casos de uso cadenas • Contadores atómicos con: INCR, DECR

    INCRBY, DECRBY INCRFLOATBY INCR dowloads:item:123 => 450 INCR dowloads:item:123 => 451 Monday, June 24, 13
  16. Listas • Listado de cadenas donde el orden es importante

    • Operaciones de inserción por la izquierda y por la derecha o por posición • Máxima longitud de 2^32 -1 (+4 billones) key s2 s1 s3 ... http://redis.io/commands#list Monday, June 24, 13
  17. Casos de uso Listas • Representación de colas (insertando por

    la derecha, leyendo por la izquierda) RPUSH, LPOP • Representación de pilas (insertando y leyendo por la izquierda) LPUSH, LPOP • Comandos blocking BLPOP, BRPOP, BRPOPLPUSH Monday, June 24, 13
  18. Conjuntos • Colección de elementos únicos donde el orden no

    importa • Operaciones típicas de conjuntos sobre los datos key blue green red black Monday, June 24, 13
  19. Casos de uso Conjuntos • Representación de relaciones • Tracking

    de sucesos únicos • Cualquier problema donde por su naturaleza se realicen operaciones sobre conjuntos Monday, June 24, 13
  20. Conjuntos Ordenados • Conjuntos de datos, pero ordenados por un

    score • Elementos únicos dentro del conjunto, cada uno con un score asignado key blue – 520 green – 890 red – 303 black – 680 Monday, June 24, 13
  21. Conjuntos Ordenados • Operaciones de conjuntos aplicables • Operaciones de

    acceso por score y por rango en tiempo constante y predecible http://redis.io/commands#sorted_set Monday, June 24, 13
  22. Casos de uso Conjuntos Ordenados • Leaderboards • Rankings •

    Tracking basado en tiempo Monday, June 24, 13
  23. Hashes • Múltiples campo => valor en una misma clave

    • Hasta un máximo de 2^32 -1 pares campo => valor key field1 value1 field2 value2 field3 value3 http://redis.io/commands#hash Monday, June 24, 13
  24. Hashes • Puede verse como un arreglo asociativo en PHP:

    clave => [ campo1 => valor1, campo2 => valor2, campo3 => valor3 ] • Operaciones sobre campos individuales Monday, June 24, 13
  25. Casos de uso Hashes • Almacenamiento de objetos compuestos por

    varios campos • Mappings Monday, June 24, 13
  26. Resumiendo... • Tenemos la oportunidad de usar la estructura de

    datos adecuada para cada tipo de problema • Tendremos tiempos de ejecución constantes y predecibles, sin importar el tamaño de los conjuntos de datos (dataset) Monday, June 24, 13
  27. Clientes para PHP • https://github.com/nrk/predis • https://github.com/nicolasff/phpredis Clientes disponibles para

    la mayoría de los lenguajes de programación (http://redis.io/clients) Monday, June 24, 13
  28. Predis "require": { "predis/predis": "~0.8.3" }, • Escrito en PHP

    • Maduro y activamente mantenido • Extensible • Feature-complete (pipelines, client side sharding, server profiles, master/slave config, etc.) Monday, June 24, 13
  29. phpredis • Escrito en C como una extensión PHP •

    Listo para producción • Extremadamente rápido • No backward compatible con anteriores versions de Redis Monday, June 24, 13
  30. ¿Cuál usar? • Depende... • Predis cubre la mayoría de

    las necesidades, fácil de instalar con composer, y nos ofrece un rendimiento aceptable • phpredis si necesitas un rendimiento excepcional Monday, June 24, 13
  31. SncRedisBundle • Integra Predis y phpredis en Symfony2 • Soporte

    para: • Session storage • Monolog logging handler • SwiftMailer Spooling • Doctrine caching Monday, June 24, 13
  32. Definiendo clientes snc_redis: clients: default: type: predis alias: default dsn:

    redis://redis.example.com session: type: predis alias: session dsn: - redis://rses1.example.com - redis://rses2.example.com config.yml / redis.yml Monday, June 24, 13
  33. Configuración avanzada snc_redis: clients: cache: type: predis alias: cache dsn:

    - redis://cache1.example.com - redis://cache2.example.com options: profile: 2.6 connection_timeout: 10 readwrite_timeout: 30 config.yml / redis.yml Monday, June 24, 13
  34. Obteniendo el cliente a través del container $redis = $container->get('snc_redis.default');

    $key = 'downloads:' . $pid . ':count' $downloads = $redis->incr($key); php app/console container:debug | grep snc_redis Monday, June 24, 13
  35. Clientes registrados como servicios php app/console container:debug snc_redis.default Information for

    service snc_redis.default Service Id snc_redis.default Class Predis\Client Tags - Scope container Public yes Synthetic no Required File - Monday, June 24, 13
  36. Inyectando Redis como dependencia namespace Acme\DemoBundle\Service; use Snc\RedisBundle\Client\Predis as Redis;

    class DownloadCounter { protected $redis; public function __construct(Redis $redis) { $this->redis = $redis; } public function count($itemId) { return $this->redis->incr('downloads:' . $itemId . ':count'); } } Monday, June 24, 13
  37. Sesiones • Difícil de escalar con la configuración por defecto

    • Por naturaleza no cacheable (read-change- write back) • Se necesita mantener estado consistente Monday, June 24, 13
  38. • Difícil de escalar con mucho tráfico • Escrituras en

    cada request • Replication lag Monday, June 24, 13
  39. • In-memory sessions • Tiempo de acceso constante y predecible

    • Escala horizontalmente Monday, June 24, 13
  40. Sesiones en Redis • Sesiones distribuídas (ej. detrás de un

    balanceador sin sticky sessions) • Excepcional rendimiento de escritura/lectura • Tiempo de acceso constante y predecible • Escalable horizontalmente (client-side sharding) Monday, June 24, 13
  41. Session handlers • A través de un session handler implementado

    en PHP, conectando a través de un cliente Redis • A través de un session handler implementado en una extensión de PHP (phpredis) Monday, June 24, 13
  42. Usando Predis snc_redis: clients: session_cluster: type: predis alias: session dsn:

    - redis://sess000.example.net - redis://sess001.example.net - redis://sess002.example.net session: client: session_cluster ttl: 1200 prefix: appsession config.yml Monday, June 24, 13
  43. Usando phpredis framework: session: # Default storage service storage_id: "session.storage.native"

    # No handler service, use default handler_id: ~ # The name for the session cookie name: "appsesid" config.yml Monday, June 24, 13
  44. php.ini usando phpredis session.save_handler = redis session.save_path = " tcp://s000.example.net:6379?weight=1,

    tcp://s001.example.net:6379?weight=2, tcp://s002.example.net:6379?weight=2 " Monday, June 24, 13
  45. redis 127.0.0.1:6379> MONITOR OK "GET" "appsession:9jmmp11dvh3b4f1bp9trfuqlj3" "SETEX" "appsession:9jmmp11dvh3b4f1bp9trfuqlj3" "1440" "_sf2_attributes|a:1:{s:5:\"visit\";i:1371678033;}

    _sf2_flashes|a:0:{}_sf2_meta|a:3:{s:1:\"u\";i: 1371678033;s:1:\"c\";i:1371678023;s:1:\"l\";s:1:\"0\";}" session name session id (cookie) ttl (expire time) session data Monday, June 24, 13
  46. Monolog logging • Los mensajes de logs son almacenados en

    una lista • Ideal cuando se necesita un broker que reciba los logs que serán posteriormente enviados a un agregador (ej. logstash) Monday, June 24, 13
  47. Monolog config snc_redis: clients: monolog: type: predis alias: monolog dsn:

    redis://localhost/1 logging: false monolog: client: monolog key: monolog monolog: handlers: main: type: service id: monolog.handler.redis level: debug Monday, June 24, 13
  48. SwiftMailer Spooling • Los mensajes no se envian directamente, sino

    que se mantienen en un “spool” y son enviados por un proceso en background • Usando redis como “spool”, los mensajes son mantenidos en una lista hasta que son enviados Monday, June 24, 13
  49. Mailer spooling snc_redis: clients: emails: type: predis alias: emails dsn:

    redis://emails-spool-00.example.com logging: false swiftmailer: client: emails key: swiftmailer config.yml Monday, June 24, 13
  50. Router dinámicos • Necesitamos convertir URLs amigables a rutas internas

    de Symfony2: Idioma Ruta interna Ruta “amigable” es /sport/123 /futbol en /sport/123 /football Monday, June 24, 13
  51. Router dinámicos Idioma Ruta “amigable” _controller es /futbol Bundle:SportController:sportPageAction, array(‘sport’

    => 123) es /madrid Bundle:CityController:cityPageAction array(‘city’ => 456) Monday, June 24, 13
  52. Desventajas • Difícil de cambiar la configuración de routing una

    vez creadas • Es requerido tener copia de la base de datos de routings en los ambientes de desarrollo para que la aplicación funcione Monday, June 24, 13
  53. Solución alternativa • Crear un mapping entre rutas amigables y

    rutas internas • Subscribirse a KernelEvents::REQUEST y antes que nada, cambiar pathInfo en el objeto Request Monday, June 24, 13
  54. Usando Redis hashes /futbol /sport/123 /baloncesto /sport/456 /tenis /sport/789 routes:es

    /sport/123 /futbol /sport/456 /baloncesto /sport/789 /tenis alias:es Monday, June 24, 13
  55. ¿Quién está online? • Mantenemos un conjunto con los usuarios

    que han estado online por cada minuto • En cada request, agregamos al usuario al conjunto de usuarios online del minuto actual • Obtenemos los usuarios que han estado online de la unión de los 5 últimos conjuntos Monday, June 24, 13
  56. Amigos online • Mantenemos un conjunto con los amigos de

    cada usuario • Obtenemos los amigos que están online, de la intersección del conjunto de usuarios online con el conjunto de amigos de un usuario Monday, June 24, 13
  57. class OnlineUsersManager { protected $redis; public function __construct(Redis $redis) {

    $this->redis = $redis; } public function trackUser($userId) { $timestamp = time(); $minute = date('i', $timestamp); $key = 'online_users:' . $minute; // Add the user to the set of online users in the current minute. $this->redis->sadd($key, $userId); // Expire in 10 minutes. $this->redis->expire(60 * 10); } Monday, June 24, 13
  58. Leaderboards • Caso de uso típico el cual es fácil

    de implementar con Redis y díficil de implementar de forma eficiente en otro sistema • Los conjuntos ordenados son las estructuras de datos perfectas para su implementación Monday, June 24, 13
  59. class RankingManager { protected $redis; public function __construct(Redis $redis) {

    $this->redis = $redis; } public function onCombatFinished(Combat $combat) { $winner = $combat->getAttacker(); $score = $combat->getScoreResult(); $this->redis->zincr('ranking', $score, $winner->getId()); } public function getTopScores($limit) { return $this->redis->zrevrange('ranking', 0, $limit); } } Monday, June 24, 13
  60. Extra Tips • Redis es single-threaded, todos los comandos son

    atómicos • Transacciones pueden usarse para ejecutar múltiples comandos de forma atómica • Lua scripts se ejecutan de forma atómica también • Monday, June 24, 13
  61. Performance Tips • Usando client-side sharding puede escalarse horizontalmente ganando

    en capacidad y rendimiento • Ejecutar múltiples comandos a través de pipelines Monday, June 24, 13
  62. NO usar Redis • Cuando el conjunto de datos (dataset)

    no cabe en memoria • Datos de naturaleza relacional • Cuando no se conoce de antemano como van a consultarse los datos Monday, June 24, 13
  63. Cuándo usar Redis • Redis para datos temporales, altamente dinámicos

    y estructuras de datos complejas • Datos de naturaleza no-relacional • Ideal para aplicaciones que son write-heavy • Datos que naturalmente se ajustan a una estructura de Redis Monday, June 24, 13
  64. Inegrando Redis en un stack PHP/Symfony2 • No necesariamente como

    la DB principal • Resolviendo problemas que son difíciles de resolver en un sistema relacional • Beneficiandonos de las características de Redis de forma incremental • Usando la herramienta adecuada para cada tarea Monday, June 24, 13