之前写过一篇nginx多tomcat负载均衡,主要记录了使用nginx对多个tomcat 进行负载均衡,其实进行负载均衡之前还有一个问题没有解决,那就是集群间的session共享,不然用户在登录网站之后session保存在tomcat A,但是下次访问的时候nginx分发到了tomcat B,这个时候tomcat B没有刚刚用户登录的session,所以用户就失去了(本次)登录状态,下次访问的时候nginx可能又分发到了tomcat A(其实通过配置可以给各个服务器分配权重,nginx根据权重来转发到对应的服务器),用户本次又是登录的状态了,这样飘忽不定肯定是不行的,所以在进行集群负载均衡之前需要解决session共享的问题。

目录

  • 概述:简述本次记录的主要内容
  • shiro的session:关于shiro的session管理
  • 实现共享
  • 总结

概述

因为项目中用到了shiro的权限控制,而且使用的是shiro的session,所以我就基于shiro的session管理基础上对session进行多tomcat共享,共享的思路也很简单,就是将session保存到数据库,每个服务器在收到客户端请求的时候都从数据库中取,这样就统一了多个服务器之间的session来源,实现了共享。只不过这里我使用的数据库是redis。

shiro的session

之前在另外一篇博客(shiro实现APP、web统一登录认证和权限管理)里面也提到了shiro的session问题,其实shiro的session只不过是基于认证的需要对tomcat的session进行了封装,所以只要实现对shiro的session进行持久化就可以了,关于shiro的session管理,开涛老师的这一篇博客讲得很清楚了(http://jinnianshilongnian.iteye.com/blog/2028675),可以参考这一篇博客来了解shiro对session的管理。

实现共享

在明白了shiro的session管理之后,我们就可以在此基础上进行session的共享了,其实只需要继承EnterpriseCacheSessionDAO(其实继承CachingSessionDAO就可以了,但是这里考虑到集群中每次都访问数据库导致开销过大,这里在本地使用ehcache进行缓存,每次读取session的时候都先尝试读取本地ehcache缓存,没有的话再去远程redis数据库中读取),然后覆盖原来的增删改查操作,这样多个服务器就共享了session,具体实现如下:

  1. import java.io.ByteArrayInputStream;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.io.Serializable;
  7.  
  8. import org.apache.shiro.session.Session;
  9. import org.apache.shiro.session.mgt.SimpleSession;
  10. import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
  11.  
  12. public class SessionRedisDao extends EnterpriseCacheSessionDAO {
  13.  
  14. // 创建session,保存到数据库
  15. @Override
  16. protected Serializable doCreate(Session session) {
  17. Serializable sessionId = super.doCreate(session);
  18. RedisDb.setObject(sessionId.toString().getBytes(), sessionToByte(session));
  19.  
  20. return sessionId;
  21. }
  22.  
  23. // 获取session
  24. @Override
  25. protected Session doReadSession(Serializable sessionId) {
  26. // 先从缓存中获取session,如果没有再去数据库中获取
  27. Session session = super.doReadSession(sessionId);
  28. if(session == null){
  29. byte[] bytes = RedisDb.getObject(sessionId.toString().getBytes());
  30. if(bytes != null && bytes.length > 0){
  31. session = byteToSession(bytes);
  32. }
  33. }
  34. return session;
  35. }
  36.  
  37. // 更新session的最后一次访问时间
  38. @Override
  39. protected void doUpdate(Session session) {
  40. super.doUpdate(session);
  41. RedisDb.setObject(session.getId().toString().getBytes(), sessionToByte(session));
  42. }
  43.  
  44. // 删除session
  45. @Override
  46. protected void doDelete(Session session) {
  47. super.doDelete(session);
  48. RedisDb.delString(session.getId() + "");
  49. }
  50.  
  51. // 把session对象转化为byte保存到redis中
  52. public byte[] sessionToByte(Session session){
  53. ByteArrayOutputStream bo = new ByteArrayOutputStream();
  54. byte[] bytes = null;
  55. try {
  56. ObjectOutputStream oo = new ObjectOutputStream(bo);
  57. oo.writeObject(session);
  58. bytes = bo.toByteArray();
  59. } catch (IOException e) {
  60. e.printStackTrace();
  61. }
  62. return bytes;
  63. }
  64.  
  65. // 把byte还原为session
  66. public Session byteToSession(byte[] bytes){
  67. ByteArrayInputStream bi = new ByteArrayInputStream(bytes);
  68. ObjectInputStream in;
  69. SimpleSession session = null;
  70. try {
  71. in = new ObjectInputStream(bi);
  72. session = (SimpleSession) in.readObject();
  73. } catch (ClassNotFoundException e) {
  74. e.printStackTrace();
  75. } catch (IOException e) {
  76. e.printStackTrace();
  77. }
  78.  
  79. return session;
  80. }
  81.  
  82. }

上面的主要逻辑是实现session的管理,下面是和redis数据库交互

  1. import java.util.Arrays;
  2. import java.util.Date;
  3. import java.util.Set;
  4.  
  5. import redis.clients.jedis.Jedis;
  6. import redis.clients.jedis.JedisPool;
  7. import redis.clients.jedis.JedisPoolConfig;
  8.  
  9. public class RedisDb {
  10. private static JedisPool jedisPool;
  11. // session 在redis过期时间是30分钟30*60
  12. private static int expireTime = 1800;
  13. // 计数器的过期时间默认2天
  14. private static int countExpireTime = 2*24*3600;
  15. private static String password = "123456";
  16. private static String redisIp = "10.10.31.149";
  17. private static int redisPort = 6379;
  18. private static int maxActive = 200;
  19. private static int maxIdle = 200;
  20. private static long maxWait = 5000;
  21. private static Logger logger = Logger.getLogger(RedisDb.class);
  22.  
  23. static {
  24. initPool();
  25. }
  26. // 初始化连接池
  27. public static void initPool(){
  28. JedisPoolConfig config = new JedisPoolConfig();
  29. config.setMaxTotal(maxActive);
  30. config.setMaxIdle(maxIdle);
  31. config.setMaxWaitMillis(maxWait);
  32. config.setTestOnBorrow(false);
  33.  
  34. jedisPool = new JedisPool(config, redisIp, redisPort, 10000, password);
  35. }
  36. // 从连接池获取redis连接
  37. public static Jedis getJedis(){
  38. Jedis jedis = null;
  39. try{
  40. jedis = jedisPool.getResource();
  41. // jedis.auth(password);
  42. } catch(Exception e){
  43. ExceptionCapture.logError(e);
  44. }
  45.  
  46. return jedis;
  47. }
  48. // 回收redis连接
  49. public static void recycleJedis(Jedis jedis){
  50. if(jedis != null){
  51. try{
  52. jedis.close();
  53. } catch(Exception e){
  54. ExceptionCapture.logError(e);
  55. }
  56. }
  57. }
  58. // 保存字符串数据
  59. public static void setString(String key, String value){
  60. Jedis jedis = getJedis();
  61. if(jedis != null){
  62. try{
  63. jedis.set(key, value);
  64. } catch(Exception e){
  65. ExceptionCapture.logError(e);
  66. } finally{
  67. recycleJedis(jedis);
  68. }
  69. }
  70.  
  71. }
  72. // 获取字符串类型的数据
  73. public static String getString(String key){
  74. Jedis jedis = getJedis();
  75. String result = "";
  76. if(jedis != null){
  77. try{
  78. result = jedis.get(key);
  79. }catch(Exception e){
  80. ExceptionCapture.logError(e);
  81. } finally{
  82. recycleJedis(jedis);
  83. }
  84. }
  85.  
  86. return result;
  87. }
  88. // 删除字符串数据
  89. public static void delString(String key){
  90. Jedis jedis = getJedis();
  91. if(jedis != null){
  92. try{
  93. jedis.del(key);
  94. }catch(Exception e){
  95. ExceptionCapture.logError(e);
  96. } finally{
  97. recycleJedis(jedis);
  98. }
  99. }
  100. }
  101. // 保存byte类型数据
  102. public static void setObject(byte[] key, byte[] value){
  103. Jedis jedis = getJedis();
  104. String result = "";
  105. if(jedis != null){
  106. try{
  107. if(!jedis.exists(key)){
  108. jedis.set(key, value);
  109. }
  110. // redis中session过期时间
  111. jedis.expire(key, expireTime);
  112. } catch(Exception e){
  113. ExceptionCapture.logError(e);
  114. } finally{
  115. recycleJedis(jedis);
  116. }
  117. }
  118. }
  119. // 获取byte类型数据
  120. public static byte[] getObject(byte[] key){
  121. Jedis jedis = getJedis();
  122. byte[] bytes = null;
  123. if(jedis != null){
  124. try{
  125. bytes = jedis.get(key);;
  126. }catch(Exception e){
  127. ExceptionCapture.logError(e);
  128. } finally{
  129. recycleJedis(jedis);
  130. }
  131. }
  132. return bytes;
  133.  
  134. }
  135.  
  136. // 更新byte类型的数据,主要更新过期时间
  137. public static void updateObject(byte[] key){
  138. Jedis jedis = getJedis();
  139. if(jedis != null){
  140. try{
  141. // redis中session过期时间
  142. jedis.expire(key, expireTime);
  143. }catch(Exception e){
  144. ExceptionCapture.logError(e);
  145. } finally{
  146. recycleJedis(jedis);
  147. }
  148. }
  149.  
  150. }
  151.  
  152. // key对应的整数value加1
  153. public static void inc(String key){
  154. Jedis jedis = getJedis();
  155. if(jedis != null){
  156. try{
  157. if(!jedis.exists(key)){
  158. jedis.set(key, "1");
  159. jedis.expire(key, countExpireTime);
  160. } else {
  161. // 加1
  162. jedis.incr(key);
  163. }
  164. }catch(Exception e){
  165. ExceptionCapture.logError(e);
  166. } finally{
  167. recycleJedis(jedis);
  168. }
  169. }
  170. }
  171.  
  172. // 获取所有keys
  173. public static Set<String> getAllKeys(String pattern){
  174. Jedis jedis = getJedis();
  175. if(jedis != null){
  176. try{
  177. return jedis.keys(pattern);
  178. }catch(Exception e){
  179. ExceptionCapture.logError(e);
  180. } finally{
  181. recycleJedis(jedis);
  182. }
  183. }
  184. return null;
  185. }
  186.  
  187. }

总结

这里只是实现了简单的session共享,但是对session的管理还不够全面,比如说session的验证。其实通过tomcat容器本身就可以实现session共享,后面再详细了解下tomcat对于session的管理。

使用redis进行基于shiro的session集群共享的更多相关文章

  1. Springboot Session集群处理

    在集群环境下,常见的基于Session的身份认证就会有一个问题,因为Session是跟着服务器走的,当用户在服务器1登陆成功后,当用户在访问服务器2的时候会因为服务器2没有用户的身份信息而再次跳转到认 ...

  2. Tomcat:基于Apache+Tomcat的集群搭建

    根据Tomcat的官方文档说明可以知道,使用Tomcat配置集群需要与其它Web Server配合使用才可以完成,典型的有Apache和IIS. 这里就使用Apache+Tomcat方式来完成基于To ...

  3. redis 5.0.3 讲解、集群搭建

    REDIS 一 .redis 介绍 不管你是从事Python.Java.Go.PHP.Ruby等等... Redis都应该是一个比较熟悉的中间件.而大部分经常写业务代码的程序员,实际工作中或许只用到了 ...

  4. Redis安装(单机及各类集群,阿里云)

    Redis安装(单机及各类集群,阿里云) 前言 上周,我朋友突然悄悄咪咪地指着手机上的一篇博客说,这是你的博客吧.我看了一眼,是之前发布的<Rabbit安装(单机及集群,阿里云>.我朋友很 ...

  5. Redis——(主从复制、哨兵模式、集群)的部署及搭建

    Redis--(主从复制.哨兵模式.集群)的部署及搭建 重点: 主从复制:主从复制是高可用redis的基础,主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复. 哨兵和集群都是 ...

  6. Redis系列5:深入分析Cluster 集群模式

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) 1 背景 前面我们 ...

  7. 基于Kubernetes的WAF集群介绍

    Kubernetes是Google开源的容器集群管理系统.它构建Docker技术之上,为容器化的应用提供资源调度.部署运行.服务发现.扩容缩容等整一套功能,可看作是基于容器技术的PaaS平台. 本文旨 ...

  8. Tomcat session集群

    author:JevonWei 版权声明:原创作品 环境 tomcatA 172.16.253.108 tomcatB 172.16.253.105 代理服务器 172.16.253.191 Tomc ...

  9. 基于zookeeper的Swarm集群搭建

    简介 Swarm:docker原生的集群管理工具,将一组docker主机作为一个虚拟的docker主机来管理. 对客户端而言,Swarm集群就像是另一台普通的docker主机. Swarm集群中的每台 ...

随机推荐

  1. h5 调起ios数字键盘的坑,限制特殊字符输入方案

    最近有个需求是利率只允许输入数字和小数点,用以下 <input type="number" pattern="[0-9]*"> 在ios会调起数字键 ...

  2. linux从0开始----01

    1.VMware 虚拟机安装与卸载 推荐安装较高版本,11.x以后的.本课程安装12.x版本,需要序列号. 2.在vmware中安装centos客户机.初学者选择典型安装也可以. 1.vware文件菜 ...

  3. 消除blur属性的边框

    直接设置样式为:  outline:none <!DOCTYPE html> <html lang="en"> <head> <meta ...

  4. DNS: Internet’s Directory

    关于DNS 互联网上几乎一切活动都以DNS请求开始.DNS(Domain Name System)是Internet的目录.访问URL时,设备所要做的第一件事就是询问目录,根据域名查出IP地址. 查询 ...

  5. HTML标签的绝对路径和相对路径

    我在javaweb中写json的Demo的时候遇到了这个问题,图片一一直取不出来,查了好久终于解决了,所以现在记录一下. 绝对路径: 其实很容易理解,如果你是一个普通的项目,那就是它在你电脑里真实存在 ...

  6. 二进制安装mysql5.7

    1.创建用户和组 groupadd mysql useradd -r -g mysql mysql 2.上传二进制包至/usr/local下解压并改名为mysql tar zxvf mysql-5.7 ...

  7. day_7数据类型的相互转换,与字符编码

    首先复一下昨天的内容 1:深浅拷贝 1:值拷贝 直接赋值 列表1=列表2       列表1中的任何值发生改变,列表2中的值都会随之改变 2:浅拷贝,列表2=列表1  列表1中存放的值的地址没有改变, ...

  8. Git .gitignore文件说明

    参见:https://book.git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E8%AE%B0%E5%BD%95%E6%AF%8F%E6%AC%A1%E ...

  9. “Xavier”安卓木马分析:可静默收集数据并远程代码执行

    趋势科技研究发现了一款Android恶意木马——Xavier.在谷歌Play应用市场中,超过800款Android应用感染了该恶意木马,影响数百万Android用户.感染的应用范围覆盖图片编辑器,墙纸 ...

  10. wordpess设置回复可见

    easy2hide 是一个不错的隐藏部分内容,评论后可见的插件,可在插件安装后台搜索 easy2hide 在线安装,或者在此下载 easy2hide. 在编辑文章的时候,切换到html文本编辑模式 测 ...