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 官方文档

php.ini 的 Session 配置参数文档

设置 Session 时,必须先调用session_start()方法进行初始化,并且在这之前不能有任何输出。

3.1 SessionHandlerInterface

SessionHandlerInterface 定义了创建自定义会话存储需要实现的接口,即回调函数 gc()、open()、read()、write()、close()、destroy()。

2 Session

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

Tp5.1 看云文档

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 一致性问题如何解决?有以下方案:

  1. Session 复制
  2. Session 绑定
  3. Session 共享
  4. 客户端存储

6.1 Session复制

Session 复制是小型企业应用使用较多的一种服务器集群 Session 管理机制,在真正的开发使用的并不是很多,通过对 WEB 服务器(例如 Tomcat)进行搭建集群。

  • 缺点
    1. Session 同步的原理是在同一个局域网里面通过发送广播来异步同步 Session 的,一旦服务器多了,并发上来了,Session 需要同步的数据量就大了,需要将其他服务器上的 Session 全部同步到本服务器上,会带来一定的网路开销,在用户量特别大的时候,会出现内存不足的情况。
  • 优点
    1. 服务器之间的 Session 信息都是同步的,任何一台服务器宕机的时候不会影响另外服务器中 Session 的状态,配置相对简单。
    2. Tomcat 内部已经支持分布式架构开发管理机制,可以对 Tomcat 修改配置来支持 Session 复制。

6.2 Session绑定

利用 Nginx 的反向代理和负载均衡对客户端和服务器进行绑定,同一个客户端就只能访问该服务器,无论客户端发送多少次请求都被同一个服务器处理。

  • 缺点
    1. 容易造成单点故障,如果有一台服务器宕机,那么该台服务器上的 Session 信息将会丢失。
    2. 前端不能有负载均衡,如果有,Session 绑定将会出问题。
  • 优点
    1. 配置简单。

实践:在 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 中。

  • 缺点
    1. 数据存储在客户端,存在安全隐患。
    2. Cookie 存储大小、类型存在限制。
    3. 数据存储在 Cookie 中,如果一次请求 Cookie 过大,会给网络增加更大的开销。

如果文章有帮到你的话,别忘了点赞收藏噢:smile:

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~