使用redis进行基于shiro的session集群共享
之前写过一篇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,具体实现如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; public class SessionRedisDao extends EnterpriseCacheSessionDAO { // 创建session,保存到数据库
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = super.doCreate(session);
RedisDb.setObject(sessionId.toString().getBytes(), sessionToByte(session)); return sessionId;
} // 获取session
@Override
protected Session doReadSession(Serializable sessionId) {
// 先从缓存中获取session,如果没有再去数据库中获取
Session session = super.doReadSession(sessionId);
if(session == null){
byte[] bytes = RedisDb.getObject(sessionId.toString().getBytes());
if(bytes != null && bytes.length > 0){
session = byteToSession(bytes);
}
}
return session;
} // 更新session的最后一次访问时间
@Override
protected void doUpdate(Session session) {
super.doUpdate(session);
RedisDb.setObject(session.getId().toString().getBytes(), sessionToByte(session));
} // 删除session
@Override
protected void doDelete(Session session) {
super.doDelete(session);
RedisDb.delString(session.getId() + "");
} // 把session对象转化为byte保存到redis中
public byte[] sessionToByte(Session session){
ByteArrayOutputStream bo = new ByteArrayOutputStream();
byte[] bytes = null;
try {
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(session);
bytes = bo.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
} // 把byte还原为session
public Session byteToSession(byte[] bytes){
ByteArrayInputStream bi = new ByteArrayInputStream(bytes);
ObjectInputStream in;
SimpleSession session = null;
try {
in = new ObjectInputStream(bi);
session = (SimpleSession) in.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} return session;
} }
上面的主要逻辑是实现session的管理,下面是和redis数据库交互
import java.util.Arrays;
import java.util.Date;
import java.util.Set; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; public class RedisDb {
private static JedisPool jedisPool;
// session 在redis过期时间是30分钟30*60
private static int expireTime = 1800;
// 计数器的过期时间默认2天
private static int countExpireTime = 2*24*3600;
private static String password = "123456";
private static String redisIp = "10.10.31.149";
private static int redisPort = 6379;
private static int maxActive = 200;
private static int maxIdle = 200;
private static long maxWait = 5000;
private static Logger logger = Logger.getLogger(RedisDb.class); static {
initPool();
}
// 初始化连接池
public static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxActive);
config.setMaxIdle(maxIdle);
config.setMaxWaitMillis(maxWait);
config.setTestOnBorrow(false); jedisPool = new JedisPool(config, redisIp, redisPort, 10000, password);
}
// 从连接池获取redis连接
public static Jedis getJedis(){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
// jedis.auth(password);
} catch(Exception e){
ExceptionCapture.logError(e);
} return jedis;
}
// 回收redis连接
public static void recycleJedis(Jedis jedis){
if(jedis != null){
try{
jedis.close();
} catch(Exception e){
ExceptionCapture.logError(e);
}
}
}
// 保存字符串数据
public static void setString(String key, String value){
Jedis jedis = getJedis();
if(jedis != null){
try{
jedis.set(key, value);
} catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
} }
// 获取字符串类型的数据
public static String getString(String key){
Jedis jedis = getJedis();
String result = "";
if(jedis != null){
try{
result = jedis.get(key);
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
} return result;
}
// 删除字符串数据
public static void delString(String key){
Jedis jedis = getJedis();
if(jedis != null){
try{
jedis.del(key);
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
}
}
// 保存byte类型数据
public static void setObject(byte[] key, byte[] value){
Jedis jedis = getJedis();
String result = "";
if(jedis != null){
try{
if(!jedis.exists(key)){
jedis.set(key, value);
}
// redis中session过期时间
jedis.expire(key, expireTime);
} catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
}
}
// 获取byte类型数据
public static byte[] getObject(byte[] key){
Jedis jedis = getJedis();
byte[] bytes = null;
if(jedis != null){
try{
bytes = jedis.get(key);;
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
}
return bytes; } // 更新byte类型的数据,主要更新过期时间
public static void updateObject(byte[] key){
Jedis jedis = getJedis();
if(jedis != null){
try{
// redis中session过期时间
jedis.expire(key, expireTime);
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
} } // key对应的整数value加1
public static void inc(String key){
Jedis jedis = getJedis();
if(jedis != null){
try{
if(!jedis.exists(key)){
jedis.set(key, "1");
jedis.expire(key, countExpireTime);
} else {
// 加1
jedis.incr(key);
}
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
}
} // 获取所有keys
public static Set<String> getAllKeys(String pattern){
Jedis jedis = getJedis();
if(jedis != null){
try{
return jedis.keys(pattern);
}catch(Exception e){
ExceptionCapture.logError(e);
} finally{
recycleJedis(jedis);
}
}
return null;
} }
总结
这里只是实现了简单的session共享,但是对session的管理还不够全面,比如说session的验证。其实通过tomcat容器本身就可以实现session共享,后面再详细了解下tomcat对于session的管理。
使用redis进行基于shiro的session集群共享的更多相关文章
- Springboot Session集群处理
在集群环境下,常见的基于Session的身份认证就会有一个问题,因为Session是跟着服务器走的,当用户在服务器1登陆成功后,当用户在访问服务器2的时候会因为服务器2没有用户的身份信息而再次跳转到认 ...
- Tomcat:基于Apache+Tomcat的集群搭建
根据Tomcat的官方文档说明可以知道,使用Tomcat配置集群需要与其它Web Server配合使用才可以完成,典型的有Apache和IIS. 这里就使用Apache+Tomcat方式来完成基于To ...
- redis 5.0.3 讲解、集群搭建
REDIS 一 .redis 介绍 不管你是从事Python.Java.Go.PHP.Ruby等等... Redis都应该是一个比较熟悉的中间件.而大部分经常写业务代码的程序员,实际工作中或许只用到了 ...
- Redis安装(单机及各类集群,阿里云)
Redis安装(单机及各类集群,阿里云) 前言 上周,我朋友突然悄悄咪咪地指着手机上的一篇博客说,这是你的博客吧.我看了一眼,是之前发布的<Rabbit安装(单机及集群,阿里云>.我朋友很 ...
- Redis——(主从复制、哨兵模式、集群)的部署及搭建
Redis--(主从复制.哨兵模式.集群)的部署及搭建 重点: 主从复制:主从复制是高可用redis的基础,主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复. 哨兵和集群都是 ...
- Redis系列5:深入分析Cluster 集群模式
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) 1 背景 前面我们 ...
- 基于Kubernetes的WAF集群介绍
Kubernetes是Google开源的容器集群管理系统.它构建Docker技术之上,为容器化的应用提供资源调度.部署运行.服务发现.扩容缩容等整一套功能,可看作是基于容器技术的PaaS平台. 本文旨 ...
- Tomcat session集群
author:JevonWei 版权声明:原创作品 环境 tomcatA 172.16.253.108 tomcatB 172.16.253.105 代理服务器 172.16.253.191 Tomc ...
- 基于zookeeper的Swarm集群搭建
简介 Swarm:docker原生的集群管理工具,将一组docker主机作为一个虚拟的docker主机来管理. 对客户端而言,Swarm集群就像是另一台普通的docker主机. Swarm集群中的每台 ...
随机推荐
- SQLServer · 最佳实践 · 透明数据加密TDE在SQLServer的应用
转:https://yq.aliyun.com/articles/42270 title: SQLServer · 最佳实践 · 透明数据加密TDE在SQLServer的应用 author: 石沫 背 ...
- Django项目及应用的创建
一.url解释 1url是全球资源定位符,网上的每个文件都有唯一的url地址,组成:协议.服务器名称(或IP地址).路径和文件名. 2有时候,URL以斜杠“/”结尾,而没有给出文件名,在这种情况下,U ...
- IDEA的相关使用-----快捷键
转:https://blog.csdn.net/qq_35091777/article/details/80988165 IntelliJ Idea 常用快捷键列表 Alt+回车 导入包,自动修正Ct ...
- springsecurity 源码解读 之 RememberMeAuthenticationFilter
RememberMeAuthenticationFilter 的作用很简单,就是用于当session 过期后,系统自动通过读取cookie 让系统自动登录. 我们来看看Springsecurity的过 ...
- windows 子系统 linux wsl 开启ssh 服务
原因 windows自带的终端操作丑陋,习惯xshell的操作 步骤 1.关闭windows自带的ssh服务,这个占用了22端口 2.wsl 安装ssh服务(使用的是ubuntu) sudo apt ...
- ES6学习:两个面试题目--关于模板字符串
号称看完就能“让开发飞起来”,不过文中的两个面试题目的知识点并没包括在文中. https://www.jianshu.com/p/287e0bb867ae 文中并没有完整的知识点去完成上面的两道题,这 ...
- mongdb的索引及备份
1. mongodb的索引 1.1 为什么mongdb需要创建索引 加快查询速度 进行数据的去重 1.2 mongodb创建简单的索引方法 语法: db.集合.ensureIndex({属性:1}), ...
- vim快捷键速查
一.移动 h/j/k/l 左/下/上/右 W w 移动到下一个单词开头 E e 移动到下一个单词结尾 B b ...
- STM32CubeMX+Keil裸机代码风格(2)
10.找到STM32cubeMx所建的工程目录,在工程目录的同级目录下新建一个文件夹用来存放自己写的代码 11.用notepad++打开keil的工程文件,在这里的<Group>前面加上 ...
- Django学习(一)连接mysql
python3.6 Django2.0 几个改动的点: 1)setting: 2)__init__.py import pymysql 然后再重启server python manage.py run ...