发新话题
打印

php session共享(实现多服务器共享 SESSION 数据)

php session共享(实现多服务器共享 SESSION 数据)

一、问题起源

稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名、密码在整个网站的各个模块中都是可以登录使用的。各个服务器共享用户数据是比较容易实现的,只需要在后端放个数据库服务器,各个服务器通过统一接口对用户数据进行访问即可。但还存在一个问题,就是用户在这个服务器登录之后,进入另一个服务器的别的模块时,仍然需要重新登录,这就是一次登录,全部通行的问题,映射到技术上,其实就是各个服务器之间如何实现共享 SESSION 数据的问题。

二、多服务器共享 SESSION 的主要障碍及解决办法

通过了解 SESSION 的工作原理,我们可以发现,在默认情况下,各个服务器会各自分别对同一个客户端产生 SESSION ID,如对于同一个用户浏览器,A 服务器产生的 SESSION ID 是 30de1e9de3192ba6ce2992d27a1b6a0a,而 B 服务器生成的则是 c72665af28a8b14c0fe11afe3b59b51b。另外,PHP 的 SESSION 数据都是分别保存在本服务器的文件系统中。

确定了问题所在之后,就可以着手进行解决了。想要共享 SESSION 数据,那就必须实现两个目标:一个是各个服务器对同一个客户端产生的 SESSION ID 必须相同,并且可通过同一个 COOKIE 进行传递,也就是说各个服务器必须可以读取同一个名为 PHPSESSID 的 COOKIE;另一个是 SESSION 数据的存储方式/位置必须保证各个服务器都能够访问到。简单地说就是多服务器共享客户端的 SESSION ID,同时还必须共享服务器端的 SESSION 数据。

第一个目标的实现其实很简单,只需要对 COOKIE 的域(domain)进行特殊地设置即可,默认情况下,COOKIE 的域是当前服务器的域名/IP 地址,而域不同的话,各个服务器所设置的 COOKIE 是不能相互访问的,如 www.aaa.com 的服务器是不能读写 www.bbb.com 服务器设置的 COOKIE 的。这里我们所说的同一网站的服务器有其特殊性,那就是他们同属于同一个一级域,如:aaa.infor96.com 和 www.infor96.com 都属于域 .infor96.com,那么我们就可以设置 COOKIE 的域为 .infor96.com,这样 aaa.infor96.com、www.infor96.com 等等都可以访问此 COOKIE。PHP 代码中的设置方法如下:

<?php
ini_set
('session.cookie_domain''.infor96.com'
);
?>

这样各个服务器共享同一客户端 SESSION ID 的目的就达到了。

第二个目标的实现可以使用文件共享方式,如 NFS 方式,但设置、操作上有些复杂。我们可以参考先前所说的统一用户系统的方式,即使用数据库来保存 SESSION 数据,这样各个服务器就可以方便地访问同一个数据源,获取相同的 SESSION 数据了。

 

TOP

三、实现方式

采用memcache来存储session
自定义一套session处理机制,关于session的实现方法我就不再多讲,直接贴程序了。
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
//===========================================
// 程序:   Memcache-Based Session Class
// 功能:   基于Memcache存储的 Session 功能类
// 作者:    yejr
// 网站:    http://imysql.cn
// 时间:    2007-01-05
//===========================================

/**
 * 文件:    MemcacheSession.inc.php
 * 类名:    MemcacheSession Class
 * 功能:    自主实现基于Memcache存储的 Session 功能
 * 描述:    这个类就是实现Session的功能,基本上是通过
 *          设置客户端的Cookie来保存SessionID,
 *          然后把用户的数据保存在服务器端,最后通过
 *          Cookie中的Session Id来确定一个数据是否是用户的,
 *          然后进行相应的数据操作
 *
 *          本方式适合Memcache内存方式存储Session数据的方式,
 *          同时如果构建分布式的Memcache服务器,
 *          能够保存相当多缓存数据,并且适合用户量比较多并发比较大的情况
 *
 * 注意: 本类必须要求PHP安装了Memcache扩展或者必须有Memcache的PHP API
 *       获取Memcache扩展请访问: http://pecl.php.net
 */

//设定 SESSION 有效时间,单位是 秒
define('SESS_LIFTTIME', 3600);

//定义memcache配置信息
define('MEMCACHE_HOST', 'localhost');
define('MEMCACHE_PORT', '10000');

if (!defined('MemcacheSession'))
{
    define('MemcacheSession',    TRUE);

class MemacheSession
{

    // {{{ 类成员属性定义
    static  $mSessSavePath;
    static  $mSessName;
    static  $mMemcacheObj;
    // }}}

    // {{{ 初始化构造函数
    /**
     * 构造函数
     *
     * @param string $login_user    登录用户
     * @param int $login_type       用户类型
     * @param string $login_sess    登录Session值
     * @return Esession
     */
    public function __construct()
    {
        //我的memcache是以php模块的方式编译进去的,可以直接调用
        //如果没有,就请自己包含 Memcache-client.php 文件
        if (!class_exists('Memcache') || !function_exists('memcache_connect'))
        {
            die('Fatal Error:Can not load Memcache extension!');
        }

        if (!empty(self::$mMemcacheObj) && is_object(self::$mMemcacheObj))
        {
            return false;
        }

        self::$mMemcacheObj = new Memcache;

        if (!self::$mMemcacheObj->connect(MEMCACHE_HOST , MEMCACHE_PORT))
        {
            die('Fatal Error: Can not connect to memcache host '. MEMCACHE_HOST .':'. MEMCACHE_PORT);
        }

        return TRUE;
    }
    // }}}

    /** {{{ sessOpen($pSavePath, $name)
     *
     * @param   String  $pSavePath
     * @param   String  $pSessName
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessOpen($pSavePath = '', $pSessName = '')
    {
        self::$mSessSavePath    = $pSavePath;
        self::$mSessName        = $pSessName;

        return TRUE;
    }
    // }}}

    /** {{{ sessClose()
     *
     * @param   NULL
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessClose()
    {
        return TRUE;
    }
    // }}}

    /** {{{ sessRead($wSessId)
     *
     * @param   String  $wSessId
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessRead($wSessId = '')
    {
        $wData = self::$mMemcacheObj->get($wSessId);

        //先读数据,如果没有,就初始化一个
        if (!empty($wData))
        {
            return $wData;
        }
        else
        {
            //初始化一条空记录
            $ret = self::$mMemcacheObj->set($wSessId, '', 0, SESS_LIFTTIME);

            if (TRUE != $ret)
            {
                die("Fatal Error: Session ID $wSessId init failed!");

                return FALSE;
            }

            return TRUE;
        }
    }
    // }}}

    /** {{{ sessWrite($wSessId, $wData)
     *
     * @param   String  $wSessId
     * @param   String  $wData
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessWrite($wSessId = '', $wData = '')
    {
        $ret = self::$mMemcacheObj->replace($wSessId, $wData, 0, SESS_LIFTTIME);

        if (TRUE != $ret)
        {
            die("Fatal Error: SessionID $wSessId Save data failed!");

            return FALSE;
        }

        return TRUE;
    }
    // }}}

    /** {{{ sessDestroy($wSessId)
     *
     * @param   String  $wSessId
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessDestroy($wSessId = '')
    {
        self::sessWrite($wSessId);

        return FALSE;
    }
    // }}}

    /** {{{ sessGc()
     *
     * @param   NULL
     *
     * @return  Bool    TRUE/FALSE
     */
    public function sessGc()
    {
        //无需额外回收,memcache有自己的过期回收机制

        return TRUE;
    }
    // }}}

    /** {{{ initSess()
     *
     * @param   NULL
     *
     * @return  Bool    TRUE/FALSE
     */
    public function initSess()
    {
        $domain = '.imysql.cn';

        //不使用 GET/POST 变量方式
        ini_set('session.use_trans_sid',    0);

        //设置垃圾回收最大生存时间
        ini_set('session.gc_maxlifetime',   SESS_LIFTTIME);

        //使用 COOKIE 保存 SESSION ID 的方式
        ini_set('session.use_cookies',      1);
        ini_set('session.cookie_path',      '/');

        //多主机共享保存 SESSION ID 的 COOKIE
        ini_set('session.cookie_domain',    $domain);

        //将 session.save_handler 设置为 user,而不是默认的 files
        session_module_name('user');

        //定义 SESSION 各项操作所对应的方法名:
        session_set_save_handler(
                array('MemacheSession', 'sessOpen'),   //对应于静态方法 My_Sess::open(),下同。
                array('MemacheSession', 'sessClose'),
                array('MemacheSession', 'sessRead'),
                array('MemacheSession', 'sessWrite'),
                array('MemacheSession', 'sessDestroy'),
                array('MemacheSession', 'sessGc')
                );

        session_start();

        return TRUE;
    }
    // }}}

}//end class

}//end define

$memSess    = new MemacheSession;
$memSess->initSess();
?>
然后,在项目程序的头文件中直接包含 MemacheSession.inc.php 即可,并且以前的程序不用做任何改动。
备注:memcache PECL 未来版本中,可以直接设置 php.ini 来这定自己的 session.save_handler,大致如下:
session.save_handler = memcache
session.save_path = "tcp://host:port?persistent=1&weight=2&timeout=2&retry_interval=15,tcp://host2:port2"

 

TOP

发新话题