PHP 实现会话Session信息共享

目录

解决方案也有很多种: 

会话保持

会话复制

会话共享

环境准备

架构设计

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,更有利于系统的运行。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jetpack Compose之线性布局和帧布局

作者:海塔灯 概述 Compose 中的线性布局对应的是Android传统视图中的LinearLayout,不一样的地方是,Compose根据Orientation的不同又将布局分为Column和Row, Column对应传统视图LinearLayout中orientation “vertical”的情况,Row对应传统视…

【AI炼丹术】写深度学习代码的一些心得体会

写深度学习代码的一些心得体会 体会1体会2体会3总结内容来源 一般情况下,拿到一批数据之后,首先会根据任务先用领域内经典的Model作为baseline跑通,然后再在这个框架内加入自己设计的Model,微调代码以及修改一些超参数即可。总体流…

RocketMQ第一节(MQ的初步了解)

目录 1:什么是消息队列 2:MQ的基础模型 3:MQ的作用 3.1:MQ用来解耦 3.2: 削峰填谷 4:MQ怎么选 1:什么是消息队列 MQ全称是Message Queue (消息队列),是消息传输中间件&#xf…

huggingface下载的.arrow数据集读取与使用说明

1.数据下载方式:load_dataset 将数据集下载到本地:(此处下载的是一个物体目标检测的数据集) from datasets import load_dataset # 下载的数据集名称, model_name keremberke/plane-detection # 数据集保存的路径 save_path da…

mac十大必备软件排行榜 mac垃圾清理软件哪个好

刚拿到全新的mac电脑却不知道该怎么使用?首先应该装什么软件呢?如果你有同样的疑惑,今天这篇文章一定不要错过。接下来小编为大家介绍mac十大必备软件排行榜,以及mac垃圾清理软件哪个好。 一、mac十大必备软件排行榜 1.CleanMyM…

停车场管理系统的设计与实现_kaic

目 录 1 概 述 1.1研究背景 1.2研究现状 1.3研究内容 2 相关技术简介 2.1 JSP技术 2.2 JAVA技术 2.3 MYSQL数据库 2.4 B/S结构 3 系统需求分析 3.1 系统可行性分析 3.1.1 操作可行性 3.1.2 经济可行性 3.1.3 技术可行性 3.2 系统性能分析 3.3系统流程分析 3.3.1注册流程 3.3.…

智慧园区数字化转型下的移动App发展

随着智慧城市的建设和智慧园区的崛起,智慧园区数字一体化建设成为园区发展的重心,当然数字转型离不开移动应用的整合服务。 在过去的几年中,智慧园区移动应用已经发展成为园区管理和服务的重要手段之一,为企业和员工提供了更加便…

Machine Learning-Ex6(吴恩达课后习题)Support Vector Machines

目录 1. Support Vector Machines 1.1 Example Dataset 1 1.2 SVM with Gaussian Kernels 1.2.1 Gaussian Kernel 1.2.2 Example Dataset 2 1.2.3 Example Dataset 3 2. Spam Classification 2.1 Preprocessing Emails 2.1.1 Vocabulary List 2.2 Extracting Feature…

安卓GB28181-2022 RTP over TCP

使用TCP传输RTP包,GB28181-2016和GB28181-2022 都是按IETF RFC4571来的。使用TCP发送RTP包,前面加个16位无符号长度字段就好(网络字节序)。具体定义格式如下: 需要注意的是LENGTH值可以是0,0的话表示空包; 另外UDP传输RTP包&#…

第二届易派客工业品展圆满落幕 3天超7万人次观展

4月15日,第二届易派客工业品展览会在苏州国际博览中心成功闭幕,展会期间共7.4万人次观展。展会以“绿色•智造•融通•赋能”为主题,为参展企业衔接供需、共享商机、共促发展提供平台,推动工业企业数字化转型、致力供应链优化升级…

blast的-max_target_seqs?

Shah, N., Nute, M.G., Warnow, T., and Pop, M. (2018). Misunderstood parameter of NCBI BLAST impacts the correctness of bioinformatics workflows. Bioinformatics. 杂志Bioinformatics以letter to the editor的形式刊发了来自美国马里兰大学计算机系的Nidhi Shah等人…

powershell搞定烦人的Windows Defender

0x00 Windows Defender真烦 最近装了不少虚拟机,发现目前较新版本的windows Defender是真的烦,关了一段时间后,自己又打开。特别是装了域控后的winserver 2016,半都关不掉,做个实验是真烦。 顺手去查了下如何使用pow…

ThinkPHP模型操作上

ThinkPHP模型操作上 前言模型一、创建模型二、模型操作 总结 前言 在mvc架构中,模型的解释是写逻辑代码的地方,其实还可以这样理解,就是一串操作写在一个模型类中,就是你要完成某一项功能,将这个功能的代码写在一个mod…

2023年产业基金研究报告

第一章 行业概况 1.1 概述 产业基金,又称为产业投资基金,是一种由政府、企业、金融机构等出资设立的,专门用于支持和促进特定产业发展的投资基金。产业基金通常以股权投资和长期投资为主,旨在推动产业结构升级、促进科技创新、提…

基于ResNet-attention的负荷预测

一、attention机制 注意力模型最近几年在深度学习各个领域被广泛使用,无论是图像处理、语音识别还是自然语言处理的各种不同类型的任务中,都很容易遇到注意力模型的身影。从注意力模型的命名方式看,很明显其借鉴了人类的注意力机制。我们来看…

融云 CTO 岑裕:出海技术前沿探索和排「坑」实践

在本文中,你将看到以下内容: 全球通信网络在接入点、链路加速、服务商、协议等层面的动态演进; 进入到具体市场,禁运国、跨国拦截、区域一致性差等细节“坑点”如何应对; 融云如何从技术侧帮助开发者应对本地化用户体…

Hive与HBase的区别及应用场景

目录: 零、前言一、定义二、区别三、应用场景 零、前言 在学大数据分析的过程中,Hive和HBase是两个非常重要的内容,对于初学者而言容易混淆。所以比较两者区别,能够帮助我们对这两个组件有一个清晰的认识和定位。那么,…

一篇文章看懂MySQL的多表连接(包含左/右/全外连接)

MySQL的多表查询 这是第二次学习多表查询,关于左右连接还是不是很熟悉,因此重新看一下。小目标:一篇文章看懂多表查询!! 这篇博客是跟着宋红康老师学习的,点击此处查看视频,关于数据库我放在了…

大神们分享STM32的学习方法

单片机用处这么广,尤其是STM32生态这么火!如何快速上手学习呢? 第一:你要考虑的是,要用STM32实现什么 为什么使用STM32而不是8051? 是因为51的频率太低,无法满足计算需求?是51的管脚太少,无法…

云HIS(二级医院,乡镇医院,民营医院,标准化HIS医院信息管理系统源码)

传统 HIS(基于医院信息系统) 和云 HIS(基于云计算的医院信息系统)各有优缺点,选择哪种系统需要根据具体情况进行权衡。 传统 HIS 系统通常由医院自行开发和维护,适用于医院内部信息化程度较高、数据安全性…