Session自定义存储及分布式存储
默认情况下,PHP 使用内置的文件会话保存管理器(files)来完成会话的保存。我们无需设置,PHP默认将session以文件的形式保存到服务器。
通过调用函数 session_start() 即可手动开始一个会话。如果配置项 session.auto_start 设置为1, 那么请求开始的时候,会话会自动开始。
PHP也提供了自定义会话保存管理器功能。有时候我们希望session可以保存到其他地方,如数据库。
自定义Session存储函数
如果需要在数据库中或者以其他方式存储会话数据, 需要使用 session_set_save_handler() 函数来创建一系列用户级存储函数。
函数 session_set_save_handler() 的参数即为在会话生命周期内要调用的一组回调函数: open, read, write 以及 close。 还有一些回调函数被用来完成垃圾清理:destroy 用来删除会话, gc 用来进行周期性的垃圾收集。
PHP手册上说明了实现原理:
会话开始的时候,PHP 会调用
open管理器,然后再调用read回调函数来读取内容,该回调函数返回已经经过编码的字符串。 然后 PHP 会将这个字符串解码,并且产生一个数组对象,然后保存至$_SESSION超级全局变量。
当 PHP 关闭的时候(或者调用了
session_write_close()之后), PHP 会对$_SESSION中的数据进行编码, 然后和会话 ID 一起传送给write回调函数。write回调函数调用完毕之后,PHP 内部将调用close回调函数。
销毁会话时,PHP 会调用
destroy回调函数。
根据会话生命周期时间的设置,PHP 会不时地调用
gc回调函数。 该函数会从持久化存储中删除超时的会话数据。 超时是指会话最后一次访问时间距离当前时间超过了$lifetime所指定的值。
需要实现:
session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )
MySQL存储session示例:
<?php
/*
CREATE TABLE IF NOT EXISTS `sessions` (
`id` varchar(40) NOT NULL,
`ip_address` varchar(45) NOT NULL DEFAULT '',
`timestamp` int(10) unsigned DEFAULT 0 NOT NULL,
`data` blob NOT NULL,
KEY `sessions_timestamp` (`timestamp`)
);
*/
$con = mysqli_connect("127.0.0.1", "user" , "pass", "session");
function open($save_path, $session_name) {
return true;
}
function close() {
return true;
}
function read($id, $con) {
if ($result = mysqli_query($con, sprintf("select * from sessions where `id`='%s' and `timestamp` > '%s'", $id, time()))) {
if ($row = mysqli_fetch_row($result)) {
return $row["data"];
}
}else {
return "";
}
}
function write($id, $data, $con) {
$expire = time() + SESSION_MAXLIFETIME;
$sql = 'INSERT INTO `sessions` (`id`, `data`, `timestamp`) '
. 'values (%s, %s, %s) '
. 'ON DUPLICATE KEY UPDATE data = %s, timestamp = %s';
if ($result = mysqli_query($con, $con, sprintf($sql, $id, $data, $expire, $data, $expire))) {
return true;
} else {
return false;
}
}
function destroy($id, $con) {
if ($result = mysqli_query($con, sprintf("delete * from sessions where id=%s", $id))) {
return true;
}else {
return false;
}
}
function gc($maxlifetime, $con) {
if ($result = mysqli_query($con, sprintf("delete * from sessions where timestamp < %s", time()))) {
return true;
} else {
return false;
}
}
session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
session_start();
当然,也支持类里面的方法:
<?php
class MySQLSession{
private $con;
function open($save_path, $session_name) {
$this->con = mysqli_connect("127.0.0.1", "user" , "pass", "session");
}
function close() {
mysqli_close($this->con);
}
function read($id) {
if ($result = mysqli_query($this->con, sprintf("select * from sessions where `id`=%s and `timestamp` > %s", $id, time()))) {
if ($row = mysqli_fetch_row($result)) {
return $row["data"];
}
}else {
return "";
}
}
function write($id, $data) {
$expire = time() + SESSION_MAXLIFETIME;
$sql = 'INSERT INTO `sessions` (`id`, `data`, `timestamp`) '
. 'values (%s, %s, %s) '
. 'ON DUPLICATE KEY UPDATE data = %s, timestamp = %s';
if ($result = mysqli_query($this->con, sprintf($sql, $id, $data, $expire, $data, $expire))) {
return true;
} else {
return false;
}
}
function destroy($id) {
if ($result = mysqli_query($this->con, sprintf("delete * from sessions where id=%s", $id))) {
return true;
}else {
return false;
}
}
function gc($maxlifetime) {
if ($result = mysqli_query($this->con, sprintf("delete * from sessions where timestamp < %s", time()))) {
return true;
} else {
return false;
}
}
}
$handler = new MySQLSession();
session_set_save_handler(
array($handler, 'open'),
array($handler, 'close'),
array($handler, 'read'),
array($handler, 'write'),
array($handler, 'destroy'),
array($handler, 'gc')
);
// 下面这行代码可以防止使用对象作为会话保存管理器时可能引发的非预期行为
register_shutdown_function ( 'session_write_close' );
session_start();
自 PHP 5.4 开始,可以使用下面的方式来注册自定义会话存储函数:
bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] )
需要实现类里的open,write,read,destroy,gc,close方法。
<?php
class MySessionHandler implements SessionHandlerInterface{
public function open($save_path, $session_id)
{
return true;
}
/**
* 写session
*/
public function write($session_id, $session_data)
{
// TODO: Implement write() method.
}
/**
* 读取session
*/
public function read($session_id)
{
// TODO: Implement read() method.
}
/**
* 删除指定session
*/
public function destroy($session_id)
{
// TODO: Implement destroy() method.
}
/**
* 销毁过期session
*/
public function gc($maxlifetime)
{
// TODO: Implement gc() method.
}
public function close()
{
return true;
}
}
$handler = new MySessionHandler ();
session_set_save_handler ( $handler , true );
session_start ();
分布式存储
当多台服务器负载均衡使用时,这时候还在使用默认的文件存储session方式,就会造成session不同步。这时候我们可以把session存储到同一个地方,如上面的mysql存储session方式。下面看看memcache、redis等如何存储session。
一些 PHP 扩展提供了内置的会话管理器,例如:redis, memcache, 可以通过配置项 session.save_handler 来使用它们。
对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。
对于缓存类保存管理器,会将会话数据保存到配置项 session.save_path 所指定的地址。
Memcache
<?php
ini_set("session.save_handler", "memcache"); // memcache
ini_set("session.save_path", "127.0.0.1:11211"); // 不要tcp:
session_start();
$mem = new memcache();
$mem->addServer('127.0.0.1', '11211');
//测试memcache是否正常
//$mem->add('uid', 6, 0, 3600);
//echo $mem->get('uid');
//设置一个session
//$_SESSION['uid'] = 10;
//var_dump($_SESSION);
//查看session在memcache里的存储
echo $mem->get(session_id());
Redis
<?php
ini_set("session.save_handler", "redis"); // memcache
ini_set("session.save_path", "127.0.0.1:6379"); // 不要tcp:
session_start();
$redis = new redis();
$redis->connect('127.0.0.1', '6379');
//测试redis是否正常
//$redis->set('uid', 6, 3600);
//echo $redis->get('uid');
//设置一个session
$_SESSION['uid'] = 10;
//var_dump($_SESSION);
//查看session在redis里的存储
var_dump($redis->get('PHPREDIS_SESSION:'.session_id()));
实例
ThinkPHP3.13设置session入库
准备工作:
1、建立session表:
CREATE TABLE `thinkphp_session` (
`session_id` varchar(255) NOT NULL,
`session_expire` int(11) NOT NULL,
`session_data` blob,
UNIQUE KEY `session_id` (`session_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
这里的表名以实际为准。
2、修改配置文件
配置文件config.php中配置session的数据表,追加一下数据:
'SESSION_OPTIONS' => array(
'type'=> 'db',//session采用数据库保存
'name' => 'PHPSESSION', //设置session名
'expire' => 3600 * 24 * 1, //SESSION保存时间
'use_trans_sid' => 1, //跨页传递
'use_only_cookies' => 0, //是否只开启基于cookies的session的会话方式
),
'SESSION_TABLE'=>'thinkphp_session',
3、确保相关驱动存在,位于Core\Extend\Driver\Session:
SessionDb.class.php
该文件默认使用mysql系列函数连接,需要修改为mysqli:
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
defined('THINK_PATH') or exit();
/**
* 数据库方式Session驱动
* CREATE TABLE think_session (
* session_id varchar(255) NOT NULL,
* session_expire int(11) NOT NULL,
* session_data blob,
* UNIQUE KEY `session_id` (`session_id`)
* );
* @category Extend
* @package Extend
* @subpackage Driver.Session
* @author liu21st <liu21st@gmail.com>
*/
class SessionDb {
/**
* Session有效时间
*/
protected $lifeTime = '';
/**
* session保存的数据库名
*/
protected $sessionTable = '';
/**
* 数据库句柄
*/
protected $hander = array();
/**
* 打开Session
* @access public
* @param string $savePath
* @param mixed $sessName
*/
public function open($savePath, $sessName) {
$this->lifeTime = C('SESSION_EXPIRE')?C('SESSION_EXPIRE'):ini_get('session.gc_maxlifetime');
$this->sessionTable = C('SESSION_TABLE')?C('SESSION_TABLE'):C("DB_PREFIX")."session";
//分布式数据库
$host = explode(',',C('DB_HOST'));
$port = explode(',',C('DB_PORT'));
$name = explode(',',C('DB_NAME'));
$user = explode(',',C('DB_USER'));
$pwd = explode(',',C('DB_PWD'));
if(1 == C('DB_DEPLOY_TYPE')){
//读写分离
if(C('DB_RW_SEPARATE')){
$w = floor(mt_rand(0,C('DB_MASTER_NUM')-1));
if(is_numeric(C('DB_SLAVE_NO'))){//指定服务器读
$r = C('DB_SLAVE_NO');
}else{
$r = floor(mt_rand(C('DB_MASTER_NUM'),count($host)-1));
}
//主数据库链接
$hander = mysqli_connect(
$host[$w].(isset($port[$w])?':'.$port[$w]:':'.$port[0]),
isset($user[$w])?$user[$w]:$user[0],
isset($pwd[$w])?$pwd[$w]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$w])?$name[$w]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander[0] = $hander;
//从数据库链接
$hander = mysqli_connect(
$host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]),
isset($user[$r])?$user[$r]:$user[0],
isset($pwd[$r])?$pwd[$r]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$r])?$name[$r]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander[1] = $hander;
return true;
}
}
//从数据库链接
$r = floor(mt_rand(0,count($host)-1));
$hander = mysqli_connect(
$host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]),
isset($user[$r])?$user[$r]:$user[0],
isset($pwd[$r])?$pwd[$r]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$r])?$name[$r]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander = $hander;
return true;
}
/**
* 关闭Session
* @access public
*/
public function close() {
if(is_array($this->hander)){
$this->gc($this->lifeTime);
return (mysqli_close($this->hander[0]) && mysqli_close($this->hander[1]));
}
$this->gc($this->lifeTime);
return mysqli_close($this->hander);
}
/**
* 读取Session
* @access public
* @param string $sessID
*/
public function read($sessID) {
$hander = is_array($this->hander)?$this->hander[1]:$this->hander;
$res = mysqli_query($hander, "SELECT session_data AS data FROM ".$this->sessionTable." WHERE session_id = '$sessID' AND session_expire >".time());
if($res) {
$row = mysqli_fetch_assoc($res);
return $row['data'];
}
return "";
}
/**
* 写入Session
* @access public
* @param string $sessID
* @param String $sessData
*/
public function write($sessID,$sessData) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
$expire = time() + $this->lifeTime;
mysqli_query($hander, "REPLACE INTO ".$this->sessionTable." ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')");
if(mysqli_affected_rows($hander))
return true;
return false;
}
/**
* 删除Session
* @access public
* @param string $sessID
*/
public function destroy($sessID) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
mysqli_query($hander, "DELETE FROM ".$this->sessionTable." WHERE session_id = '$sessID'");
if(mysqli_affected_rows($hander))
return true;
return false;
}
/**
* Session 垃圾回收
* @access public
* @param string $sessMaxLifeTime
*/
public function gc($sessMaxLifeTime) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
mysqli_query($hander, "DELETE FROM ".$this->sessionTable." WHERE session_expire < ".time());
return mysqli_affected_rows($hander);
}
/**
* 打开Session
* @access public
*/
public function execute() {
session_set_save_handler(array(&$this,"open"),
array(&$this,"close"),
array(&$this,"read"),
array(&$this,"write"),
array(&$this,"destroy"),
array(&$this,"gc"));
}
}
经过测试,使用了函数session_set_save_handler后,session.save_handle后的值被修改为了user。
Session自定义存储及分布式存储的更多相关文章
- 可灵活扩展的自定义Session状态存储驱动
Session是互联网应用中非常重要的玩意儿,对于超过单台部署的站点集群,都会存在会话共享的需求.在web.config中,微软提供了sessionstate节点来定义不同的Session状态存储方式 ...
- ASP.net 中关于Session的存储信息及其它方式存储信息的讨论与总结
通过学习和实践笔者总结一下Session 的存储方式.虽然里面的理论众所周知,但是我还是想记录并整理一下.作为备忘录吧.除了ASP.net通过Web.config配置的方式,还有通过其它方式来存储的方 ...
- session的存储方式和配置
Session又称为会话状态,是Web系统中最常用的状态,用于维护和当前浏览器实例相关的一些信息.我们控制用户去权限中经常用到Session来存储用户状态,这篇文章会讲下Session的存储方式.在w ...
- Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程
待解决的问题 Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程 解决办法 为spring session添加spr ...
- [转]mvc3 使用session来存储类来存储用户登陆信息
mvc3 使用session来存储类来存储用户登陆信息 2013-08-26 09:48:56| 分类: NET开发 |举报 |字号 订阅 项目之前的登陆机制是这样的:用户登陆后初始化一个类,类 ...
- 多台web如何共享session进行存储(转载)
session的存储了解以前是怎么做的,搞清楚了来龙去脉,才会明白进行共享背后的思想和出发点.我喜欢按照这样的方式来问(或者去搞清楚):为什么要session要进行共享,不共享会什么问题呢? php中 ...
- PHP学习笔记:使用session来存储用户的登录信息
session可以用来存储多种类型的数据,因此具有很多的用途,常用来存储用户的登录信息,购物车数据,或者一些临时使用的暂存数据等. 用户在登录成功以后,通常可以将用户的信息存储在session中,一般 ...
- 修改session的存储机制
<?php //修改session的存储机制 //最起码应该有一个 读方法, 和一个 写方法. //1, 我们先去建立 读方法 和 写方法. //2, 告知session系统,使用我们的方法完 ...
- JAVA之旅(二十)—HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习
JAVA之旅(二十)-HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习 我们继续说一下集合框架 Set:元素是无序(存入和取出的顺序不一定 ...
随机推荐
- C#中的using和yield return混合使用
最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了.我的代码里还有 ...
- ExtJS入门实例
一.去官网下载EXTJS包extjs5,这里采用的是5.0版本! 二.解压extjs包,找到 ext-all.js基础包(\ext-5.0.0\build): ext-all-debug.js基础包, ...
- scrapy-1.2.1安装失败之解决方法
前几天重装了win10 64位系统,安装了python-3.5,但是用pip安装scrapy模块时出现如下错误: 看起来是lxml安装的时候出错了,于是先pip install lxml,出现了和上面 ...
- 阿里云安装JDK1.7
本人阿里云选择的是CentOS 7.0系统,本系列文件将全部基于此环境. 1.下载JDK,版本为jdk-7u79-linux-x64.tar.gz 2.使用FileZilla上传至/softwar ...
- React的Diff算法
使用React或者RN开发APP如果不知道Diff算法的话简直是说不过去啊.毕竟"知其然,知其所以然"这句老话从远古喊到现代了. 以下内容基本是官网文章的一个总结.压缩.这次要谦虚 ...
- invalidate()和postInvalidate()的使用与区别
Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型: Android UI操作并不是线程安全的,并且这些操作必须在UI线程 ...
- bochs上网及配置
下载并安装bochs2.6:(不能是更高版本) 创建bochs 时注意勾选Dlx linux Demo,但是其文件bochsrc.bxrc中无Ne2k网卡选项,这一段要自己添加,详情见后. 先确定我们 ...
- http://blog.csdn.net/chenriwei2/article/details/38047119
SSP或者说是空间金字塔匹配(spatial pyramid matching or SPM)是BoW的一个扩展,它把一张图片划分为从不同的分辨率级别然后聚合这些不同分辨率的图像,在深度学习之前SPM ...
- 甲乙(数理逻辑)转自http://www.cnblogs.com/devymex/p/3329635.html
这是一道历史悠久,又很困难的面试题. 你在旁观主持人和甲.乙两个天才数学家玩猜数字游戏.主持人准备了两个数,告知甲乙:这两个数不同,且大于等于1,小于等于30.然后主持人将两数之积告诉甲,把两数之和告 ...
- 新装ubuntu12.04需要敲的命令集合
1.sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup sudo gedit /etc/apt/sources.list copy: ...