Infrastructure scalable et Symfony2

Si votre projet web prend de l’ampleur, une architecture orientée autour d’un simple serveur dédié, comme on le voit souvant, deviendra probablement insuffisante pour faire tourner votre application Symfony2.
Absorber des pics de charges, réduire les risques liés à la panne, … de nombreuses raisons peuvent vous ammener à adopter une infrastructure scalable composée de plusieurs serveurs.

Tour d’horizon des principales problématiques inhérentes à la mise en place de ce type d’architecture pour son projet Symfony2, d’un point de vue développeur.

INTRODUCTION

On rencontre principalement deux infrastructures, qui peuvent être des serveurs physiques, ou des machines virtuelles:

  • 1 serveur qui sert de LAMP
  • ou: 1 serveur web + 1 serveur sql

La plus part des projets PHP sont hébergés sur ce type d’infrastructure.
Facile à mettre en place, mais montrera rapidement ses limites si votre projet prend de l’ampleur, ou s’il est gourmand en ressources.

Vous ne pourrez pas disposer de plus de puissante que ce que vous fournit votre serveur, a moins de le transférer sur un serveur plus puissant..mais qui montrera tôt ou tard ses limites lui aussi: votre projet ne sera pas scalable.

 

INFRASTRUCTURE SCALABLE

En informatique matérielle et logicielle et en télécommunications, la scalability ou scalabilité désigne la capacité d‘un produit à s‘adapter à un changement d‘ordre de grandeur de la demande (montée en charge), en particulier sa capacité à maintenir ses fonctionnalités et ses performances en cas de forte demande. Wikipedia

Les infrastructures scalables pour les projets PHP sont généralement composées à minima de plusieurs :

  • load balancer (web/mysql)
  • serveur mysql
  • serveur web

Votre applicatif Symfony2 devra être capable de fonctionner sur plusieurs serveurs simultanéments.
Pour que cela soit possible, vous allez devoir résoudre quelques nouvelles problématiques dans votre projet Symfony2.

 

1/ LES SESSIONS PHP

Le fonctionnement des sessions PHP est très simple, le moteur crée un fichier texte dans lequel il stock toutes les informations de session.

Par défaut, ce fichier sera dans le répertoire /tmp de votre système, sauf si vous utilisez une version rescente de Symfony2, qui redéfini par défaut le chemin où sont stockés les sessions, dans le répertoire app/cache/ de votre projet.

Sur une architecture composée de plusieurs serveurs, toutes les sessions doivent être accessibles depuis n’importe quel serveur. Le comportement par défaut de PHP ne nous ira pas, les sessions étant accessibles uniquement sur le serveur qui les a générés.

Il existe plusieurs solutions pour résoudre ce problème dont PDOSession ou l’utilisation de Redis.

Utilisez votre serveur SQL avec PDOSession

Le framework Symfony2 contient plusieurs storages pour les sessions que vous pouvez utiliser par configuration.

Le storage PDOSession est un moyen simple pour stocker les sessions dans votre base de données. Il a cependant un inconvénient, il recalcule en direct la date d’expiration de chaque session, pouvant provoquer quelques problèmes sur un site à fort traffic.

Une solution rapide à mettre en place et qui peut se révéler utile, bien que nous recommandons l’utilisation de Redis.

Pour utiliser PDOSession, consultez le chapitre dédié sur le Cookbook de Symfony: http://symfony.com/doc/current/cookbook/doctrine/pdo_session_storage.html

 

Utilisez Redis pour stocker vos sessions

Redis est une base de donnée clé=valeur qui stocke les informations en mémoire.
Nous allons voir comment stocker les informations de session dans une instance Redis, afin d’y accéder depuis n’importe quel serveur de votre infrastructure.

Il n’y a pas de storage Redis en natif dans Symfony2, mais rassurez vous..il y a un bundle pour ca !

Installation de SncRedisBundle

Il existe deux versions du bundle, l’une utilisant une extension PHP qu’il vous faudra installer sur votre système d’exploitation, l’autre qui utilise la librairie Phredis (qui ne nécessite pas l’installation de l’extension PHP).
Nous obterons ici pour la version Phredis.

L’installation du bundle se fait avec Composer :

composer require snc/redis-bundle predis/predis
# app/AppKernel.php
    $bundles = array(
        // ...
        new Snc\RedisBundle\SncRedisBundle(),
        // ...
    );

Configuration de SncRedisBundle

Pour stocker vos sessions dans Redis, il vous suffit d’ajouter la configuration ci-dessous:

# app/config/config.yml
snc_redis:
    clients:
        default:
            type: predis
            alias: default
            dsn: redis://localhost
    session:
        client: default
        prefix: session

Le bundle permet de faire de nombreuses choses, consultez le README: https://github.com/snc/SncRedisBundle/blob/master/Resources/doc/index.md

 

2/ LES FICHIERS PARTAGES

Passons maintenant à la gestion des fichiers partagés dans votre application.
La même problématique que pour les sessions PHP va se poser pour tous les fichiers qui nécessitent d’être partagés entre vos serveurs.

Par exemple, les fichiers uploadés par les utilisateurs devront être accessibles depuis tous les serveurs de l’infrastructure et non uniquement sur le serveur où le fichier a été uploadé.

Pour partager vos fichiers, il existe plusieurs solutions plus ou moins honéreuses.
Nous allons voir deux possibilités:

  • le partage réseau via un serveur de fichier
  • l’utilisation d’un service de Cloud

 

Serveur de fichier

La mise en place de serveur de fichier (filer) dans votre infrastructure peut se révéler utile pour partager vos fichiers via le réseau.

Cette solution est transparente pour votre applicatif: on créé un raccourci vers un répertoire réseau, votre projet Symfony2 écrira dedans comme s’il écrivait dans un répertoire local.

Ce raccourci réseau devra être présent sur tous les serveurs web pour qu’ils puissent accéder aux fichiers partagés stockés sur le serveur de fichiers.

 

Amazon Cloud S3

Une autre possibilité est d’utiliser un storage Cloud pour le stockage de vos fichiers.

Voici un exemple d’implémentation du SDK d’Amazon S3.
Tout d’abord, nous devons ajouter la librairie dans notre projet Symfony2 :

composer require aws/aws-sdk-php

Le SDK d’Amazon est désormais présent dans votre projet.
Nous allons déclarer un nouveau client :

# app/config/parameters.yml.dist
amazon_key:         YOUR_AWS_KEY
amazon_secret_key:  YOUR_AWS_SECRET
amazon_bucket_name: YOUR_BUCKET_NAME
amazon_bucket_url:  YOUR_BUCKET_BASE_URL
amazon_region:      YOUR_REGION
# app/config/config.yml
aws:
    version:    latest
    region:     %amazon_region%
    credentials:
        key:    %amazon_key%
        secret: %amazon_secret_key%

Nous avons désormais un service app.amazon_s3_client qui nous permet d’intéragir avec Amazon S3.

Passons désormais à la création d’un manager, nous permettant d’envoyer un fichier vers le Cloud :

# src/AppBundle/Resources/config/services.yml
  app.file_storage:
    class: AppBundle\Manager\FileStorageManager
    arguments:
         - "@aws.s3"
         - %amazon_bucket_name%
         - %amazon_bucket_url%

Il ne nous reste désormais plus qu’à appeler notre manager, dans une tâche par exemple :

Pour plus de détail, regardez la doc du SDK PHP: http://docs.aws.amazon.com/aws-sdk-php/v3/guide/service/s3-multipart-upload.html

 

3/ LA BASE DE DONNEES

Une infrastructure scalable implique généralement une réplication de la base de données sur plusieurs serveurs.

Il existe plusieurs types de réplications selon les SGBD, les deux principales sont :

  • maitre <-> maitre
  • maître <-> esclave

La réplication maitre <-> maitre ne nécessite aucune modification, tandis que la deuxième va vous obliger à adapter votre projet Symfony2.

 

Travailer avec une réplication maître <-> esclave

La configuration maître <-> esclave nécessite de séparer les requêtes READ et les requêtes WRITE.

Les requêtes INSERT/UPDATE/DELETE devront être envoyées au serveur maître, tandis que les requêtes SELECT devront être envoyées aux serveurs esclaves.

Symfony et Doctrine permettent de faire facilement cela grâce à quelques paramètres de configuration :

# app/config/config.yml
doctrine:
    dbal:
        default_connection:   default
        connections:
            dev:
                host:     localhost
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
            default:
                host:      master1.myhost.fr
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                slaves:
                    slave1:
                        host:     slave1.myhost.fr
                        port:     "%database_port%"
                        dbname:   "%database_name%"
                        user:     "%database_user%"
                        password: "%database_password%"
                    slave2:
                        host:      slave2.myhost.fr
                        port:     "%database_port%"
                        dbname:   "%database_name%"
                        user:     "%database_user%"
                        password: "%database_password%"

Vous trouverez sur le site Symfony le détail de la configuration possible pour Doctrine: http://symfony.com/doc/current/reference/configuration/doctrine.html

 

4/ LES CRONS

Si vous ne disposez pas d’un système de CRON distribué et que vous souhaitez les éxécuter sur vos serveurs webs, vous rencontrerez la problématique suivante dans une architecture distribuée: l’éxécution de vos crontasks se fera simultanéments autant de fois qu’il y a de serveurs webs. Cela peut vite devenir problématique, imaginez un email envoyé plusieurs fois…

Une solution rapide à mettre en place grâce à Redis est d’effectuer un lock qui préviendra toute exécution simultanée de vos tâches.

Voici un exemple d’implémentation, pour utiliser le lock redis dans l’une de vos commandes, vous n’avez qu’à utiliser le Trait RedisLockCommand et appeler la méthode checkIfLocked() dans votre tâche.

ALLEZ PLUS LOIN..

Partagez cet article