在这个互联网高度发达的时代,许多应用的用户动辄成百上千万,甚至上亿。为了支持海量用户的访问,应用服务器集群这种水平扩展的方式是最常用的。这种情形下,就会涉及到许多单机环境下完全不需要考虑的问题,这其中session的创建、共享和存储是最常见之一。

在单机环境中,Session的创建和存储都是由同一个应用服务器实例来完成,而存储也仅是内存中,最多会在正常的停止服务器的时候,把当前活动的Session钝化到本地,再次启动时重新加载。

而多个实例之间,Session数据是完全隔离的。而为了实现Session的高可用,多实例间数据共享是必然的,下面我们以Redis 的SessionManager实现多Tomcat实例Session共享的配置为例,我们来梳理下一般session共享的流程:

  1. 添加具体要使用的manager的Jar文件及其依赖

    • redis session manager依赖jedis, commons-pool, commons-pool2

    • 对应版本的redis session manager的jar文件

  2. 在TOMCAT_HOME/conf/context.xml中增加如下配置

<Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" />
<Manager className="com.radiadesign.catalina.session.RedisSessionManager"
         host="localhost"  
         port="6379" database="0"
         maxInactiveInterval="30" />

其中hostport等替换为对应的配置信息

  1. 启动多个Tomcat实例,以自带的examples应用为例进行验证

  2. 访问examples应用的servlets/servlet/SessionExample

  3. 在页面中添加数据到session中,并查看页面上对应的session信息

  4. 访问另一个实例上相同应用的页面,查看session信息,两者应该是一致的

  5. 使用redis-cli查看redis中存储的对应数据,相应的sessionId对应的数据已经保存了下来

以上是一个基本的配置过程,而在这些配置与验证的步骤中,第二步是核心逻辑实现。 前面的文章,曾介绍过Tomcat的Valve,在请求处理时,Pipeline中的各个Valve的invoke方法会依次执行。Tomcat的AccessLogValve介绍

此处的session处理,就是以一个自定义Valve的形式进行的。关于Session的文章,前面也写过几篇,会附在结尾处

以下是RedisSessionhandlerValve的invoke方法,我们看,主要是在Valve执行后进行Session的存储或移除。

public void invoke(Request request, Response response) {
    try {
      getNext().invoke(request, response);
    } finally {
      final Session session = request.getSessionInternal(false);
      storeOrRemoveSession(session);
      manager.afterRequest();
    }
  }

而session的保存和移除又是通过manager执行的。 manager.save(session); manager.remove(session);

这里,manager就是前面定义的RedisSessionManager。默认单实例情况下,我们使用的都是StandardManager,对比一下两者,标准的Manager对于session的创建和删除,都会调到其父类ManagerBase中相应的方法,

public void add(Session session) {

        sessions.put(session.getIdInternal(), session);
        int size = getActiveSessions();
        if( size > maxActive ) {
            synchronized(maxActiveUpdateLock) {
                if( size > maxActive ) {
                    maxActive = size;
                }
            }
        }
    }
    
public void remove(Session session, boolean update) {  
if (session.getIdInternal() != null) {
            sessions.remove(session.getIdInternal());
        }
    }  

我们来看,由于其只保存在内存的Map中protected Map<String, Session> sessions = new ConcurrentHashMap<>(),每个Tomcat实例都对于不同的map,多个实例间无法共享数据。

对应到RedisSessionManager对于session的处理,都是直接操作redis,基本代码是下面这个样:

public void save(Session session) throws IOException {
    Jedis jedis = null;
    Boolean error = true;
    try {
      RedisSession redisSession = (RedisSession) session;

      Boolean sessionIsDirty = redisSession.isDirty();
      redisSession.resetDirtyTracking();
      byte[] binaryId = redisSession.getId().getBytes();

      jedis = acquireConnection();
      if (sessionIsDirty || currentSessionIsPersisted.get() != true) {
        jedis.set(binaryId, serializer.serializeFrom(redisSession));
      }
      currentSessionIsPersisted.set(true);
      jedis.expire(binaryId, getMaxInactiveInterval());
    } }

移除时的操作是这样的

public void remove(Session session, boolean update) {
    Jedis jedis = null;
    Boolean error = true;

    log.trace("Removing session ID : " + session.getId());

    try {
      jedis = acquireConnection();
      jedis.del(session.getId());
      error = false;
    } finally {
      if (jedis != null) {
        returnConnection(jedis, error);
      }
    }
  }

而此时,多个Tomcat实例都读取相同的Redis,session数据是共享的,其它实例的初始请求过来时,由于会执行findSession的操作,此时会从Redis中加载session,

public Session findSession(String id) throws IOException {
    RedisSession session;

    if (id == null) {
      session = null;
      currentSessionIsPersisted.set(false);
    } else if (id.equals(currentSessionId.get())) {
      session = currentSession.get();
    } else {
      session = loadSessionFromRedis(id); // 看这里,会从redis中load

      if (session != null) {
        currentSessionIsPersisted.set(true);
      }
    }

    currentSession.set(session);
    currentSessionId.set(id);

    return session;
  }

从而可以保证在一个实例被切换后,另外的实例可以继续响应同一个session的请求。

以上即为Redis实现session共享高可用的一些关键内容。有兴趣的朋友可以看下通过Memcached实现高可用,也是这个原理。顺着这个思路,如果你有将Session存储在其它地方的需求时,完全可以写一个出来,自己动手,丰衣足食。

总结一下,我们是通过自定义的Valve来实现请求后session的拦截,同时,使用自定义的SessionManager,来满足不同的session创建与存储的需求。而至于是存储在Redis/Memcached中,还是存储在DB中,只是位置的区别。原理,是一致的。

详解集群内Session高可用的实现原理的更多相关文章

  1. LVS集群和Keepalived高可用实战

    第四十章LVS集群和Keepalived高可用实战 一.ARP协议 1.概念 地址解析协议,即ARP(AddressResolutionProtocol),是根据IP地址获取物理MAC地址的一个TCP ...

  2. EMQ集群搭建实现高可用和负载均衡(百万级设备连接)

    一.EMQ集群搭建实现高可用和负载均衡 架构服务器规划 服务器IP 部署业务 作用 192.168.81.13 EMQTTD EMQ集群 192.168.81.22 EMQTTD EMQ集群 192. ...

  3. 深入解析 SQL Server 高可用镜像实现原理

    作者:郭忆 本文由 网易云 发布. SQL Server 是 windows 平台 .NET 架构下标配数据库解决方案,与 Oracle.MySQL 共同构成了 DB-Engines Ranking ...

  4. 深入解析SQL Server高可用镜像实现原理

    本文来自网易云社区 SQL Server 是windows平台.NET架构下标配数据库解决方案,与Oracle.MySQL共同构成了DB-Engines Ranking的第一阵营,在国内外企业市场中有 ...

  5. Redis从出门到高可用--Redis复制原理与优化

    Redis从出门到高可用–Redis复制原理与优化 单机有什么问题? 1.单机故障; 2.单机容量有瓶颈 3.单机有QPS瓶颈 主从复制:主机数据更新后根据配置和策略,自动同步到备机的master/s ...

  6. Redis高可用之主从复制原理演进分析

    Redis高可用之主从复制原理演进分析 在很久之前写过一篇 Redis 主从复制原理的简略分析,基本是一个笔记类文章. 一.什么是主从复制 1.1 什么是主从复制 主从复制,从名字可以看出,至少需要 ...

  7. redis详解(四)-- 高可用分布式集群

    一,高可用 高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响. 停止服务的原因可能由于网卡.路由器.机房.CPU负载过高.内存溢出.自然灾害等不可预期的原 ...

  8. ActiveMQ(七)_伪集群和主从高可用使用

      一.本文目的         介绍如何在同一台虚拟机上搭建高可用的Activemq服务,集群数量包含3个Activemq,当Activemq可用数>=2时,整个集群可用.         本 ...

  9. ActiveMQ(七)_伪集群和主从高可用使用(转)

    本文转自: https://www.cnblogs.com/gossip/p/5977489.html 一.本文目的         介绍如何在同一台虚拟机上搭建高可用的Activemq服务,集群数量 ...

随机推荐

  1. oracle插入数据报错ORA-26026

    今天进行数据清理时发现报错ORA-26026,主要是把从交易库提取数据并插入到归档库中. 检查一下发现是归档库的索引问题. 当时为了提高插入速度,所以删除了归档库的索引,可能对主键索引产生了影响. 解 ...

  2. /etc目录深入理解

    /etc This is the nerve center of your system, it contains all system related configuration files in ...

  3. 三步走起 提升 iOS 审核通过率 上篇

    <ignore_js_op> Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明 ...

  4. Jquery插件之信息弹出框showInfoDialog(成功、错误、警告、通知)

    一.信息种类说明: 1.1.操作成功信息 1.2.错误信息 1.3.警告信息 1.4.通知信息 二.使用说明 <!DOCTYPE html PUBLIC "-//W3C//DTD HT ...

  5. 原始的JDBC操作

    -----------------------------根据配置文件---------------------------- package cn.gdpe.jdbc; import java.io ...

  6. Java Runtime Data Area

    java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个区域,这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖着用户的线程的启动和结束而建 ...

  7. html5+css3实现上拉和下拉刷新

    <!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content=& ...

  8. thinkphp中表有前缀名的时候申明模板的方法

    $Model=new \Home\Model\Stu_activityModel; $Model=D('Stu_activity');//错误的声明不了

  9. python中的buildin函数详解(第一篇)

    这会是很长的一个帖子,因为我打算从python最基础的东西开始,尝试去完全的掌握它,buildin中有一些常用的函数比如 abs, open, setattr, getattr, 大家都很了解他们的用 ...

  10. 初学socket,c语言写的简单局域网聊天

    在客户端所在的目录新建一个IP.bwj的文件,写上服务端的IP,不要带空格,保存.双方都打开一个客户端和一个服务端就可以聊天了,(可以写自己的IP,自己跟自己聊..)没有第三方服务器,服务端所在的电脑 ...