2013년 8월 9일 금요일

[PHP] Session 파일로 관리하지 마세요.




Windows7+Apache에서 CentOS+Nginx로 갈아탔는데도 불구하고

PHP를 사용할 때 갑자기 매우 느려지는 현상이 있었습니다.

PHP-FPM 도 사용해보고 , Nginx가 Dynamic한 파일 처리에 적합하지 않다고 하여

Reverse Proxy로 Apahce를 연결해 보았지만 여전히 느린 현상이 없어지지 않았습니다.

그래서 지인과 이야기를 하던 도중 중대한 사실을 하나 알게되었습니다.

세션을 파일로 관리했던 것!

그러고보니 아무리 찾아봐도 느릴 이유가 없는데..

시스템 IO는 아직도 대부분의 시스템에서 가장 중요한 작업이고, 

강력한 Priority를 갖기 때문일 것이라 생각 했습니다.

그래서 그때부터 Redis와 PHP를 연결한 후 Redis-server 에서 세션을 다루어 보았습니다.

PHP에서 Redis를 사용하기 위해서는 , PREDIS 를 먼저 설치 해야합니다.

pear 가 어디에 있는지 find 명령어로 먼저 찾아 내고,

/pear의 위치/pear channel-discover pear.nrk.io 이후

/pear의 위치/pear install nrk/Predis 로 설치하면 끝.

저는 이 파일을 아파치에 옮겨 주었구요

php.ini 를 find해서 

extension=redis.so 

하면 php에서 redis를 사용이 가능합니다.

사용법은 
require('./redis-session.php');
RedisSession::start();

이고, 다음부터는 일반 session 사용하듯 사용하시면 됩니다.
redis-session.php 파일은 아래와 같습니다.



redis-session.php


if(!class_exists('\Predis\Client')){
  chdir(dirname(__FILE__)); 
  require_once('./Predis/Autoloader.php');
/*
./Predis/Autoloader.php 이 부분은 
redis-session.php 위치로 부터의 상대 경로입니다.
*/
  Predis\Autoloader::register();
}

function json_decode_array($d){
  return json_decode($d, true);
}

function redis_session_id_mutator($id){
  return $id;
}

class RedisSession{
  private $serializer;
  private $unserializer;
  private $unpackItems;
  private $id_mutator;

  static function start($redis_conf = array(), $unpackItems = array()){
    if(!defined('REDIS_SESSION_PREFIX'))
      define('REDIS_SESSION_PREFIX', 'session:php:');
    if(!defined('REDIS_SESSION_SERIALIZER'))
      define('REDIS_SESSION_SERIALIZER', 'json_encode');
    if(!defined('REDIS_SESSION_UNSERIALIZER'))
      define('REDIS_SESSION_UNSERIALIZER', 'json_decode_array');
    if(!defined('REDIS_SESSION_ID_MUTATOR'))
      define('REDIS_SESSION_ID_MUTATOR', 'redis_session_id_mutator');
    $obj = new self($redis_conf, $unpackItems);
    session_set_save_handler(
      array($obj, "open"),
      array($obj, "close"),
      array($obj, "read"),
      array($obj, "write"),
      array($obj, "destroy"),
      array($obj, "gc"));
    session_start(); 
    return $obj;
  }

  function __construct($redis_conf, $unpackItems){
    $this->serializer = function_exists(REDIS_SESSION_SERIALIZER) ? REDIS_SESSION_SERIALIZER : 'json_encode';
    $this->unserializer = function_exists(REDIS_SESSION_UNSERIALIZER) ? REDIS_SESSION_UNSERIALIZER : 'json_decode_array';
    $this->id_mutator = function_exists(REDIS_SESSION_ID_MUTATOR) ? REDIS_SESSION_ID_MUTATOR : 'redis_session_id_mutator';
    $this->unpackItems = $unpackItems;

    $this->redis = new \Predis\Client($redis_conf);
  }

  function serializer(){
    return call_user_func_array($this->serializer, func_get_args());
  }

  function unserializer(){
    return call_user_func_array($this->unserializer, func_get_args());
  }

  function id_mutator(){
    return call_user_func_array($this->id_mutator, func_get_args());
  }

  function read($id) {
    $d = $this->unserializer($this->redis->get(REDIS_SESSION_PREFIX . $this->id_mutator($id)));
    // Revive $_SESSION from our array
    $_SESSION = $d;
  }


  function write($id, $data) {
    
    $data = $_SESSION;
    $ttl = ini_get("session.gc_maxlifetime");
    $unpackItems = $this->unpackItems;
    $serializer = $this->serializer;
    $id_mutator = $this->id_mutator;

    $this->redis->pipeline(function ($r) use (&$id, &$data, &$ttl, &$unpackItems, &$serializer, &$id_mutator) {
      $r->setex(REDIS_SESSION_PREFIX . $id_mutator($id), $ttl, $serializer($data));

    });
  }


  function destroy($id) {
    $this->redis->del(REDIS_SESSION_PREFIX . $this->id_mutator($id));

  }

  function open($path, $name) {}
    function close() {}
    function gc($age) {}
}

register_shutdown_function('session_write_close');

?>


빨라지는 마법을 경험했습니다.

그래도... 앞으로는 PHP사용을 자제해야 겠습니다.