2 Session
0 前言
HTTP 是无状态协议,无状态协议的意思是服务端与客户端不会记录任何一次通信的信息。由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是 Cookie 与 Session 。
1 什么是Session
Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。
2 Session机制原理
当客户端请求创建一个 Session 时,服务端会先检查客户端的请求里面有没有带着 Session 标识(Session Id)。如果有,则说明服务器以前已为此客户端创建过 Session ,于是就根据 Session Id 把 Session 检索出来。如果没有,则为客户端创建一个 Session 并且生成一个与这个 Session 相关联的 Session Id。Session Id 将被在本次响应中返回给客户端保存。保存 Session Id 的方式大多情况下用的是Cookie。
3 PHP操作Session
参考:
设置 Session 时,必须先调用session_start()
方法进行初始化,并且在这之前不能有任何输出。
3.1 SessionHandlerInterface
SessionHandlerInterface 定义了创建自定义会话存储需要实现的接口,即回调函数 gc()、open()、read()、write()、close()、destroy()。
3.2 session_set_save_handler
session_set_save_handler() —— 设置用户自定义会话存储函数。
将用户自定义的会话存储类(实现了 SessionHandlerInterface 的类)的回调函数传递给 session_set_save_handler() 调用(由 PHP 内部调用)。
使用方法:用户自定义的会话存储类实例化后,将对象作为参数传给 session_set_save_handler($sessionHandlerObject) 。
例如,tp5.1 中的 Session 使用 Redis 驱动来存储会话,Redis 实现了用户自定义会话存储回调函数 。
<?php
namespace think\session\driver;
use SessionHandlerInterface;
use think\Exception;
class Redis implements SessionHandlerInterface
{
/**
* 打开Session
* @access public
* @param string $savePath
* @param mixed $sessName
* @return bool
* @throws Exception
*/
public function open($savePath, $sessName)
{
// 与 Redis 建立连接,省略代码
}
/**
* 关闭Session
* @access public
*/
public function close()
{
// 省略代码
}
/**
* 读取Session
* @access public
* @param string $sessID
* @return string
*/
public function read($sessID)
{
// 省略代码
}
/**
* 写入Session
* @access public
* @param string $sessID
* @param string $sessData
* @return bool
*/
public function write($sessID, $sessData)
{
// 省略代码
}
/**
* 删除Session
* @access public
* @param string $sessID
* @return bool
*/
public function destroy($sessID)
{
return $this->handler->del($this->config['session_name'] . $sessID) > 0;
}
/**
* Session 垃圾回收
* @access public
* @param string $sessMaxLifeTime
* @return bool
*/
public function gc($sessMaxLifeTime)
{
return true;
}
/**
* Redis Session 驱动的加锁机制
* @access public
* @param string $sessID 用于加锁的sessID
* @param integer $timeout 默认过期时间
* @return bool
*/
public function lock($sessID, $timeout = 10)
{
// 省略代码
}
/**
* Redis Session 驱动的解锁机制
* @access public
* @param string $sessID 用于解锁的sessID
*/
public function unlock($sessID)
{
// 省略代码
}
}
3.3 Session锁
基于文件的会话数据存储,在会话开始的时候都会给会话数据文件加锁, 直到 PHP 脚本执行完毕或者显式调用 session_write_close() 来保存会话数据。 在此期间,其他脚本不可以访问同一个会话数据文件。
<?php
# This works in PHP 5.x and PHP 7
// 将 session 数据读到 $_SESSION中
session_start();
$_SESSION['something'] = 'foo';
// 关闭会话锁:写入 session 数据,关闭 session 文件,解除 session 锁
session_write_close();
PHP7 开始,可以设置额外的选项。这两个例子效果一样。
<?php
# This only works in PHP 7
session_start(['read_and_close' => true]);
$_SESSION['something'] = 'foo';
4 Tp操作Session
session 配置文件中的参数在代码中的使用:
// use_trans_sid
ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
// auto_start
ini_set('session.auto_start', 0);
// use_lock:是否启用锁机制
// var_session_id:请求 Session Id 变量名,默认为 PHPSESSID。
if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
session_id($_REQUEST[$config['var_session_id']]);
} elseif (isset($config['id']) && !empty($config['id'])) {
session_id($config['id']);
}
// name:设置 session_name
session_name($config['name']);
// path
session_save_path($config['path']);
// domain
ini_set('session.cookie_domain', $config['domain']);
// expire
ini_set('session.gc_maxlifetime', $config['expire']);
ini_set('session.cookie_lifetime', $config['expire']);
// secure
ini_set('session.cookie_secure', $config['secure']);
// httponly
ini_set('session.cookie_httponly', $config['httponly']);
// use_cookies
ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
// cache_limiter
session_cache_limiter($config['cache_limiter']);
// cache_expire
session_cache_expire($config['cache_expire']);
// type:设置驱动
\think\session\driver\
5 Session的垃圾回收机制
超过过期时间的 Session 数据会被视为“垃圾”,过期时间默认配置为 1400s,其配置项为:
;php.ini
session.gc_maxlifetime = 1400
这些过期文件需要启动 GC 机制来清除,当调用 session_start() 时触发 GC 机制。
触发概率由 gc_probability/gc_divisor 计算得出,配置项为:
;php.ini
session.gc_probability = 1
session.gc_divisor = 100
6 分布式Session
客户端发送一个请求,经过负载均衡后该请求会被分配到服务器中的其中一个,由于不同服务器含有不同的 WEB 服务器,不同的 WEB 服务器中并不能发现之前 WEB 服务器保存的 Session 信息,就会再次生成一个 Session Id,之前的状态就会丢失。
那分布式 Session 一致性问题如何解决?有以下方案:
- Session 复制
- Session 绑定
- Session 共享
- 客户端存储
6.1 Session复制
Session 复制是小型企业应用使用较多的一种服务器集群 Session 管理机制,在真正的开发使用的并不是很多,通过对 WEB 服务器(例如 Tomcat)进行搭建集群。
- 缺点
- Session 同步的原理是在同一个局域网里面通过发送广播来异步同步 Session 的,一旦服务器多了,并发上来了,Session 需要同步的数据量就大了,需要将其他服务器上的 Session 全部同步到本服务器上,会带来一定的网路开销,在用户量特别大的时候,会出现内存不足的情况。
- 优点
- 服务器之间的 Session 信息都是同步的,任何一台服务器宕机的时候不会影响另外服务器中 Session 的状态,配置相对简单。
- Tomcat 内部已经支持分布式架构开发管理机制,可以对 Tomcat 修改配置来支持 Session 复制。
6.2 Session绑定
利用 Nginx 的反向代理和负载均衡对客户端和服务器进行绑定,同一个客户端就只能访问该服务器,无论客户端发送多少次请求都被同一个服务器处理。
- 缺点
- 容易造成单点故障,如果有一台服务器宕机,那么该台服务器上的 Session 信息将会丢失。
- 前端不能有负载均衡,如果有,Session 绑定将会出问题。
- 优点
- 配置简单。
实践:在 Docker 下,通过 Nginx 负载均衡实现 Session 绑定
6.3 Session共享
6.3.1 基于Redis存储
修改 php.ini 配置:
session.save_handler = redis
session.save_path = "tcp://172.16.60.2:6937?auth=这里是密码"
测试:
<?php
session_start();
$_SESSION['user_info'] = ['id'=>1,'name'=>'jml'];
print_r(session_id());
echo "<br>";
print_r($_SESSION['user_info']);
在 Redis 可以看到结果:
// 键:
PHPREDIS_SESSION:2q9h5fjijjkh98o3k1lp5i4edj
// 值:
{
"user_info": {
"id": 1,
"name": "jml"
}
}
6.3.2 基于Memcache存储
6.3.3 基于数据库存储
6.4 客户端存储
直接将信息存储在 Cookie 中。
- 缺点
- 数据存储在客户端,存在安全隐患。
- Cookie 存储大小、类型存在限制。
- 数据存储在 Cookie 中,如果一次请求 Cookie 过大,会给网络增加更大的开销。
如果文章有帮到你的话,别忘了点赞收藏噢:smile: