蒙国造博客

Redis缓存Session同步的实践方案

最近公司Web服务器换集群方式,集群所带来直接的问题就是session共享。
如果用PHP自带的session处理方式,又要达到一致性,我已知的解决方案是NFS方法,不过担心磁盘性能以及session的处理机制,决定放弃这种方法,最后决定用内存缓存服务器来实现。

公司目前主要缓存的使用已经全部转至Redis下面(主要因为我的极力推荐,呵呵)。所以几简单写了个类实现了对session的操作,后续还要进行优化和扩展,前期没办法呀,公司催得紧呀。。。。

下面把代码贴出来,大家也分享一下了。呵呵。有啥意见也可以提提,别拍砖。呵呵。

/**
 * @author shenjun
 * @Createdate 2010-10-14
 * @todo session机制,存在redis内存中,解决web集群中session共享问题
 */

class Session{

    static protected $connect = FALSE;

    // Redis服务器属性
    protected $redis = NULL;
    protected $redis_host = '192.168.1.107';
    protected $redis_port = '6379';

    // Session属性
    protected $sess_id = NULL ;
    protected $sess_life = 300 ;
    protected $sessions = array () ;

    // 是否自动保存session,默认为自动保存
    protected $auto_save = true ;

    // 判断是否有修改过 session 中的值
    protected $changed = false;

    /**
    * @todo redis初始化方法,单例入口
    * @desc 自动判断系统是否带redis,则是否有编译redis的客户端环境
    */
    static public function singleton()
    {
        if ( self::$connect == FALSE )
        {
            self::$connect = new Session();
        }

        return self::$connect;
    }

    /**
    * @todo 构造函数
    * @desc 建立redis连接,取得已有sessionID,并取得所有session的值
    */
    protected function __construct()
    {
        // 连接redis数据库
        if ( class_exists( 'redis' ) )
        {
            $redis = new Redis ( );
            $conn = $redis->connect( $this->redis_host , $this->redis_port );
        } else {
            require_once dirname(__FILE__) . '/PhpRedis.php';
            $redis = new PhpRedis ( $this->redis_host , $this->redis_port );
            $conn = $redis->connect();
        }

        if ( $conn )
        {
            $this->redis = $redis ;
        } else {
            trigger_error( '无法正常连接缓存服务器!' , E_USER_ERROR );
        }

        $sess_name = $this->GetSessionName();

        // 取得session ID
        if ( isset( $_COOKIE[ $sess_name ] ) && !empty( $_COOKIE[ $sess_name ] ) )
        {
            $this->sess_id = $_COOKIE[ $sess_name ] ;
            
            // 如果已经有session ID则取出其中的值
            $this->sessions = (array)json_decode( $this->redis->get( $this->sess_id ) );
        } else {
            $this->sess_id = $this->GetSessionID() ;
            
            //如果没有cookie则建立cookie
            setcookie( $sess_name , $this->sess_id );
        }

        return $this;
    }

    /**
    * @todo 取得session name
    */
    public function GetSessionName ()
    {
        // sessionname 的名称用客户端的IP加上浏览器的信息
        $name = $_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT'];
        
        return hash ( 'crc32' , $name );
    }

    /**
    * @todo 取得sessionID
    * @return string 返回sessionID
    */
    public function GetSessionID( )
    {
        if ( $this->sess_id == null )
        {
            $id = time().$_SERVER['HTTP_USER_AGENT'];
            $this->sess_id = hash( 'md5' , $id );
        }

        return $this->sess_id;
    }

    /**
    * @todo 设置session 值
    * @desc 每次设置的值不会马上写入缓存,不过会记录在内存中,所以写入的值在当次也会有效
    * @param string $name 相当于$_SESSION[$name] 这中间的变量
    * @param any $value Session的值
    */
    public function Set ( $name , $value )
    {
        $this->sessions[ $name ] = $value;
        $this->changed = true ;
    }

    public function __call( $name , $param )
    {
        trigger_error( sprintf( '您调用了不存的session方法%s!' , $name ) , E_USER_ERROR );
    }

    public function info()
    {
        return $this->redis->info();
    }

    /**
    * @todo 取得session中所有的字段
    * @desc 私有方法,不供外部使用
    * @return array session中的值,如果空session则为空数组
    */
    protected function GetAll( )
    {
        return count( $this->sessions ) > 0 ? $this->sessions : json_decode( $this->redis->get( $this->sess_id ) );
    }

    /**
    * @todo 取得session中的值
    * @desc 如果$name 为空,则返回全部session,如果不为空则返回对应key的值,如果key不存在,则返回空
    * @param string $name session中的key
    * @return array or string Session的值
    */
    public function Get( $name = '' )
    {
        if ( empty( $name ) )
            return $this->sessions;
        if ( isset( $this->sessions[ $name ] ) )
            return $this->sessions[ $name ];
        
        return null ;
    }

    /**
    * @todo 删除session中的值
    * @param string $name session中的key
    * @return 无返回值
    */
    public function Del( $name = '' )
    {
        if ( empty( $name ) )
            $this->sessions = array();
        if ( isset( $this->sessions[ $name ] ) )
            unset ( $this->sessions[ $name ] );

        return false ;
    }

    /**
    * @todo 保存session数据至缓存中
    * @return 无返回值
    */
    public function Save()
    {
        if ($this->changed === true)
        {
            $this->redis->set( $this->sess_id , json_encode( $this->sessions ) );
            
            // 更新过期时间
            $this->redis->expire( $this->sess_id , $this->sess_life );
            
            //当保存过以后,就设置修改标记为假
            $this->changed = false;
        }
    }

    protected function Expire()
    {
        $this->redis->expire( $this->sess_id , $this->sess_life );
    }

    /**
    * @todo 取得session的生命周期
    * @desc 如果已过期则返回-1
    */
    public function GetExpire()
    {
        return $this->redis->ttl( $this->sess_id );
    }

    /**
    * @todo 方法结束时,将session值写入缓存
    */
    public function __destruct()
    {
        $this->auto_save && $this->Save();
    }
}

其实用方法也很简单了。

Session::singleton()->Set('name','shenjun');
echo Session::singleton()->Get() ;
echo Session::singleton()->Get( 'name' ) ;
echo Session::singleton()->GetExpire();

 

参考资料:

  1. http://blog.csdn.net/enough_br/article/details/8572183
退出移动版