解决Shiro频繁访问Redis读取和更新session(十二)

原文:https://blog.csdn.net/qq_34021712/article/details/80791339

该博客是接着上一篇博客: Shiro使用redis作为缓存(解决shiro频繁访问Redis) 请将两篇博客同时打开,方便查看。

前言

关于shiro频繁访问redis,共分为两种,一种是频繁的去redis读取session , 一种是频繁的去更新redis 中的session,针对两种不同情况,分别写出解决方案。

频繁读取session解决有两种方案:

第一种方案:本地缓存

上面的RedisSessionDAO类中依赖一个叫SessionInMemory的类,是shiro-redis作者为了解决一次请求频繁访问redis读取session的解决方案,基于本地cache,如果是在一秒内的请求,都会从本地cache中获取request。下面有更好的方案,但是这个代码我也没有删除。共存互不影响。

SessionInMemory.java
package com.springboot.test.shiro.config.shiro;

import org.apache.shiro.session.Session;

import java.util.Date;

/**
 * Use ThreadLocal as a temporary storage of Session, so that shiro wouldn't keep read redis several times while a request coming.
 */
public class SessionInMemory {
    private Session session;
    private Date createTime;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

然后在RedisSessionDAO的doReadSession先去本地缓存查询是否有,有的话从本地获取,并对比时间,在规定时间内,则使用缓存中的session。详细代码,请参考上篇博客。

另一种方案:从request中获取

参考博客:http://www.hillfly.com/2017/182.html
关于频繁去Redis中读取Session有一个更好的解决方案,重写
DefaultWebSessionManager 的 retrieveSession()方法。在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的,这个类有个我们很熟悉的属性:servletRequest。小伙伴们应该都灵光一现了!直接把 session 对象怼进 request 里去!那么在单次请求周期内我们都可以从 request 中取 session 了,而且请求结束后 request 被销毁,作用域和生命周期的问题都需要我们考虑了。
显然我们要 Override 这个retrieveSession方法,为此我们需要使用自定义的 SessionManager,如下:

ShiroSessionManager.java
package com.springboot.test.shiro.config.shiro;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import java.io.Serializable;

/**
 * @author: wangsaichao
 * @date: 2018/6/23
 * @description: 解决单次请求需要多次访问redis
 */
public class ShiroSessionManager extends DefaultWebSessionManager {

    private static Logger logger = LoggerFactory.getLogger(DefaultWebSessionManager.class);
    /**
     * 获取session
     * 优化单次请求需要多次访问redis的问题
     * @param sessionKey
     * @return
     * @throws UnknownSessionException
     */
    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);

        ServletRequest request = null;
        if (sessionKey instanceof WebSessionKey) {
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        if (request != null && null != sessionId) {
            Object sessionObj = request.getAttribute(sessionId.toString());
            if (sessionObj != null) {
                logger.debug("read session from request");
                return (Session) sessionObj;
            }
        }

        Session session = super.retrieveSession(sessionKey);
        if (request != null && null != sessionId) {
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }
}

记得在ShiroConfig中配置SessionManager为自定义的ShiroSessionManager

解决频繁更新session解决方案

参考博客:https://blog.csdn.net/zsg88/article/details/74806374
由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。

ShiroSession.java
package com.springboot.test.shiro.config.shiro;

import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;

/**
 * @author: wangsaichao
 * @date: 2018/6/23
 * @description: 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,
 * 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回
 */
public class ShiroSession extends SimpleSession implements Serializable {
    // 除lastAccessTime以外其他字段发生改变时为true
    private boolean isChanged = false;

    public ShiroSession() {
        super();
        this.setChanged(true);
    }

    public ShiroSession(String host) {
        super(host);
        this.setChanged(true);
    }


    @Override
    public void setId(Serializable id) {
        super.setId(id);
        this.setChanged(true);
    }

    @Override
    public void setStopTimestamp(Date stopTimestamp) {
        super.setStopTimestamp(stopTimestamp);
        this.setChanged(true);
    }

    @Override
    public void setExpired(boolean expired) {
        super.setExpired(expired);
        this.setChanged(true);
    }

    @Override
    public void setTimeout(long timeout) {
        super.setTimeout(timeout);
        this.setChanged(true);
    }

    @Override
    public void setHost(String host) {
        super.setHost(host);
        this.setChanged(true);
    }

    @Override
    public void setAttributes(Map<Object, Object> attributes) {
        super.setAttributes(attributes);
        this.setChanged(true);
    }

    @Override
    public void setAttribute(Object key, Object value) {
        super.setAttribute(key, value);
        this.setChanged(true);
    }

    @Override
    public Object removeAttribute(Object key) {
        this.setChanged(true);
        return super.removeAttribute(key);
    }

    /**
     * 停止
     */
    @Override
    public void stop() {
        super.stop();
        this.setChanged(true);
    }

    /**
     * 设置过期
     */
    @Override
    protected void expire() {
        this.stop();
        this.setExpired(true);
    }

    public boolean isChanged() {
        return isChanged;
    }

    public void setChanged(boolean isChanged) {
        this.isChanged = isChanged;
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    protected boolean onEquals(SimpleSession ss) {
        return super.onEquals(ss);
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public String toString() {
        return super.toString();
    }
}
ShiroSessionFactory.java
package com.springboot.test.shiro.config.shiro;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionFactory;
import org.apache.shiro.web.session.mgt.DefaultWebSessionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

/**
 * @author: wangsaichao
 * @date: 2018/6/23
 * @description:
 */
public class ShiroSessionFactory implements SessionFactory {
    private static final Logger logger = LoggerFactory.getLogger(ShiroSessionFactory.class);

    @Override
    public Session createSession(SessionContext initData) {
        ShiroSession session = new ShiroSession();
        HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST");
        session.setHost(getIpAddress(request));
        return session;
    }

    public static String getIpAddress(HttpServletRequest request) {
        String localIP = "127.0.0.1";
        String ip = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

然后在ShiroConfig配置该Bean,并赋值给sessionManager

@Bean("sessionFactory")
public ShiroSessionFactory sessionFactory(){
    ShiroSessionFactory sessionFactory = new ShiroSessionFactory();
    return sessionFactory;
}

@Bean("sessionManager")
public SessionManager sessionManager() {
    ShiroSessionManager sessionManager = new ShiroSessionManager();
     ... 该出省略其他配置
    sessionManager.setSessionFactory(sessionFactory());
    return sessionManager;

}

然后在RedisSessionDAO的update方法上判断如果只是更改session的lastAccessTime,则直接返回。

@Override
public void update(Session session) throws UnknownSessionException {
    //如果会话过期/停止 没必要再更新了
    try {
        if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
            return;
        }

        if (session instanceof ShiroSession) {
            // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
            ShiroSession ss = (ShiroSession) session;
            if (!ss.isChanged()) {
                return;
            }
            //如果没有返回 证明有调用 setAttribute往redis 放的时候永远设置为false
            ss.setChanged(false);
        }

        this.saveSession(session);
    } catch (Exception e) {
        logger.warn("update Session is failed", e);
    }
}

这里注意:在操作redis更新session时候, changed属性一定是false, 原博客中并没有说明,我在测试的时候,发现如果只是更改lastAccessTime也不会直接返回,因为从redis拿出来的是true 。所以,既然走到往redis更新session这一步,那一定有setAttributes等方法被调用.所以往redis放的时候设置为false。下次从redis获取session是false 只是更改lastAccessTime 那么 changed属性就是false,将不会操作redis。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro-Redis 是一个用于在 Shiro 中实现 Session 共享的插件,它使用 Redis 作为数据存储和缓存,以实现分布式环境下的 Session 共享。 要实现 Shiro-RedisSession 共享,你需要进行以下步骤: 1. 引入 Shiro-Redis 插件依赖:在项目的 Maven 或 Gradle 配置文件中添加 Shiro-Redis 依赖。 2. 配置 Redis 连接信息:在项目的配置文件中配置 Redis 的连接信息,包括主机名、端口号、密码等。 3. 配置 RedisSessionDAO:在 Shiro配置文件中配置 RedisSessionDAO,指定使用 Redis 作为 Session 存储和缓存的实现。可以设置过期时间、前缀等参数。 4. 配置 Session Manager:在 Shiro配置文件中配置 Session Manager,指定使用自定义的 RedisSessionManager 作为 Session 的管理器。同时,需要将之前配置RedisSessionDAO 设置给 RedisSessionManager。 5. 配置 SecurityManager:在 Shiro配置文件中配置 SecurityManager,指定使用自定义的 RedisSessionManager 作为 Session 管理器。同时,需要将之前配置RedisSessionDAO 设置给 RedisSessionManager。 6. 配置 Filter Chain:在 Shiro配置文件中配置 Filter Chain,将自定义的 RedisSessionManager 添加到 Filter Chain 中,以便对请求进行 Session 管理。 通过以上步骤配置完成后,Shiro 将会使用 Redis 进行 Session 的存储和缓存,从而实现 Session 的共享。在分布式环境中,不同应用节点之间可以通过 Redis 共享 Session 数据,从而实现用户的登录状态和会话信息的共享。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值