Spring-Session 会话共享 -> 基于 Redis 集群,内附各大错误合集,包括配置,类寻找不到、连接错误等
⚠ 本文仅限于博客园阅读,不得转载
如需转载,请与博主联系,否则视为侵犯著作权
本文不会删除,会长期保留着,因此不必担心哪一天会找不到。
PS:此文技术点在两年内有效,如果采用更高级的框架,Jar 包,则此文仅作参考
以下为正文:
首先需要简单介绍一下 Session ,它是我们即将需要进行配置的基础对象,如果你已经认识了此对象,那么可滑动至示例阶段。
在各类应用程序,都会产生一个叫做 会话 的连接,而在Web项目中,它称之为 Session,此文以 Tomcat 9.0 为基础进行演示。
Session 的作用是为了记录用户的操作,状态以及相关数据;而通常的情况下,则是由容器(Tomcat或其它服务器)进行管理:如销毁,创建等操作。
但是这存在一个缺陷:如果网站过大,需要多个服务器来支持网站的运行:用于支撑起数十万之巨的用户数量;那么单靠一个 Tomcat 是无法完成这个任务的,这个时候,只能通过 集群 来解决这个问题(负载均衡),而多几台机器就意味着需要有多个Tomcat来支持,那么这些 Tomcat 的会话可以共享吗?
答案是不能。每个容器都管理着各自的 Session。
需要解决的问题:例如某个用户在服务器A进行登录,其域名为 www.xxx.com/index,此时由于某个需求,需要跳转至另外的项目:www.xxx.com/container 中,假设二者是由不同的机器上的容器运行的,它意味着两个项目无法兼容,对于 container 项目来说,用户是没有登录的,则会产生一个新的会话。
问题的解决很简单,那便是 Session 会话共享:
就目前而言,已经有了数套方案可以实现。
- 使用容器插件,或者扩展来实现。
- Tomcat插件如 tomcat-redis-session-manager,当然还有其它(我没有进行实践,不过缺陷亦有:容器升级就需要重新配置)
- 自己编写一套 Session 会话管理类,需要读取用户会话时,在此类获取,而此类可以将其存放在 Redis 以保持Session状态,但开发周期可能会延长。
- (推荐)使用框架的会话管理,本文主要介绍的也是此类:即 Spring Session ,方案因为不依赖容器(Tomcat)亦不需要改动大量原有代码(低侵入性),在目前为止,都是各方面表现较好的 Session 会话共享解决方案。
至于 Spring Session 介绍,这篇博客就不占据过多篇幅来介绍:可以转到官网中查看。现在已经有了中文官网;不过官方的错误解决,以及依赖都没有太多的解释,导致网上博客有着各类错误解决方案....还都不尽相同,都不能够解决我的配置问题。
Spring Session 中文官网 -> 基于 1.3.4 版本(博主使用 1.3.1 版本)
写这篇文章之前,我是从零开始学习 Redis 集群会话共享技术,已经学习了四天,大致问题都已经解决了,而网上资料难以阅读,所以我决定发在博客上供大家参考。
Spring Session 示例:
大致分为四步,全部按照示例完成,就可以实现会话共享了 - > 这仅需改动一点原有代码。
1.导入 Jar 包,其中包括各项依赖性库,这些 Jar 包缺一不可
如果你是基于 Maven 进行版本管理的话,那么请自行根据以下 Jar 包添加依赖。
如果你仅是基于SSM,没有版本管理的话,请将以下 Jar 包载入你的 lib 目录下
- 在 Tomcat 中,此目录通常是:WEB-INF/lib
Jar 包依赖如下:
- commons-pool-1.6.jar
- commons-pool2-2.4.2.jar
- jedis-2.9.0.jar
- spring-data-commons-1.9.1.RELEASE.jar
- spring-data-redis-1.8.8.RELEASE.jar
- spring-session-1.3.1.RELEASE.jar
- spring-session-data-redis-1.3.2.RELEASE.jar
值得注意的是:版本号一定要对上,否则依赖上可能会出现错误。
2. 将 Jar 包环境准备就绪后,在 web.xml 文件中配置 SpringSessionRepositoryFilter 过滤器
<filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
【重要】请将此过滤器放置在其它过滤器之前。
3. 在 Spring 配置文件中配置一个 RedisHttpSessionConfiguration 类(此处默认项目已经可以正常访问并使用)
<!-- spring注解、bean的处理器 --> <context:annotation-config/> <!-- Spring session 的配置类 --> <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <!-- 指定session策略 --> <!--<property name="httpSessionStrategy" ref="httpSessionStrategy"/>--> </bean> <bean id="httpSessionStrategy" class="org.springframework.session.web.http.CookieHttpSessionStrategy"> <!-- 使用cookie策略 --> <property name="cookieSerializer" ref="cookieSerializer"/> </bean> <bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer"> <!-- 指定cookie的域名及路径:此处配置值得注意,可能将引发异常(domainName)--> <property name="cookiePath" value="/"/> <property name="domainName" value="localhost"/> </bean>
4.写入 Redis 连接配置 -> Spring redis 配置
PS:如果可以的话,建议采用属性配置文件形式来配置连接数据 -> redis.properties
但我急于测试,因此没有写属性配置文件,不过我在下方已顺便贴出配置文件
<!--读取redis.properties属性配置文件--> <context:property-placeholder location="classpath:redis.properties"/> <!-- 使用Spring配置JedisPoolConfig对象 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大空闲数 --> <property name="maxIdle" value="300" /> <!-- 最大连接数 --> <property name="maxTotal" value="100" /> <!-- 最大等待时间 --> <property name="maxWaitMillis" value="20000" /> </bean> <!-- 配置jedis连接工厂,用于连接redis --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1"/> <property name="port" value="6379"/> <property name="password" value="cloud2650"/> <property name="usePool" value="true"/> <property name="timeout" value="10000"/> </bean>
如果你没有采用硬编码的形式来导入配置,而是属性配置文件,那么请在 value 中写入:
value="${属性配置名称}"
属性配置文件如下:
redis.hostName=127.0.0.1 redis.port=6379 redis.password=123265 redis.usePool=true redis.timeout=10000
PS:【重要】网上有更全的配置,请根据实际情况而定。
完成了四大步骤,接下来就可以进入测试阶段了。
以下是两种域名情况:
www.xxx.com/项目一
www.xxx.com/项目二
值得注意的是:Spring Session 不支持根域名的会话共享,如:
项目一.xxx.com
项目二.xxx.com
好了,在实际开发中,你可能会遇到诸多问题,我猜你的项目肯定报错。
接下来我总结了九大错误,它们全都出现在我的项目里。。
它们对我厚爱有加,不过我觉得各自不同,可能有些项目没有报错呢。
错误类型,共如下(目前遇到):
- NoClassDefFoundError : 类找不到错误
- Jar 包 -> 通常是 commons-pool 包互相冲突
- 此错误会引发 NoClassDefFoundError
- redis 配置错误
- jedis 配置错误
- 读取超时:Read timed out
- enableRedisKeyspaceNotificationsInitializer 集群通知出错
- Cookie 错误
- 实例化 POJO(实体类)对象出错
- Http status 500 reids denied 拒绝服务
我们一个个来解决,请根据实际错误滑动以浏览。
类找不到错误通常是 jar 包的缺失而导致的,请转至示例中查看是否缺失某个 Jar 包
大致导入示例中的 Jar 包便会进入下一个问题: Jar 包冲突,这是因为 commons-pool两个版本不兼容的缘故。
我的Spring版本是 4.3.21,tomcat容器是 9.0.07
此时,应该改动以下两种配置:
(如是手动打包则跳过此步骤,仅适合IDE)
【重要】选中第二条:将应用发布到容器目录内,而不是IDE生成的副本容器
之后修改 Tomcat 配置文件,此时你应该明确知道程序是在哪个容器运行的,可能是副本,不过你选中第二个,则转到其路径下,并寻找 ->
apache-tomcat-9.0.17\conf\context.xml
新增一行配置代码如下:
<valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve"/> <manager className="com.radiadesign.catalina.session.RedisSessionManager" database="0" host="127.0.0.1" maxInactiveInterval="60" port="6379"/>
host 地址和其余子配置视实际情况而定
注意:时刻多看我的环境以及配置,指不定哪天官方更新,Tomcat 配置失效依旧会出现错误(以及Jar冲突出现的各种ClassNotFoundException):
java.lang.ClassNotFoundException: com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve
因为技术总是不断迭代,今年(2019)的Spring5版本我无法升级,总会出现错误,所以我的网站还没有使用最新的框架。
首要的两条错误解决后,即:类找不到和 Jar 包冲突错误,如果仍然出错,请继续往下阅读,我觉得应该是 redis 配置文件问题
此时的错误跟以下关键字有关:
enableRedisKeyspaceNotificationsInitializer
打开redis的conf配置文件中配置(redis.windows.conf):
按下快捷键来找到这一行配置:
notify-keyspace-events ""
将其修改为:
notify-keyspace-events Egx
【重要】此错误是集群通知错误,打开会占用一点CPU和内存容量。
所以,官方将其默认关闭了。
另外的配置错误:客户端携带了密码进行访问,导致无法连接。
错误代码为:
ERR Client sent AUTH, but no password is set
此时的错误,原因有很多种,我都总结下来了,如下:
【重要】请依次检查:
- 是否启动了 redis
- 是否配置文件中有空格或特殊符号
- 是否 redis 的配置端口有误
- 密码是否错误
- 连接池无连接对象可供使用
- 如是外网,配置 redis 的机器上是否有防火墙限制?
唔。。以上觉得没啥问题的话,请转到 redis 配置文件来检查是否启用了密码登录
打开 redis.windows.conf (其余系统请自行查看配置文件名称)文件:
使用 文本编辑工具 打开后,使用快捷键 Ctrl+F 找到此关键字:requirepass
将那一行的第一个符号:# ,删减掉,然后写入你在Spring配置中的密码。
如果错误代码为:Could not get a resource from the pool
此错误代码应该根据实际情况解决,例如服务器暂时挂起(长时间没有连接)等
其它各自看情况解决吧,如果是无连接对象使用,那么视情况增加连接对象:
当然,如无其他问题,也可能是服务器自行挂起,此时重启一下 redis 服务即可
若需增加连接对象请滑动至Spring Session示例 -> 第四步骤中修改对应的代码
读取超时错误,错误代码为:
java.net.SocketTimeoutException: Read timed out
此错误比较容易解决:重启网站应用以及 redis 服务器
错误发生原因:有一定几率为资源阻塞,增加线程池线程数量亦可(增加连接对象)
Cookie 错误:关键字带有 Cookie 二字,此时的网站应该可以正常访问,但数据异常。
错误的发生源自 Spring Session 示例第三步配置错误,请进行修改:
<property name="domainName" value="localhost"/>
【重要】value 中不能带有端口,例如 localhost:8080
PS:Session 会话都共享了,端口不必加上了。
POJO 对象实例化错误,错误关键字:
DefaultSerializer requires a Serializable payload but received an object of type [model.Admin]
错误的发生源自我没有好好看官方文档;
事实上,Spring 会将对象序列化后存入 Session ,然后将其导入至 redis 中,因此 Java 对象必须实现 Serializable 接口
大致代码如下:
public class User implements Serializable
Http status 500 reids denied 拒绝服务
通常来说,单机上难以遇见,不过我依旧碰到了。。解决方案网络上有很多种,我在此贴上网络方案中常见的两种:
1. 【推荐】修改 bind 地址 (redis配置文件中)
2. 关闭 protected mode
依旧打开 redis 配置文件:
绑定多个可信任IP即可,如:bind 192.168.1.210 xxx.xxx.xxx.xxx
中间使用空格隔开即可,当然亦可将其注释掉以信任所有IP访问。
第二种方案:
更改 protected-mode yes 为 protected-mode no
最后,重启一下 Redis 服务器以使其生效。
关于 Spring Session 会话共享就到这里了,我会长期更新(若遇到错误问题)
来自:https://www.cnblogs.com/chongsaid/
写于 2019/10/23,于惠州石湾。
博客:https://www.cnblogs.com/chongsaid/
转载权限:需取得博主意见,否则视为侵犯著作权