把session保存到redis中,session-redis

web集羣,跨域,跨服等,需要共享session。

想要共享 SESSION 數據,那就必須實現兩個目標:

          一個是各個服務器對同一個客戶端產生的 SESSION ID 必須相同,並且可通過同一個 COOKIE 進行傳遞,也就是說各個服務器必須可以讀取同一個名爲 PHPSESSID 的 COOKIE;

         另一個是 SESSION 數據的存儲方式/位置必須保證各個服務器都能夠訪問到。

        簡單地說就是多服務器共享客戶端的 SESSION ID,同時還必須共享服務器端的 SESSION 數據。


        第一個目標的實現其實很簡單,只需要對 COOKIE 的域(domain)進行特殊地設置即可,默認情況下,COOKIE 的域是當前服務器的域名/IP 地址,而域不同的話,各個服務器所設置的 COOKIE 是不能相互訪問的,如 www.aaa.com 的服務器是不能讀寫 www.bbb.com 服務器設置的 COOKIE 的。

       第二個目標的實現可以使用文件共享方式,如 NFS 方式,但設置、操作上有些複雜。

class MyRedis
{
    private $host;
    private $port;
    private $password;
    private $handle;

    /**
     *
     * @param string $host
     * @param int $port
     * @param string $password
     */
    public function  __construct( $host = '127.0.0.1', $port = 6379, $password = NULL )
    {
        $this->host = $host;
        $this->port = $port;
        $this->password = $password;
    }

    /**
     * Ping server
     *
     * @param int $server_index Index of the server
     */
    public function ping( $server_index )
    {
        return $this->execute_command($this->get_connection( $server_index ), 'PING');
    }

    # === Scalar operations ===

    /**
     * @param string $key
     * @param mixed $value
     */
    public function set( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("SET {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $this->get_error($response);
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function get( $key )
    {
        $response = $this->execute_command( "GET {$key}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        $length = (int)substr($response, 1);
        if ( $length > 0 )
        {
            $value = $this->get_response();
            return $this->unpack_value($value);
        }
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function set_expire($key, $lifetime, $value)
    {
        if(!$this->set($key, $value)){
            return $this->execute_command( "EXPIRE {$key} {$lifetime}" );
        }
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function setex($key, $lifetime, $value)
    {
        $value = $this->pack_value($value);
        $cmd = array("SETEX {$key} {$lifetime} " . strlen($value), $value);
        $response = $this->execute_command( $cmd );
        return $this->get_error($response);
    }

    /**
     * @param string $key
     */
    public function del( $key )
    {
        return $this->execute_command( "DEL {$key}" );
    }

    /**
     * @param string $key
     * @return boolean
     */
    public function exists( $key )
    {
        return $this->execute_command( "EXISTS {$key}" ) == ':1';
    }

    /**
     * @param string $index
     * @return boolean
     */
    public function select( $index )
    {
        return $this->execute_command( "SELECT {$index}" );
    }

    /**
     *
     * @param string $key
     * @param int $by
     */
    public function inc( $key, $by = 1 )
    {
        $response = $this->execute_command( "INCRBY {$key} {$by}" );
        return substr($response, 1);
    }

    /**
     *
     * @param string $key
     * @param int $by
     */
    public function dec( $key, $by = 1 )
    {
        $response = $this->execute_command( "DECRBY {$key} {$by}" );
        return substr($response, 1);
    }

    # === List operations ===

    public function prepend( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("LPUSH {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $this->get_error($response);
    }

    public function append( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("RPUSH {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $this->get_error($response);
    }

    public function get_list($key, $limit, $offset = 0)
    {
        $limit--;
        $start = $offset;
        $end = $start + $limit;

        $response = $this->execute_command( "LRANGE {$key} {$start} {$end}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        $count = (int)substr($response, 1);
        $list = array();
        for ( $i = 0; $i < $count; $i++ )
        {
            $length = substr($this->get_response(), 1);
            $value = $this->get_response();
            $list[] = $this->unpack_value($value);
        }

        return $list;
    }

    public function get_filtered_list($key, $filters, $limit = 0, $offset = 0)
    {
        $start = 0;
        $end = $this->get_list_length($key);

        $response = $this->execute_command( "LRANGE {$key} {$start} {$end}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        $limit = !$limit ? $end : $limit + $offset;

        $list = array();
        for ( $i = 0; $i < $end; $i++ )
        {
            $length = substr($this->get_response(), 1);
            $value = $this->get_response();
            $value = $this->unpack_value( $value );
            if ( ( $filters == array_intersect($value, $filters) ) && ( ++$added <= $limit ) )
            {
                $list[] = $value;
            }
        }

        $list = array_slice($list, $offset);

        return $list;
    }

    public function get_list_length($key)
    {
        $response = $this->execute_command( "LLEN {$key}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        return (int)substr($response, 1);
    }

    public function remove_from_list($key, $value, $count = 0)
    {
        $value = $this->pack_value($value);
        $response = $this->execute_command( array("LREM {$key} {$count} " . strlen($value), $value) );

        if ( $this->get_error($response) )
        {
            return;
        }

        return (int)substr($response, 1);
    }

    public function remove_by_filter($key, $filters)
    {
        $list = $this->get_filtered_list($key, $filters);

        foreach ( $list as $item )
        {
            $this->remove_from_list($key, $item);
        }
    }

    public function truncate_list($key, $limit, $offset = 0)
    {
        $limit--;
        $start = $offset;
        $end = $start + $limit;

        $response = $this->execute_command( "LTRIM {$key} {$start} {$end}" );

        if ( $this->get_error($response) )
        {
            return;
        }

        return true;
    }

    # === Set operations ===

    public function add_member( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("SADD {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $response == ':1';
    }

    public function remove_member( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("SREM {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $response == ':1';
    }

    public function is_member( $key, $value )
    {
        $value = $this->pack_value($value);
        $cmd = array("SISMEMBER {$key} " . strlen($value), $value);

        $response = $this->execute_command( $cmd );
        return $response == ':1';
    }

    public function get_members($key)
    {
        $response = $this->execute_command( "SMEMBERS {$key}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        $count = (int)substr($response, 1);
        $list = array();
        for ( $i = 0; $i < $count; $i++ )
        {
            $length = substr($this->get_response(), 1);
            $value = $this->get_response();
            $list[] = $this->unpack_value($value);
        }

        return $list;
    }

    public function get_members_count($key)
    {
        $response = $this->execute_command( "SCARD {$key}" );
        if ( $this->get_error($response) )
        {
            return;
        }

        return (int)substr($response, 1);
    }

    # === Middle tier ===

    /**
     * Init connection
     */
    private function get_connection()
    {
        $this->handle = fsockopen($this->host, $this->port, $errno, $errstr);
        if ( !$this->handle )
        {
            return false;
        }

        // AUTH if password exists
        if( $this->password ) {
            $response = $this->execute_command('AUTH '.$this->password);
            if ( $this->get_error($response) ){
                return;
            }
        }
        return $this->handle;
    }

    private function pack_value( $value )
    {
        if ( is_numeric($value) )
        {
            return $value;
        }
        else
        {
            return serialize($value);
        }
    }

    private function unpack_value( $packed )
    {
        if ( is_numeric($packed) )
        {
            return $packed;
        }

        return unserialize($packed);
    }

    private function execute_command( $commands )
    {
        if(!$this->handle){
            if(!$this->get_connection()) return false;
        }

        if ( is_array($commands) )
        {
            $commands = implode("\n", $commands);
        }

        $command = '';
        $command .= $commands . "\r\n";

        for ( $written = 0; $written < strlen($command); $written += $fwrite )
        {
            if ( !$fwrite = fwrite($this->handle, substr($command, $written)) )
            {
                return false;
            }
        }

        return $this->get_response();
    }

    private function get_response()
    {
        if ( !$this->handle ) return false;
        return trim(fgets($this->handle), "\r\n ");
    }

    private function get_error( $response )
    {
        if ( strpos($response, '-ERR') === 0 )
        {
            return substr($response, 5);
        }

        return false;
    }
}

class Redissession
{
    private $redis = NULL;
    private $db = 0;
    // stores settings
    public $settings;

    private static $_instance;

    /**
     * Constructor
     *
     * sets the settings to their new values or uses the default values
     *
     * @param (Array) $settings
     * @return void
     */
    public function __construct( $settings = array() )
    {
        // A neat way of doing setting initialization with default values
        $this->settings = array_merge(array(
            'session.name'		=> 'pp_zone_session',
            'session.id'		=> '',
            'session.expires'	=> 3600,
            //'session.expires'	=> ini_get('session.gc_maxlifetime'),
            'cookie.lifetime'	=> 0,
            'cookie.path'		=> '/',
            'cookie.domain'		=> '',
            'cookie.secure'		=> false,
            'cookie.httponly'	=> true,
            'redis.host'		=> '127.0.0.1',
            'redis.port'	    => 6379,
            'redis.password'    => ''
        ), $settings);

        // if the setting for the expire is a string convert it to an int
        if ( is_string($this->settings['session.expires']) ){
            $this->settings['session.expires'] = intval($this->settings['session.expires']);
        }

        // cookies blah!
        session_name($this->settings['session.name']);

        session_set_cookie_params(
            $this->settings['cookie.lifetime'],
            $this->settings['cookie.path'],
            $this->settings['cookie.domain'],
            $this->settings['cookie.secure'],
            $this->settings['cookie.httponly']
        );

        ini_set('session.save_handler', 'user');
        session_set_save_handler(
            array($this, "open"),
            array($this, "close"),
            array($this, "read"),
            array($this, "write"),
            array($this, "destroy"),
            array($this, "gc")
        );
        session_start();
        //session_regenerate_id(true);
    }

    public static function instance($settings = array())
    {
        if (self::$_instance == null) {
            self::$_instance = new self($settings);
        }
        return self::$_instance;
    }

    public function open($save_path, $session_name)
    {
        if ($this->redis === NULL){
            if(class_exists('Redis')){
                $this->redis = new Redis();
                $this->redis->connect($this->settings['redis.host'], $this->settings['redis.port']);
                if($this->settings['redis.password']){
                    $this->redis->auth($this->settings['redis.password']);
                }
            } else {
                $this->redis = new MyRedis($this->settings['redis.host'], $this->settings['redis.port'], $this->settings['redis.password']);
            }
            if ($this->db != 0) {
                $this->redis->select($this->db);
            }
        }
        return true;
    }

    public function close()
    {
        $this->redis = NULL;
        return true;
    }

    public function read($session_id)
    {
        $key = "{$this->settings['session.name']}:{$session_id}";

        $sess_data = $this->redis->get($key);
        if ($sess_data === NULL){
            return "";
        }
        return $sess_data;
    }

    public function write($session_id, $sess_data)
    {
        $key = "{$this->settings['session.name']}:{$session_id}";
        $lifetime = $this->settings['session.expires'];

        $this->redis->setex($key, $lifetime, $sess_data);
    }

    public function destroy($session_id)
    {
        $key = "{$this->settings['session.name']}:{$session_id}";

        $this->redis->del($key);
    }

    public function gc($maxlifetime)
    {
        return true;
    }

    public function init()
    {

    }

    /**
     * Destructor
     *
     * do things
     * @return void
     */
    public function __destruct()
    {
        // the following prevents unexpected effects when using objects as save handlers
        session_write_close();
    }

    /**
     * Set session
     *
     * @access	public
     * @return
     */
    public function set_session($newdata = array(), $newval = '')
    {
        //$this->init();
        if (is_string($newdata)){
            $newdata = array($newdata => $newval);
        }

        if (count($newdata) > 0){
            foreach ($newdata as $key => $val){
                $_SESSION[$key] = $val;
            }
        }
    }
    /**
     * Get one session
     *
     * @access	public
     * @return	string
     */
    public function get_session($key)
    {
        return ( ! isset($_SESSION[$key])) ? FALSE : $_SESSION[$key];
    }
    /**
     * Get all session
     *
     * @access	public
     * @return	array
     */
    public function get_all_session()
    {
        return (empty($_SESSION)) ? FALSE : $_SESSION;
    }

    /**
     * Destroy the current session
     *
     * @access	public
     * @return	void
     */
    public function destroy_session()
    {
        $_SESSION = array();

        /***刪除sessin id.由於session默認是基於cookie的**/
        if (isset($_COOKIE[session_name()])) {
            setcookie(session_name(), '', time()-42000, '/');
        }

        // Kill session data
        session_destroy();
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章