redissession共享(基于redis的session共享)
WebSocket Session共享
最近在做消息中心模块,想要实现消息实时推送到前端页面展示,直接摒弃了前端定时轮训调用接口来获取消息数据的方式,采用了WebSocket服务端推送。
流程是首先前端跟后端应用新建一个连接,并携带当前登录的用户ID,此时WebSocket会创建一个WebsocketSession来唯一绑定该连接,我们会在后端用Map建立用户ID与Session的映射关系:
后续有新消息到达时,就可以通过该Map映射找到指定用户ID对应的session来推送消息。但有一个问题,后端是多应用节点,每个节点维护一个这样的Map, 无法共享WebsocketSession ,而且 redis也无法对WebsocketSession序列化后进行存储 。
由于项目目前用到了Redis,所以可以 采用Redis的发布/订阅功能来实现WebsocketSession共享问题。
1.新建一个对象,属性有userId, message,用于发送消息
2. 当新消息到达时,将消息注册到redis指定topic的频道上
3.每个应用节点都订阅该topic的频道,这样新消息一注册,每个节点都能接收到Object,然后从Object中获取userId,再从映射Map中获取userId对应的WebsocketSession(在哪个节点建立的连接和Map映射关系,就会在哪个节点找到对应的session),进行消息推送。
就这样通过Redis的发布/订阅功能实现session共享。当然在步骤2,新消息到达时,可以先在本节点的Map映射中查找是否有userId对应的session,如果有,直接推送消息,而且不必要再将消息注册到redis中。
spring boot + redis 实现session共享分析
HttpSession是由servelet容器进行管理的。而我们常用的应用容器有 Tomcat/Jetty等, 这些容器的HttpSession都是存放在对应的应用容器的内存中,在分布式集群的环境下,通常我们使用Nginx或者LVS、Zuul等进行反向代理和负载均衡,因此用户请求是由一组提供相同服务的应用来进行处理,而用户最终请求到的服务由Nginx和LVS、Zuul进行确定。
那么问题就来了,我们怎样保证多个相同的应用共享同一份session数据?对于这种问题Spring为我们提供了Spring Session进行管理我们的HttpSession。项目地址:
1.添加Spring session的包,而Spring session 是将HttpSession存放在Redis中,因此需要添加Redis的包。我们这里是用了Spring boot进行配置Rdies。
2.使用@EnableRedisHttpSession注解进行配置启用使用Spring session。
3.配置我们的Redis链接,我们这里使用的是Spring Boot作为基础进行配置,因此我们只需要在YML或者Properties配置文件添加Redis的配置即可。
4.创建请求的控制器来进行确定我们是否启用Session 共享。
5.将当前的工程拷贝一份.
通过上面请求显示的结果我们可以看出使用的是同一个Seesion,我们也可以查看下存在Redis中的Session。我这里使用RDM进行查看,我们还可以查看Session的属性。从图可以看出我们存进入的url属性。
我们从启动Spring Session的配置注解@EnableRedisHttpSession开始。
1.我们可以通过@EnableRedisHttpSession可以知道,Spring Session是通过RedisHttpSessionConfiguration类进行配置的。
2.我们在RedisHttpSessionConfiguration类种的注释可以知道,该类是用于创建一个过滤SessionRepositoryFilter。
3.探究下SessionRepositoryFilter类是在哪里创建\创建过程\作用。
(1)哪里创建:
通过搜索RedisHttpSessionConfiguration发现SessionRepositoryFilter的创建不是在RedisHttpSessionConfiguration,而是在父类SpringHttpSessionConfiguration中创建。
(2)SessionRepositoryFilter创建过程:
这里我们可以总结下:
Redis确保链接的情况下。
1.创建sessionRedisTemplate
2.创建RedisOperationsSessionRepository
3.创建SessionRepositoryFilter
(3)SessionRepositoryFilter的作用:
SessionRepositoryFilter的主要作用接管Seession的管理。我们可以从下面几个点知道为什么?
4.我们研究下SessionRepositoryRequestWrapper是怎样接管Session?
(1)存储Session的过程
当调用SessionRepositoryFilter.this.sessionRepository.save(session)完毕后,会判断当前的SessionId是否与请求的中的Cookie中SessionId一致,若不一致的情况下会调用onNewSession()方法,我们可以通过SpringHttpSessionConfiguration配置类的可以看到使用的是
CookieHttpSessionStrategy();
从CookieHttpSessionStrategy.onNewSession()方法可以看到是将SessionId写到Cookie中。
(2)获取Session的过程
我们根据源码的分析可以知道:
1.Spring Session 是通过SessionRepositoryFilter过滤器进行拦截,然后通过SessionRepositoryRequestWrapper继承HttpServletRequestWrapper进行管理Session。
2.Spring Session 为我们提供了3中存放的策略而每种策略提供对应的注解启动。分别为:
(1)NoSql形式的MongoDb:@EnableMongoHttpSession
(2)持久化形式的JDBC:@EnableJdbcHttpSession
(3)缓存形式的Redis:@EnableRedisHttpSession
3.Spring Session 共享Session过程:
(1)先过程过滤器存储将SessionID存放到本地的Cookie 和Redis中。
如果本地没有启用Cookie的情况下,Spring Session也就不能使用。
(2)获取Session的时候,先从请求中获取Session,Session不为空的情况下直接返回Session,若当前的Session为空的情况下,从Cookie中获取SessionId,判断SessionId不为空,再从Redis中获取Session,若从Redis中获取到的Session不为空将Session存放到请求中,再返回Session,如果从Redis中获取的Session为空,再创建新的Session并且添加到请求中,后返回Session。
PHP实现负载均衡session共享redis缓存操作示例
本文实例讲述了PHP实现负载均衡session共享redis缓存操作。分享给大家供大家参考,具体如下:
1、首先先创建html表单页面
meta
chatset='utf-8'
center
form
action="se.php"
method="post"
table
tr
td帐号:/td
tdinput
type="text"
name="username"/td
/tr
tr
td密码:/td
tdinput
type="password"
name="pwd"/td
/tr
tr
td/td
tdinput
type="submit"
value="登录"/td
/tr
/table
/form
/center
2、创建接受表单的文件
?php
header('content-type:text/html;charset=utf-8');
set_time_limit(10);
ini_set("session.save_handler",'redis');//开启php.ini中的redis配置
ini_set("session.save_path","tcp://192.168.1.70:6379");//第一台服务器的redis
session_start();//开启session
$username
=
$_POST['username'];
$_SESSION['username']
=
$username;
echo
"scriptalert('登录成功!');location.href='from.php'/script";//登录成功后跳转到欢迎登录页面
?
3、跳转到from.php去判断第一台服务器的redis中的session是否存到了本台服务器的session中
?php
header('content-type:text/html;charset=utf-8');
set_time_limit(10);
ini_set("session.save_handler",'redis');//开启php.ini中的redis配置
ini_set("session.save_path","tcp://192.168.1.70:6379");//第一台服务器的redis
session_start();//开启session
$username
=
isset($_SESSION['username'])
?
$_SESSION['username']
:
'';//判断当前是否存在session
//$id
=
$_SESSION['PHPSESSID'];
//echo
$id;
if(empty($username)){
echo
"scriptalert('请重新登录!');location.href='index.php'/script";
}else{
echo
"欢迎".$username."登录";
}
?
这样就简单了实现了redis
session共享的功能,要测试的话需要两台服务器,建议使用linux
比较好用
linux上安装redis可参考《Linux平台安装redis及redis扩展的方法》
更多关于PHP相关内容感兴趣的读者可查看本站专题:《php缓存技术总结》、《PHP数组(Array)操作技巧大全》、《php字符串(string)用法总结》、《PHP错误与异常处理方法总结》、《php面向对象程序设计入门教程》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》
希望本文所述对大家PHP程序设计有所帮助。
您可能感兴趣的文章:Nginx
安装笔记(含PHP支持、虚拟主机、反向代理负载均衡)PHP开发负载均衡指南PHP实现负载均衡下的session共用功能Thinkphp结合AJAX长轮询实现PC与APP推送详解PHP经典算法集锦【经典收藏】php
分库分表hash算法php的hash算法介绍PHP中对各种加密算法、Hash算法的速度测试对比代码PHP实现的一致性Hash算法详解【分布式算法】PHP实现负载均衡的加权轮询方法分析
redis实现session共享的一些细节
如果仅仅是写demo,对于sprintboot项目,只要在启动类加上@EnableRedisHttpSession注解就可以实现session共享(参考网上教程),但是,如果企业项目,还有很多细节需要考虑。
每一个会话在redis中对应3个key
其中spring:session:sessions:32d0d3b2-0f04-469b-a917-3a55e60f7393存放的是具体内容,sessionAttr开头的field与setAttribute()一一对应
第一次创建SESSION时(一般是登录的时候),后端把sessionId set-cookie到前端
之后的请求,前端把cookie中的SESSION传到后端,后端根据cookie识别session
后端的接口一般会有token认证机制(jwt是常见的token实现方案),token会有一定的有效期,如果token过期,后端返回401状态码(unauthorized),前端的公共js方法看到状态码为401 ,提示用户“登陆已过期”并跳转到登录页。
假设token有效期默认为30分钟,用户A登录后生成一个token,30分钟内用户不停的操作,这种情况下,假如token依然过期,提示用户“登陆已过期”并跳转到登录页,用户体验就会非常差。
因此,token通常会有自动续期的机制,每次用户调用接口时,把redis中该token的ttl重置为30min。
假设token的有效期比session的长,session过期了但是token没过期,那么用户仍处于登录状态,这时如果调用一些需要从session取数据的接口,就会有问题。
因此,session的有效期,至少要跟token一样长,但是token有自动续期机制,所以session也要有自动续期机制。
经测试,springboot项目,使用redis实现session共享,session的有效期默认为2100s,即35分钟,并且,springboot已经实现了自动续期,每次访问session(getSession或者存取数据),都会把ttl重置为2100s。
看起来已经完美了,其实还有问题。
假如用户35min内的操作,都不涉及session,那么session就会过期,但是token依然没过期,还是会有问题。
解决方案是:每次token校验成功后,调用一次getSession(false)方法,重置session的ttl。
如果某一次请求时,后端创建了新的session,就会把新的sessionId set-cookie到前端,之后前端发起请求时,cookie会带上新的sessionId,后端也根据这个新的sessionId寻找会话。
但是,这个新的sessionId并没有对应的内容(一般只会在登录的时候,把用户信息等内容set到session)。
因此,仅当登录的时候,允许创建新的session,其余的地方,如果需要获取session,需要用getSession(false),false表示不创建新session,若session为空返回null。
注意:getSession()等价于getSession(true),在没有session时,会创建新session。
如果是通过session的setAttribute()和getAttribute() api实现数据存取,springboot会帮我们实现序列化和反序列化,但是,假如我们需要自己实现数据存取(比如我们是开发人员,想要查看用户session里的信息,就需要自己实现反序列化),该怎么办?
以获取session中的内容为例,session的内容在redis中以hash格式存放,而redis对该hash的value使用的serializer是JdkSerializationRedisSerializer,因此,如果要把session的内容(字节数组)转化为java对象(即反序列化过程),需要设置serializer为JdkSerializationRedisSerializer。
每次执行session.setAttribute(),并不会马上把数据写到redis,而是先写到本地内存缓存,等本次请求结束后,再写到redis。
path属性为glcs,代表请求路径需要包含glcs,才会把该cookie带到后端;
httponly属性,表示该cookie无法通过js读取/修改,比如document.cookie无法读取,只有发起http请求的时候才会自动带到后端
也可以使用EnableMongoHttpSession注解用MongoDB来管理session