PHP 实现会话Session信息共享

news2024/11/24 14:53:39

目录

解决方案也有很多种: 

会话保持

会话复制

会话共享

环境准备

架构设计

SessionHandlerInterface接口

代码编写

总结

优化


前言:

小流量的网站中,我们往往只需要一台服务器就可以维持用户正常的访问以及相关的操作。
随着网站的访问用户剧增,网站业务变得庞大,一台服务器根本无法支撑,因此当今流行的网站应用都部署了多台服务器,实现均衡负载,用来支撑庞大的用户需求。
在多台服务器中,如何保证会话信息的一致性呢?这是我们设计系统值得关注的问题。
如果使用传统的方法,将会话信息保存在服务器中,假设用户A第一次访问的服务器是ServerA,下次访问的是ServerB,而恰好该用户在ServerA中保存有其登录信息,但ServerB中却不存在该登录信息,造成应用无法正确判断用户已经登录,这样的问题是致命,存在不足的。

解决方案也有很多种: 

  • 会话保持

Session保持(会话保持)是我们见到最多的名词之一,通过会话保持,负载均衡进行请求分发的时候保证每个客户端固定的访问到后端的同一台应用服务器。
会话保持方案在所有的负载均衡都有对应的实现。而且这是在负载均衡这一层就可以解决Session问题。

  • 会话复制

会话复制在Tomcat上得到了支持,它是基于IP组播(multicast)来完成Session的复制,Tomcat的会话复制分为两种:
1)全局会话复制:利用Delta Manager复制会话中的变更信息到集群中的所有其他节点。
2)非全局复制:使用Backup Manager进行复制,它会把Session复制给一个指定的备份节点。

  • 会话共享

既然会话保持和会话复制都不完美,那么我们为什么不把Session放在一个统一的地方呢,这样集群中的所有节点都在一个地方进行Session的存取就可以解决问题。

前两种解决方案都会遇到瓶颈问题,例如会话复制,在集群超过6个节点以上,就会出现各种问题,不推荐使用。

于是,我们需要在设计系统上,实现会话共享。我们可以将会话信息保存到mysql,redis等持久化服务中。

本文将介绍如何使用PHP将会话信息持久化到mysql中,并实现会话共享

环境准备

· laravel5 (php框架)
· mysql
· nginx(配置均衡负载)

架构设计

 在PHP中,有一个函数session_set_save_handler(),该函数的作用是设置会话保存的handler,该函数接受一个SessionHandlerInterface实现。

SessionHandlerInterface接口

官方文档: PHP: session_set_save_handler - Manualhttps://www.php.net/manual/en/function.session-set-save-handler.php

通过查阅PHP官方文档可知,该接口定义了Session的各种处理句柄,开发者可以编写相关的实现类用来实现Session会话的持久化。

了解该接口后,我们现在来编写实现类

代码编写

首先我们需要编写一个获取和保存会话的接口,该接口可以有多种持久化服务的实现类 ,这样在后期系统需要变更持久化服务时候,只需要编写对应服务的驱动实现就可以了。

namespace App\Library\Authorize;
interface SessionSet
{
    /**
     * 获取会话信息数据
     */
    public function getSessionData($session_id);

    /**
     * 创建会话数据
     */
    public function createSessionData($session_id,$extra = "",$expires_time = 0);

    /**
     * 保存会话数据
     */
    public function setSessionData($session_id,$extra = "");

    /**
     * 删除会话信息
     */
    public function removeSessionData($session_id);
}

接着我们编写实现SessionHandlerInterface,SessionSet的基础实现类(Session)

namespace App\Library\Authorize;

abstract class Session implements \SessionHandlerInterface,SessionSet
{
    //保存单一的Session对象
    public static $instance = null;

    private function __construct(){}

    /**
     * 获取实际驱动对象
     */
    public static function getInstance($driver = 'MySql'){
        if(self::$instance == null) {
            $driver_model = ucfirst(strtolower($driver))."SessionDriver";
            $class = "\App\Library\Authorize\\Drivers\\".$driver_model;
            $uncheckClass = $class::getRealInstance();
            if(!$uncheckClass instanceof SessionSet){
                throw new SessionDriverException("非法驱动类");
            }
            self::$instance = $uncheckClass;
        }

        return self::$instance;
    }

    /**
     * 关闭 session
     */
    public function close()
    {
        return true;
    }

    /**
     * 删除 session
     */
    public function destroy($session_id)
    {
        return $this->removeSessionData($session_id);
    }

    /**
     * 清理旧 sessions
     */
    public function gc($maxlifetime)
    {
        return true;
    }

    /**
     * 初始化 session
     */
    public function open($save_path, $name)
    {
        return true;
    }

    /**
     * 读取session数据
     */
    public function read($session_id){
        $session = $this->getSessionData($session_id);
        $time = request()->time;
        //如果设定过期时间则判断是否过期
        if(empty($session) || $session->expires > 0 && $time > ($session->session_time + $session->expires)){
            return "";
        }
        return (string)$session->info;
    }


    /**
     * 保存session信息
     */
    public function write($session_id, $session_data)
    {
        $session = $this->getSessionData($session_id);
        $request = request();
        if(empty($session)) {
            //如果会话ID不存在则需要创建
            $expires_time = isset($request->authorizeData['expires_time']) ? $request->authorizeData['expires_time'] : 0;
            return $this->createSessionData($session_id,$session_data,$expires_time);
        }
        return $this->setSessionData($session_id,$session_data);
    }

 

基础类Session实现了SessionHandlerInterface,SessionSet接口,该类负责Session的写入以及读取等其他操作。仅有基础类的功能还不够,我们还需要编写将会话数据持久化到mysql的驱动类。
新建一个驱动类MysqlSessionDriver,该类继承Session基础类,并实现相关的方法。

namespace App\Library\Authorize\Drivers;
use App\Library\Authorize\Session;
use App\Session as SessionModel;

class MysqlSessionDriver extends Session
{

    public $model = null;

    public static $instance = null;

    private function __construct()
    {
       //该模型为laravel框架的Model,有关该模型的使用请查阅laravel官方相关文档
       $this->model = new SessionModel();
    }

    /**
     * 获取单例对象
     */
    public static function getRealInstance()
    {
        if(self::$instance == null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 根据Session_ID获取一条Session数据记录
     */
    public function getSessionData($session_id)
    {
        return $this->model->newQuery()->where([
            'session_id' => $session_id
        ])->first();
    }

    /**
     * 创建一条session数据
     */
    public function createSessionData($session_id, $data = "", $expires_time = 0)
    {
             $insert_data = [
                "session_id" => $session_id,
                'session_time' => request()->time,
                'info' => $data, //会话数据保存到这里
                'expires' => $expires //过期时间
            ];
            $rs = $this->newQuery()->insert($insert_data);
            return $rs;
    }

    /**
     * 删除session数据
     */
    public function removeSessionData($session_id)
    {
        $r = $this->model->newQuery()->where(['session_id'=>$session_id])->delete();
        if($r !== FALSE){
            return true;
        }

        return false;
    }

    /**
     * 保存session数据
     */
    public function setSessionData($session_id, $data = "")
    {
        $model = $this->newQuery()->where("session_id",$session_id)->first();
       if(empty($model)) {
           return false;
       }
       $model->info = $data;
       $model->session_time = request()->time;
       return $model->save();
    }
}

编写好驱动类之后,我们就建立了会话与Mysql之间的联系,php会根据你在session_set_save_hanlder中设置的处理类来进行session操作,开发者只需要在业务代码中操作Session,数据就只自动保存到Mysql中,如果不存在则会创建一条记录。

总结

 

session_set_save_hanlder能够帮助我们编写自己的持久化会话数据的处理程序,这样我们就能编写程序,将会话数据保存到mysql,redis等持久化服务当中,能真正解决多台服务器部署应用后会话信息不一致的问题。

SessionHandlerInterface接口除了read方法和write方法,还有其他几种方法,我们可以根据不同的业务需求进行编写,例如SessionHandlerInterface::gc()方法,该方法可以实现清除过期的session数据,当session_start()方法被调用的时候触发该方法。更多使用方法可以查阅php官方文档获知:

优化

虽然这样的方案很好的解决了均衡负载会话共享的问题,但是当数据量剧增,I/O的压力也随之增大,导致Mysql查询缓慢,因此我们可以编写redis驱动来替代mysql,更有利于系统的运行。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/460378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Superset整合keycloak系统

本篇主要介绍superset如何整合单点登陆系统keycloak,现在网上的博客大部分都是失效了,这里我相当于更新一下,避免大家再走弯路 一、环境配置 Macos keycloak:18.0.0 superset:2.1.0 keycloak规定:每一…

原理这就是索引下推呀

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 索引下推是之前面试的时候遇到的一个面试题,当时没有答上来,今天来学习一下。 介绍索引下推之前先看一下MySQL基…

2022年NOC大赛创客智慧编程赛道图形化scratch复赛题,包含答案解析

目录 2022 年 NOC 大赛创客智慧编程图形化复赛用题 下载文档打印做题:

ArcGIS Pro、Python、USLE、INVEST模型等多技术融合的生态系统服务构建生态安全格局

第一章、生态安全评价理论及方法介绍 一、生态安全评价简介 ​ 二、生态服务能力简介 ​ 三、生态安全格局构建研究方法简介 ​ 第二章、平台基础一、ArcGIS Pro介绍1. ArcGIS Pro简介2. ArcGIS Pro基础3. ArcGIS Pro数据编辑4. ArcGIS Pro空间分析5. 模型构建器6. ArcGIS Pro…

命令执行的判断根据:;、、

文章目录 命令执行的判断根据:;、&&、||cmd ; cmd(不考虑命令相关性的连续命令执行)$?(命令返回值)与&&或||使用案例例题 命令执行的判断根据:;、&&、|| 在某些情况下,我想要一次执行很多命令,而不想分…

【jvm系列-10】深入理解jvm垃圾回收器的种类以及内部的执行原理

JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…

JavaScript字符串操作

ASCII(as key) 字符集(了解) 我们都知道,计算机只能存储 0101010 这样的二进制数字,那么我们的 a ~ z / A ~ Z / $ / /… 之类的内容也有由二进制数字组成的 我们可以简单的理解为, a ~ z / …

浅谈文心一言

作者简介: 辭七七,目前大一,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: 七七的闲谈 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖&#x1f…

[oeasy]python0140_导入_import_from_as_namespace_

导入import 回忆上次内容 上次学习了 tryexcept 注意要点 半角冒号缩进输出错误信息 有错就报告 不要隐瞒否则找不到出错位置还可以用traceback把 系统报错信息原样输出 但是代码量好多啊 10多 行了 🤯可以把他输入部分和输出部分么?🤔 我…

【计算机组成原理】第四章 指令系统

系列文章目录 第一章 计算系统概论 第二章 运算方法和运算器 第三章 多层次的存储器 第四章 指令系统 第五章 中央处理器 第六章 总线系统 第七章 外围设备 第八章 输入输出系统 文章目录 系列文章目录前言第四章 指令系统4.1 指令系统的发展与性能要求4.1.1 指令系统的发展4…

CTR-GCN 代码理解

论文代码:https://github.com/Uason-Chen/CTR-GCN ctrgcn.py 文件路径:CTR-GCN/model/ctrgcn.py import math import pdbimport numpy as np import torch import torch.nn as nn from torch.autograd import Variabledef import_class(name):compone…

动态设置图片的主题色(保留明暗关系)

github地址 PrimaryColorDemo 效果 原始图片 就是一张普通的png图片 根据选择的主题色动态渲染。 思考 最近在思考怎么实现动态的设置图片的主题色。不是那种渲染透明iocn。而是把图片的明暗关系保留。而改变其中的主题色。终于花了半天的时间研究出来了。和大家共享。 …

AI对话AI才是正解?KAUST研究团队提出基于角色扮演的大模型交互代理框架CAMEL

电影《盗梦空间》中有这样一句非常经典的台词: “世上最具有可塑性的寄生虫是什么?是人类的想法。人类大脑中一个简单的想法,就可以建立一座庞大的城市。有时一个想法也可以改变世界,并改写一切规则,这就是我为什么要…

0703齐次方程-微分方程

文章目录 1 定义和解法1.1 定义1.2 微分方程中的变量替换1.3 齐次方程的解法 2 例题结语 1 定义和解法 1.1 定义 形式上可化为 d y d x g ( y x ) \frac{dy}{dx}g(\frac{y}{x}) dxdy​g(xy​)的方程,称为齐次方程。 例如 d y d x y x tan ⁡ y x , d y d x e y…

操作系统考试复习—第二章 2.1 2.2程序和进程的描述

第二章 进程的描述与控制 程序:有序的指令集合 程序顺序执行的特征:1.顺序性 2.封闭性 3.可再现性(确定性) 在多道程序环境下,允许多个程序并发执行,此时他们将失去封闭性,并具有间断性和不可再现性的特征。为此引…

net::ERR_CONTENT_LENGTH_MISMATCH 206 (Partial Content) 报错

一、问题描述 最近现场实施人员反馈有个功能不能正常使用,F12查看浏览器的控制台,提示net::ERR_CONTENT_LENGTH_MISMATCH 206 (Partial Content)的错误, HTTP状态码206表示“部分内容”(Partial Content),…

全网最细,Jmeter性能测试-分布式压力测试环境搭建(超详细)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 JMeter设计之初是…

【Redis】不卡壳的 Redis 学习之路:从十大数据类型开始入手

目录 类型 String 字符串 List 列表 Set 集合 Sorted Set /ZSet 有序集合 Hash 哈希表 GEO 地理空间 HyperLogLog 基数统计 Bitmap 位图 BitField 位域 Stream 流 线上测试地址 常用命令 key 操作指令 String 操作指令 List 操作指令 Set 操作指令 ZSet 操作…

STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期

STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期 目录 STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期1、介绍2、STM32CubeMX配置2.1 基本配置2.2 PWM输出配置2.3 PWM输入捕获配置 3、程序修改和测试 本篇博客以STM32F103C8为例,其他…

selenium_交互 (谷歌浏览器驱动下载 xpath插件安装)

安装selenium (1)查看谷歌浏览器版本 谷歌浏览器右上角 ‐‐> 帮助 ‐‐> 关于 查看 浏览器版本: (2)操作谷歌浏览器驱动下载地址 http : // chromedriver . storage . googleapis . com / index . html 找到…