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:元素是无序(存入和取出的顺序不一定 ...
随机推荐
- Coursera Robotics系列课心得
Robotics Perception Professor Kostas and Jianbo Shi week 1: camera model 凸透镜成像原理:凸透镜焦点与焦距是固定的,这是物理性质 ...
- JS-数组的方法
var arr = [ 1,2,3 ];arr.push( 'abc' );//从后面加 arr.unshift( 0 );//从前面加 arr.pop()//从后面删除 arr.shift()//从 ...
- 4.总结近5周以来的github上的工作情况,以图表方式分析你小组的工作情况、存在的问题及解决的方案。(尤心心)
4.总结近5周以来的github上的工作情况,以图表方式分析你小组的工作情况.存在的问题及解决的方案. (1)利用github本身的graphs可以清晰的看出小组成员在github上面的交互,可以直接 ...
- 使用TableViewRow完成下拉菜单效果
在TableViewRow标签中增加一个属性opened(自己定义的)=true or false 用于标记当前row的状态 然后用table_view.deleteRow()方法 参数 ...
- 洛谷P1330 封锁阳光大学
题目描述 曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街.河蟹看到欢快的曹,感到不爽.河蟹决定封锁阳光大学,不让曹刷街. 阳光大学的校园是一张由N个点构成的无向图,N个点之间由M ...
- Xcode调用旧版本库出现Undefined symbols for architecture x86_64: ld: symbol(s) not found for architecture x86_64
问题:Undefined symbols for architecture x86_64: ld: symbol(s) not found for architecture x86_64 问题原因 ...
- JAVAWEB学习
http://www.cnblogs.com/xdp-gacl/p/3744053.html JavaWeb学习总结(三)——Tomcat服务器学习和使用(二)
- Java学习笔记五——流程控制
分支结构 Java提供了两种常见的分支控制结构:if语句和switch语句. if语句 if语句使用布尔值或布尔表达式(表达式结果为布尔值),if语句有3中形式: 第一种形式: if (5 > ...
- NSDate
NSDate : NSDate *date = [NSDate date];获取当前日期 NSDate 可以进行比较,通过earlierDate:方法获取二个日期中最早的. NSDate 通过late ...
- 如何为Eclipse安装主题(Color Theme)
Eclipse开发环境默认都是白底黑字的,看到同事的Xcode中设置的黑灰色背景挺好看的,就去网上查了一下.发现Eclipse也可以设置主题. 方法1:你可以从Eclipse Marketplace中 ...