基于Redis实现分布式Session

1、概述

我们可以自己实现类似Session的机制,采用 Redis 等分布式缓存中间件来实现。

Redis是独立于应用服务器的,基于Redis实现的Session机制自动具备了分布式属性。

Redis可以很方便地做集群配置,则Session避免了单点故障。

2、实现

实现代码极其简单,如下所示。

/**
 * @author liuhailong2008#foxmail
 */
public class ApiSession implements Serializable {

    private static final long serialVersionUID = 1055965810150154404L;

    /**Session ID*/
    private final String              id;
    /**Session创建时间*/
    private long                creationTime;
    /**Session最后一次访问时间*/
    private long                lastAccessedTime;
    /**Session的最大空闲时间间隔*/
    private int                 maxInactiveInterval;
    /**是否是新建Session*/
    private boolean             newSession;

    private static final String SESSION_KEY_PREFIX = "SESS_";
    //private Set<String> attrNameSet = Collections.synchronizedSet(new HashSet<String>());
    private final String sessionKey ;

    /**
     * 创建新的Session。
     * @param maxIdleSeconds
     */
    public ApiSession(int maxIdleSeconds){
        id = StringUtil.getUUID();
        long now = System.currentTimeMillis();
        creationTime = now;
        lastAccessedTime = now;
        this.maxInactiveInterval = maxIdleSeconds;
        newSession = true;
        //this.attrNameSet.clear();

        sessionKey = SESSION_KEY_PREFIX + id;
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(sessionKey,this);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }

    /**
     * 通过Session id获取已经存在的Session,如果没有,返回null。
     * @return
     */
    public static ApiSession get(String id){
        String sessionKey = SESSION_KEY_PREFIX + id;
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        ApiSession ret = (ApiSession) cb.get(sessionKey);
        if(ret!=null){
            ret.newSession = false;
            ret.refresh();
        }
        return ret;
    }
    /**
     * 更新 lastAccessedTime 。
     */
    public void refresh() {
        this.lastAccessedTime = System.currentTimeMillis();
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(sessionKey,this);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }
    /**
     * 是否超时过期。
     * 
     * @param session
     * @return
     */
    public boolean isExpired() {
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        ApiSession _this = (ApiSession) cb.get(this.sessionKey);
        // 先查看缓存层面的超时控制
        if(_this==null){
            return false;
        }
        long now = System.currentTimeMillis();
        long last = this.getLastAccessedTime();
        long interal = now - last;
        if(interal>this.getMaxInactiveInterval()){
            this.invalidate();
            return true;
        }else{
            return false;
        }
    }
    /**
     * 强制Session立即失效。
     */
    public synchronized void invalidate() {
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        cb.remove(this.sessionKey);
    }

    /**
     * 移除属性。
     * 
     * @param attrName
     * @return
     */
    public synchronized Object removeAttribute(String attrName){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        Object ret = cb.remove(attrSessionKey);
        return ret;
    }

    /**
     * 设置属性。
     * @param attrName
     * @param attrValue
     */
    public synchronized void setAttribute(String attrName,Object attrValue){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        CacheElement ce = new CacheElement(attrSessionKey,attrValue);
        ce.setTimeToIdleSeconds(this.getMaxInactiveInterval());
        cb.put(ce);
    }

    /**
     * 获取属性的值。
     * @param attrName
     * @return
     */
    public Object getAttribute(String attrName){
        this.refresh();
        String attrSessionKey = getAttrSessionKey(attrName);
        CacheBlock cb = CacheManager.getBlock(Const.CACHE_BLOCK_INDEX);
        Object retObject = cb.get(attrSessionKey);
        return retObject;
    }

    private String getAttrSessionKey(String attrName){
        String attrSessionKey = sessionKey + attrName;
        return attrSessionKey;
    }

    public int getMaxInactiveInterval() {
        if(maxInactiveInterval==-1){
            maxInactiveInterval = 3600;
        }
        return maxInactiveInterval;
    }

    public void setMaxInactiveInterval(int maxInactiveInterval) {
        this.maxInactiveInterval = maxInactiveInterval;
    }

    public String getId() {
        return id;
    }

    public long getCreationTime() {
        return creationTime;
    }

    public long getLastAccessedTime() {
        return lastAccessedTime;
    }

    public boolean isNewSession() {
        return newSession;
    }
}

3、用法

3.1、建立Session

// 建立Session
int maxIdleSeconds = 60 * 20 ;
ApiSession session = new ApiSession( maxIdleSeconds );
String sessId = session.getId();
session.setAttribute("CURRENT_USER", user);

3.2、读取Session

// 读取Session
ApiSession session = ApiSession.get(tokenToBeChecked);
if(session==null){
    logger.debug(String.format("会话超时啦,token:%s。", tokenToBeChecked));
    return false;
}
// 检查是否超时
boolean isExpired = session.isExpired();
if(isExpired){
    logger.debug(String.format("会话超时啦,token:%s。", tokenToBeChecked));
    return false;
}
// 从Sesion中取出token
String token = (String)session.getAttribute(Const.TOKEN_SESSION_KEY);
if(StringUtils.isEmpty(token)){
    return false;
}
// 同调用方提交的比较
if(token.equalsIgnoreCase(tokenToBeChecked)){
    session.refresh();
    return true;
}

4、优化点

以上只是提供了实现范例。进一步的优化点包括:

  1. Redis存储规划,设计Session Id 、Attr的存储方式。
  2. 采用自己的持久化方式,提高持久化效率。
  3. 提供更多工具方法,让Session更易用。
  4. 进一步实现Session的其他接口。

等等。

未尽事宜,欢迎留言讨论。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值