Context
When you scale a PHP
application you have to consider several aspects of runtime environment such us:
- Bytecode caching (e.x. APC or Zend Optimizer Plus or eAccelerator), more;
- Reading project files from RAM instead of HDD;
- Caching and minify static content etc.
- One additional aspect is storing sessions.
By default, PHP stores sessions in files. There are also several approaches to speed up saving sessions, such us memcached, mapping save_path folder as ramdisc, etc.
In scaling approaches there is important that many worker nodes (with deployed application) runs the same code, round-robin selected or load-ballanced, but have the same space to store sessions, because there is no guarantee in distributes architecture, that next user’s request will be handled by the same node. This implies, that session memory have to be shared between nodes, unfortunately storing these data in local
RAM doesn’t meet this requirement.
Redis as PHP Session Handler
One of additional approach to storing sessions in fast-memory is Redis – key-value store. This could be configured as centralized or distributed database.
There is available a Redis session_handler for PHP. To use it:
- install Redis first as a service [more]
- copy/compile redis.so PHP extension [more information]
- register an extension in php.ini configuration file
- reconfigure session.save_handler in your php.ini configuration file, or set it directly on runtime by writing for e.x.:
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://localhost:6379');
Redis Session Handler in Symfony 2
I am using Symfony 2 framework. Unfortunately, 4th step don’t affects the application. You have to register own SessionHandler in config.yml file:
framework:
session:
handler_id: session_handler_redis
This configuration uses new SessionHandler registered ad session_handler_redis Symfony Service (more).
We have to write own SessionHandler in Symfony. I have found the Redis SessionHandler proposed by Andrej Hudec on GitHub (original code here). I have decided to use and improve existing implementation.
Declare new SessionHandler class somewhere in your project:
<?php
namespace Fokus\Webapp\CommonBundle\SessionHandler;
use \Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
/**
* NativeRedisSessionStorage.
*
* Driver for the redis session save hadlers provided by the redis PHP extension.
*
* @see https://github.com/nicolasff/phpredis
*
* @author Andrej Hudec <pulzarraider@gmail.com>
* @author Piotr Pelczar <me@athlan.pl>
*/
class NativeRedisSessionHandler extends NativeSessionHandler
{
/**
* Constructor.
*
* @param string $savePath Path of redis server.
*/
public function __construct($savePath = "")
{
if (!extension_loaded('redis')) {
throw new \RuntimeException('PHP does not have "redis" session module registered');
}
if ("" === $savePath) {
$savePath = ini_get('session.save_path');
}
if ("" === $savePath) {
$savePath = "tcp://localhost:6379"; // guess path
}
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', $savePath);
}
}
Now, add the entry that declares
the class as a Symfony Service in services.yml file:
services:
session_handler_redis:
class: Fokus\Webapp\CommonBundle\SessionHandler\NativeRedisSessionHandler
arguments: ["%session_handler_redis_save_path%"]
I have improved Andrzej’s code that you can configure the session handler calling it’s constructor and pass the Redis connection string just in services in Symfony, without touching ini_set or php.ini settings. As you see, the %session_handler_redis_save_path%
parameter has been used.
Now, declare the value of parameter in parameters.yml file:
session_handler_redis_save_path: tcp://localhost:6379
That’s all!
Just refresh your page, use the session such us in after loging and check out if it works. Type in command line:
redis-cli
and show all keys stored by PHP Session Handler. Keys begins with string PHPREDIS_SESSION:
.
KEYS PHPREDIS_SESSION*
Example output:
redis 127.0.0.1:6379> KEYS PHPREDIS_SESSION* 1) "PHPREDIS_SESSION:s4uvor0u5dcsq5ncgulqiuef14" 2) "PHPREDIS_SESSION:dcu54je80e6feo5rjqvqpv60h7"
Hope it helped!