精华内容
下载资源
问答
  • Session共享

    千次阅读 2019-06-26 13:55:36
    session共享

    嘿,大家好,今天我要更新的内容是session共享。。。

    开发背景

    由于是比较老的项目(struts2+spring2.5+ibatis),且没有使用redis进行缓存,项目中需要缓存的东西通通都放到了session中,我们想要达到这样一个目的,使用Nginx来负载两个tomcat,这样我们在更新或者升级代码的时候,可以保证另外一个项目还可以继续运行,用户还可以继续访问。那么问题来了,当tomcat-A停止运行的时候,另一个tomcat-B中的session中并没有tomcat-A中的我们缓存的数据,则不能保证服务正常运行。

    所以呢,这里有两个解决方案,方案A:将所有存取session的操作改到存取redis中,但是由于存取session的地方太多了,工作量过大,而且麻烦,所以这种方案就被pass了,然后我们采取方案B:实现session的共享的方式,使用这种方式的好处就是,不改变原来的代码逻辑,而且简单。

    顺着这条路我开始研究session共享,最终找到两种可行的方法:

    一:tomcat-session-redis

    使用tomcat+redis实现session共享,下面是操作步骤以及在配置tomcat-session-redis的时候遇到的一些坑

    1.引入三个jar包:commons-pool2-2.2.jar、jedis-2.6.0.jar和tomcat-redis-session-manager-1.2-tomcat-7.jar放在tomcat的 lib下面。(这里博主的jdk和tomcat的版本都是7,大家有兴趣可以尝试下别的版本,点击链接可以下载不同版本的tomcat-redis-session的jar包)

    2.然后tomcat的context.xml配置里增加如下配置,不要加错了哦,博主蠢蠢的把这面这段配置加到了server.xml中导致报错,好久才发现。。。

    <Valve className="com.radiadesign.tomcat.catalina.RedisSessionHandlerValve" />
    <Manager className="com.radiadesign.tomcat.catalina.RedisSessionHandlerValve"
             host="localhost" 
             port="6379" 
             database="0" 
             maxInactiveInterval="60" 
             sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.." 
             sentinelMaster="SentinelMasterName"
             sentinels="sentinel-host-1:port,sentinel-host-2:port,.." />
    

    3.在这里需要注意一个问题,就是大家在配置的时候一定要注意看大家下载的tomcat-redis-session.jar包的版本的RedisSessionHandlerValve的对应路径是否正确,最新版本的RedisSessionHandlerValve的类路径可能会是下面这个,大家根据自己下载的选择配置

    <Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
    <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
             host="localhost" 
             port="6379" 
             database="0" 
             maxInactiveInterval="60" 
             sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.." 
             sentinelMaster="SentinelMasterName"
             sentinels="sentinel-host-1:port,sentinel-host-2:port,.."  />
    

    其中sessionPersistPolicies有三种策略,上文中的是默认策略,还有另外两种保存策略,大家可以根据需要选择配置即可

    SAVE_ON_CHANGE:每次session.setAttribute()或被session.removeAttribute()称为会话都将被保存。注意:此功能无法检测对已存储在特定会话属性中的对象所做的更改。权衡:此选项会稍微降低性能,因为会话的任何更改都会将会话同步保存到Redis。

    ALWAYS_SAVE_AFTER_REQUEST:每次请求后强制保存,无论管理器是否检测到会话更改。如果对已存储在特定会话属性中的对象进行更改,则此选项特别有用。权衡:如果并非所有请求都改变了会话,则此选项实际上会增加竞争条件的可能性。

    配置完成后我们分别启动Nginx(Nginx.conf配置在文章底部),redis,tomcat-A,tomcat-B,此时查看两个tomcat服务的sessionId是否一致,如果一致,则说明我们配置成功了。。。

    遇到的问题

    下面是我在配置时遇到的几个问题,大家如果在配置的时候,有遇到和我相同问题的可以参考一下:

    1.配置要放在context.xml中,而不是server.xml。估计只有我会犯这种错误吧,哈哈哈哈~~~

    2.保证tomcat配置session交由redis。

    3.应用的session管理交由tomcat管理,如果应用针对session做了个性化管理,session 共享会失效,博主就是没有发现项目中的web.xml对session做了过期时间的设置,导致session 共享会失效。

    4.session里不能存入null,因为不能getByte(null),这也是我遇到的一个坑,明明都是配置好了,sessionId也已经相同了,后来查看tomcat-redis-session.jar的源码发现,将数据通过getByte()的方式序列化到了redis中,但是项目中存在set一个null到session中,导致一直报错,在相应位置位置做了非空判断之后,session共享正常。。。

    至此Nginx+tomcat+redis配置session共享测试成功!

    二:spring-session

    注意,spring-session这种方式对spring的版本有一定的要求,由于公司项目使用的spring版本较老,不支持spring-session,所以将spring版本升级到4.3。ps:spring session支持的spring版本最低为spring 4.2以上

    具体操作步骤如下:

    1.确保spring的版本在4.2之上,如为满足条件需先升级spring的版本

    2.引入spring session与redis相关的jar包,分别为:commons-pool2-2.4.2.jar,jedis-2.9.0.jar,spring-data-redis-1.7.3.RELEASE.jar,spring-session-1.2.2.RELEASE.jar,由于博主项目较老,而且还不是maven管理的,所以这里直接引入相关jar包,如果是maven项目的小伙伴在pom中直接引入相关jar包即可

    3. 在web.xml添加过滤器:(放在所有过滤器配置之前)

    <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    4.applicationContext.xml文件配置相关redis会话连接管理

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
          <property name="maxTotal" value="100" />
          <property name="maxIdle" value="10" />
    </bean>
    
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
          <property name="hostName" value="127.0.0.1"/>
          <property name="port" value="6379"/>
          <property name="password" value="" />
          <property name="timeout" value="180000"/>
          <property name="usePool" value="true"/>
          <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>
    
    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
          <property name="maxInactiveIntervalInSeconds" value="3000"/>
    </bean>
    

    遇到的问题

    5.至此其实配置已经结束了,但是其实还没完,这样配置完成之后会存在List、Map、TreeMap等集合不能序列化,不能存入redis的问题,spring-session默认使用的序列化方式是JdkSerializationRedisSerializer,所以我们要修改spring-session默认的序列化方式,使用我们自定义的序列化方式SessionSerializer,另外我们要把需要存取redis的实体类实现序列化接口~~~

    RedisHttpSessionConfig :

    package cn.gathub.session;
    
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.session.data.redis.RedisFlushMode;
    import org.springframework.session.data.redis.RedisOperationsSessionRepository;
    import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
    import org.springframework.session.web.http.CookieSerializer;
    import org.springframework.session.web.http.DefaultCookieSerializer;
    import org.springframework.session.web.http.HeaderHttpSessionStrategy;
    import org.springframework.session.web.http.HttpSessionStrategy;
    
    import com.fasterxml.jackson.databind.ser.std.StringSerializer;
    
    import redis.clients.jedis.JedisPoolConfig;
    
    /**
     * Spring Session分布式会话解决方案
     */
    @Configuration
    @EnableScheduling
    public class RedisHttpSessionConfig extends RedisHttpSessionConfiguration {
    
        @Bean(name = "redisHttpSessionConfiguration")
        public RedisHttpSessionConfiguration redisHttpSessionConfiguration() {
            RedisHttpSessionConfiguration sessionConfiguration = new RedisHttpSessionConfiguration();
            sessionConfiguration.setMaxInactiveIntervalInSeconds(1800);
            return sessionConfiguration;
        }
    
        /**
         * Spring Data Redis 的会话存储仓库配置,可选
         */
        @Bean(name = "sessionRepository")
        public RedisOperationsSessionRepository sessionRepository(
                RedisOperations<Object, Object> sessionRedisTemplate,
                ApplicationEventPublisher applicationEventPublisher) {
            this.setMaxInactiveIntervalInSeconds(Integer.valueOf(1800)); // 单位:秒
            this.setRedisNamespace(getApplicationName());
            this.setRedisFlushMode(RedisFlushMode.ON_SAVE);
            return super.sessionRepository(sessionRedisTemplate, applicationEventPublisher);
        }
    
        /**
         * Spring Data Redis 的默认序列化工具,可选
         */
        @Bean(name = "springSessionDefaultRedisSerializer")
        public RedisSerializer springSessionDefaultRedisSerializer() {
            //RedisSerializer defaultSerializer = new JdkSerializationRedisSerializer();
            RedisSerializer defaultSerializer = new SessionSerializer();
            return defaultSerializer;
        }
       
        private String getApplicationName() {
            return "test";
        }
    }
    

    SessionSerializer :

    package cn.gathub.session;
    
    import java.nio.charset.Charset;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.type.TypeFactory;
    
    
    public class SessionSerializer implements RedisSerializer<Object> {
       
       private Log logger = LogFactory.getLog(SessionSerializer.class);
       private ObjectMapper mapper = new ObjectMapper();
       private Charset charset = Charset.forName("utf-8");
       private final String separator = "=";
       private final String classPrefix = "<";
       private final String classSuffix = ">";
       private final String classSeparator = ",";
    
       private Pattern pattern;
    
       public SessionSerializer() {
          pattern = Pattern.compile("<(.*)>");
       }
    
       /**
         * 获取class,包含集合类的泛型
         * <p>暂只支持最多两个泛型,同时集合内数据必须为同一个实现类,不可将泛型声明成父类</p>
         *
         * @param obj 将要序列化的对象
         * @return 没有泛型,形式为java.lang.String<>
         * <p>一个泛型,形式为java.lang.String<java.lang.String></p>
         * <p>两个个泛型,形式为java.lang.String<java.lang.String,java.lang.String></p>
         */
        private String getBegin(Object obj) {
            StringBuilder builder = new StringBuilder(obj.getClass().toString().substring(6) + classPrefix);
            if (obj instanceof List) {
                List list = ((List) obj);
                if (!list.isEmpty()) {
                    Object temp = list.get(0);
                    builder.append(temp.getClass().toString().substring(6));
                }
            } else if (obj instanceof Map) {
                Map map = ((Map) obj);
                Iterator iterator = map.keySet().iterator();
                if (iterator.hasNext()) {
                    Object key = iterator.next();
                    Object value = map.get(key);
                    builder.append(key.getClass().toString().substring(6)).append(classSeparator).append(value.getClass().toString().substring(6));
                }
            } else if (obj instanceof Set) {
                Set set = ((Set) obj);
                Iterator iterator = set.iterator();
     
                if (iterator.hasNext()) {
                    Object value = iterator.next();
                    builder.append(value.getClass().toString().substring(6));
                }
            } else if (obj instanceof TreeMap) {
               TreeMap treeMap = ((TreeMap) obj);
                Iterator iterator = treeMap.keySet().iterator();
                if (iterator.hasNext()) {
                    Object key = iterator.next();
                    Object value = treeMap.get(key);
                    builder.append(key.getClass().toString().substring(6)).append(classSeparator).append(value.getClass().toString().substring(6));
                }
            } 
            builder.append(classSuffix);
            return builder.toString();
        }
     
        @Override
        public byte[] serialize(Object o) throws SerializationException {
            if (o == null)
                return new byte[0];
            try {
                String builder = getBegin(o) +
                        separator +
                        mapper.writeValueAsString(o);
                return builder.getBytes(charset);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
     
        @Override
        public Object deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length == 0) return null;//已被删除的session
            try {
                String temp = new String(bytes, charset);
     
                String cl[] = getClass(temp);
     
                if (cl == null) {
                    throw new RuntimeException("错误的序列化结果=" + temp);
                }
                if (cl.length == 1) {
                    return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), Class.forName(cl[0]));
                } else if (cl.length == 2) {
                    TypeFactory factory = mapper.getTypeFactory();
                    JavaType type = factory.constructParametricType(Class.forName(cl[0]), Class.forName(cl[1]));
                    return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), type);
                } else if (cl.length == 3) {
                    TypeFactory factory = mapper.getTypeFactory();
                    JavaType type = factory.constructParametricType(Class.forName(cl[0]), Class.forName(cl[1]), Class.forName(cl[2]));
                    return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), type);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
     
        /**
         * 解析字符串,获取class
         * <p>一个类型,java.lang.String<>={}</p>
         * <p>两个类型,后面为泛型,java.lang.String<java.lang.String>={}</p>
         * <p>三个类型,后面为泛型,java.lang.String<java.lang.String,java.lang.String>={}</p>
         *
         * @param value 包含class的字符串
         * @return 返回所有类的数组
         */
        private String[] getClass(String value) {
            int index = value.indexOf(classPrefix);
            if (index != -1) {
                Matcher matcher = pattern.matcher(value.subSequence(index, value.indexOf(classSuffix) + 1));
                if (matcher.find()) {
                    String temp = matcher.group(1);
                    if (temp.isEmpty()) {//没有泛型
                        return new String[]{value.substring(0, index)};
                    } else if (temp.contains(classSeparator)) {//两个泛型
                        int nextIndex = temp.indexOf(classSeparator);
                        return new String[]{
                                value.substring(0, index),
                                temp.substring(0, nextIndex),
                                temp.substring(nextIndex + 1)
                        };
                    } else {//一个泛型
                        return new String[]{
                                value.substring(0, index),
                                temp
                        };
                    }
                }
            }
            return null;
        }
    }
    

    配置完成后我们分别启动Nginx(Nginx.conf配置在文章底部),redis,tomcat-A,tomcat-B,此时查看两个tomcat服务的sessionId是否一致,如果一致,则说明我们配置成功了。。。

    以上就是实现session共享的的两种方式。。。

    Nginx.conf配置文件

    使用Nginx负载均配两个tomcat配置如下:

    worker_processes  1;
    
    events {
        worker_connections  1024;
    }
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        sendfile        on;
    
        keepalive_timeout  65;
    
    	#负载均衡
        #1、轮询(默认)
        #每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
        upstream test{
            server 127.0.0.1:8080;
            server 127.0.0.1:8081;
        }
        #设定虚拟主机配置
        server {
            #侦听80端口
            listen       80;
            #定义使用服务名 一般和域名相同 多个域名用空格分隔
            server_name  127.0.0.1;
    
            #编码
            charset UTF-8;
    
            #URL映射
            location /test {
            	#反向代理
                proxy_pass http://test;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header  X-Real-IP  $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                client_max_body_size 50m;
                client_body_buffer_size 256k;
                proxy_connect_timeout 30;
                proxy_send_timeout 30;
                proxy_read_timeout 60;
                proxy_buffer_size 16k;
                proxy_buffers 4 32k;
                proxy_busy_buffers_size 64k;
                proxy_temp_file_write_size 64k;
            }
        }
    }
    

    今天的更新到这里就结束了,拜拜!!!

    感谢一路支持我的人,您的关注是我坚持更新的动力,有问题可以在下面评论留言或随时与我联系。。。。。。
    QQ:850434439
    微信:w850434439
    EMAIL:gathub@qq.com

    如果有兴趣和本博客交换友链的话,请按照下面的格式在评论区进行评论,我会尽快添加上你的链接。

    网站名称:GatHub-HongHui’S Blog
    网站地址:https://www.gathub.cn/
    网站描述:不习惯的事越来越多,但我仍在前进…就算步伐很小,我也在一步一步的前进。
    网站Logo/头像:头像地址

    展开全文
  • session共享

    2019-04-13 17:50:53
    session共享为何要共享sessionsession的获取过程getSession()里做了什么?集群间如何实现session共享1.持久化session到数据库,即使用数据库来储存session。数据库正好是我们普遍使用的公共储存空间,一举两得,推荐...

    session共享

    为何要共享session

    针对企业,为了应对庞大的用户访问压力,目前大多数大型网站服务器都采用集群部署的方式;针对个人,仅一台服务器而言,也会安装多个tomcat进行错时更新,保证更新后台业务时服务不断开,即模拟了集群的运行方式。在此集群中,我们就不得不考虑一个用户鉴权的问题,即在不同服务上如何保证用户均已登录,并能获取相同的用户登录信息。所以需要共享session到每一个服务器上面。

    session的获取过程

    服务器可将请求<->响应这一个完整的过程称为一次会话,并为这次会话生成一个唯一的标识符,即sessionId,用来表示这次会话,Session储存在服务器端;
    Java Web的共用的用户鉴权机制是采用Session-Cookie技术,实现原理是:用户登录时,请求到达服务器,服务器调用通过getSession()方法判断session是否存在,如果不存在,则新建session,并通过其算法为session生成一个随机数作为sessionId,开发者可在session中储存一些用户信息;第二次请求时,如获取用户信息,getSession()方法判断session存在,则取出session,而不是新建,从而从session中获取到用户的相关信息。

    getSession()里做了什么?

    1.第一次用户请求,客户端本地没有任何数据,即其cookie为空,朝服务器发送request,getSession()中会解析request,发现其约定的cookie为null,则认为没有session,所以会重新创建一个session对象;

    2.创建session后会将此session的id放入response中,回传给客户端,客户端则保存response中的cookie;

    3.再次请求,服务器getSession()又会重新解析request获取cookie,发现了其中的sessionId,那么根据此sessionId去服务器的中去找,则得到了上次创建的session对象,那么则认为鉴权成功。

    集群间如何实现session共享

    按照前文所说的session-cookie机制,session是保存在每台服务器的,但在集群中,拥有多台服务器,每台各自为政,势必会造成在这台服务器中登录,获取session成功,但是到另一台服务器上,又会获取不到session,造成鉴权失败,这样对用户来说是极不友好的,那么怎么解决这个问题呢?

    通过我们以上的分析,即可得出几种处理方式:
    A.找一块公共的空间用来储存session,而不是将session储存在集群节点的某台服务器上,此时,每一台服务器都能访问这块空间,从而实现session共享;

    B.仍在每台服务器上保存session信息,不作修改,但采用另一种同步机制,实时同步没一台服务器的session信息;

    C.构建一种全新的鉴权机制,不采用session-cookie机制,但要去除此鉴权机制对单个服务器的依赖。

    1.持久化session到数据库,即使用数据库来储存session。数据库正好是我们普遍使用的公共储存空间,一举两得,推荐使用mysql数据库,轻量并且性能良好。

    优点:就地取材,符合大多数人的思维,使用简单,不需要太多额外编码工作
    缺点:对mysql性能要求较高,访问mysql需要从连接池中获取连接,又因为大部分请求均需要进行登录鉴权,所以操作数据库非常频繁,当用户量达到一定程度之后,极易造成数据库瓶颈,不适用于处理高并发的情况。

    2.使用redis共享session。redis是一个key-value的储存系统。可以简单的将其理解为一个数据库,与传统数据库的区别是,它将数据储存于内存中,并自带有内存到硬盘的序列化策略,即按策略将内存中的数据同步到磁盘,避免数据丢失,是目前比较流行的解决方

    优点:无需增加数据库的压力,因为数据存储于内存中,所以读取非常快,高性能,并能处理多种类型的数据。
    缺点:额外增加一些编码,以便操作redis。

    3.使用memcache同步session,memcache可以实现分布式,可将服务器中的内存组合起来,形成一个“内存池”,以此充当公共空间,保存session信息。

    优点:数据储存在内存中,读取非常快,性能好;
    缺点:memcache把内存分成很多种规格的存储块,有大有小,不能完全利用内存,会产生内存碎片,浪费资源,如果储存块不足,还会产生内存溢出。

    4.通过脚本或守护进程在多台服务器之间同步session。

    优点:实现了session共享;
    缺点:对个人来说实现较为复杂,速度不稳定,有延时性,取决于现实中服务运行状态,偶然性较大,如果用于访问过快,可能出现session还没同步成功的情况。

    5.使用NFS共享session。NFS是Network File Server共享服务器的简称,最早由Sun公司为解决Unix网络主机间的目录共享而研发。选择一台公共的NFS做共享服务器,储存所有session数据,每台服务器所需的session均从此处获取

    优点:较好的实现了session共享;
    缺点:成本较高,对于个人来说难以实现。NFS依托于复杂的安全机制和文件系统,因此并发效率不高。

    6.使用Cookie共享session。此方案可以说是独辟蹊径了,将分布式思想用到了极致。如上文分析所说,session-cookie机制中,session与cookie相互关联,以cookie做中转站,用来找到对应的session,其中session存放在服务器。那么如果将session中的内容存放在cookie中呢,那么则省略了服务器保存session的过程,后台只需要根据cookie中约定的标识进行鉴权校验即可。

    优点:完美的贯彻分布式的理念,将每个用户都利用起来,无需耗费额外的服务器资源;
    缺点:受http协议头长度限制,cookie中存储的信息不宜过多;为了保持cookie全局有效,所以其一般依赖在根域名下,所以基本上所有的http请求都需要传递cookie中的这些标记信息,所以会占用一些服务器的带宽;鉴权信息全存储于cookie中,cookie存在于客户端,服务器并没有储存相关信息,cookie存在着泄露的可能,或则其他人揣摩出规则后可以进行伪装,其安全性比其他方案差,故需要对cookie中信息进行加密解密,来增强其安全性。

    ###转自参考腾讯https://cloud.tencent.com/developer/article/1143159
    本人菜鸟一枚,欢迎各位大佬指正批评。

    展开全文
  • 完整tomcat8内含session共享包,亲自测试可以使用,有问题可以私聊。
  • 分布式如何实现session共享 2017年02月25日 21:40:22 阅读数:34072  最近,在工作中遇到一个问题,问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那么用户...

     

    分布式如何实现session共享

    2017年02月25日 21:40:22

    阅读数:34072

             最近,在工作中遇到一个问题,问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那么用户在session失效之前如果再次使用app,那么可能会被路由到server2,这时问题来了,server没有该用户的session,所以需要用户重新登录,这时的用户体验会非常不好,所以我们想如何实现多台server之间共享session,让用户状态得以保存。

             当然业界已经有很多成熟的解决方案,我罗列如下:

    1.服务器实现的session复制或session共享,这类型的共享session是和服务器紧密相关的,比如webSphere或JBOSS在搭建集群时候可以配置实现session复制或session共享,但是这种方式有一个致命的缺点,就是不好扩展和移植,比如我们更换服务器,那么就要修改服务器配置。

    2.利用成熟的技术做session复制,比如12306使用的gemfire,比如常见的内存数据库如redis或memorycache,这类方案虽然比较普适,但是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。

    3.将session维护在客户端,很容易想到就是利用cookie,但是客户端存在风险,数据不安全,而且可以存放的数据量比较小,所以将session维护在客户端还要对session中的信息加密。

     

           我们实现的方案可以说是第二种方案和第三种方案的合体,可以利用gemfire实现session复制共享,还可以将session维护在redis中实现session共享,同时可以将session维护在客户端的cookie中,但是前提是数据要加密。这三种方式可以迅速切换,而不影响应用正常执行。我们在实践中,首选gemfire或者redis作为session共享的载体,一旦session不稳定出现问题的时候,可以紧急切换cookie维护session作为备用,不影响应用提供服务,下面我简单介绍方案中session共享的实现方式和原理。

            这里主要讲解redis和cookie方案,gemfire比较复杂大家可以自行查看gemfire工作原理。利用redis做session共享,首先需要与业务逻辑代码解耦,不然session共享将没有意义,其次支持动态切换到客户端cookie模式。redis的方案是,重写服务器中的HttpSession和HttpServletRequest,首先实现HttpSession接口,重写session的所有方法,将session以hash值的方式存在redis中,一个session的key就是sessionID,setAtrribute重写之后就是更新redis中的数据,getAttribute重写之后就是获取redis中的数据,等等需要将HttpSession的接口一一实现。

            实现了HttpSesson,那么我们先将该session类叫做MySession(当然实践中不是这么命名的),当MySession出现之后问题才开始,怎么能在不影响业务逻辑代码的情况下,还能让原本的request.getSession()获取到的是MySession,而不是服务器原生的session。这里,我决定重写服务器的HttpServletRequet,这里先称为MyRequest,但是这可不是单纯的重写,我需要在原生的request基础上重写,于是我决定在filter中,实现request的偷梁换柱,我的思路是这样的,MyRequest的构建器,必须以request作为参数,于是我在filter中将服务器原生的request(也有可能是框架封装过的request),当做参数new出来一个MyRequest,并且MyRequest也实现了HttpServletRequest接口,其实就是对原生request的一个增强,这里主要重写了几个request的方法,但是最重要的是重写了request.getSession(),写到这里大家应该都明白为什么重写这个方法了吧,当然是为了获取MySession,于是这样就在filter中,偷偷的将原生的request换成MyRequest了,然后再将替换过的request传入chan.doFilter(),这样filter时候的代码都使用的是MyRequest了,同时对业务代码是透明的,业务代码获取session的方法仍然是request.getSession(),但其实获取到的已经是MySession了,这样对session的操作已经变成了对redis的操作。这样实现的好处有两个,第一开发人员不需要对session共享做任何关注,session共享对用户是透明的;第二,filter是可配置的,通过filter的方式可以将session共享做成一项可插拔的功能,没有任何侵入性。

             这个时候已经实现了一套可插拔的session共享的框架了,但是我们想到如果redis服务出了问题,这时我们该怎么办呢,于是我们延续redis的想法,想到可以将session维护在客户端内(加密的cookie),当然实现方法还是一样的,我们重写HttpSession接口,实现其所有方法,比如setAttribute就是写入cookie,getAttribute就是读取cookie,我们可以将重写的session称作MySession2,这时怎么让开发人员透明的获取到MySession2呢,实现方法还是在filter内偷梁换柱,在MyRequest加一个判断,读取sessionType配置,如果sessionType是redis的,那么getSession的时候获取到的是MySession,如果sessionType是coolie的,那么getSession的时候获取到的是MySession2,以此类推,用同样的方法就可以获取到MySession 3,4,5,6等等。

             这样两种方式都有了,那么我们怎实现两种session共享方式的快速切换呢,刚刚我提到一个sessionType,这是用来决定获取到session的类型的,只要变换sessionType就能实现两种session共享方式的切换,但是sessionType必须对所有的服务器都是一致的,如果不一致那将会出现比较严重的问题,我们目前是将sessionType维护在环境变量里,如果要切换sessionType就要重启每一台服务器,完成session共享的转换,但是当服务器太多的时候将是一种灾难。而且重启服务意味着服务的中断,所以这样的方式只适合服务器规模比较小,而且用户量比较少的情况,当服务器太多的时候,务必需要一种协调技术,能够让服务器能够及时获取切换的通知。基于这样的原因,我们选用zookeeper作为配置平台,每一台服务器都会订阅zookeeper上的配置,当我们切换sessionType之后,所有服务器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。

             方案大体分享完了,在此将我的收获记录下来,我们已经实现了一个版本,大家也可以发挥想象,分享不同的方案,我们一起进步,文中有不足之处,还望各位不吝赐教。

    原文:https://blog.csdn.net/sxiaobei/article/details/57086489

     

    集群间实现Session共享

    置顶2017年08月24日 17:21:02

    阅读数:5420

    上一篇,同一tomcat不同项目下session共享方案:http://blog.csdn.net/qinmengdecluntan/article/details/72832648

    一、引言

    针对企业,为了应对庞大的用户访问压力,目前大多数大型网站服务器都采用集群部署的方式;针对个人,仅一台服务器而言,也会安装多个tomcat进行错时更新,保证更新后台业务时服务不断开,即模拟了集群的运行方式。在此集群中,我们就不得不考虑一个用户鉴权的问题,即在不同服务上如何保证用户均已登录,并能获取相同的用户登录信息。

    二、Java Web推荐的(公认的)用户鉴权机制

    说此部分之前先了解几个概念: 
    1.请求,即Request,指客户端向服务器发送的信息,通常是通信的发起方; 
    2.响应,即Response,指服务器对请求的应答,通常是通信的回复方; 
    3.会话,即Session,服务器可将请求<->响应这一个完整的过程称为一次会话,并为这次会话生成一个唯一的标识符,即sessionId,用来表示这次会话,Session储存在服务器端; 
    4.Cookie,客户端保存在本地终端的数据,即Cookie储存在客户端。

    Java Web的共用的用户鉴权机制是采用Session-Cookie技术,实现原理是:用户登录时,请求到达服务器,服务器调用通过getSession()方法判断session是否存在,如果不存在,则新建session,并通过其算法为session生成一个随机数作为sessionId,开发者可在session中储存一些用户信息;第二次请求时,如获取用户信息,getSession()方法判断session存在,则取出session,而不是新建,从而从session中获取到用户的相关信息。

    客户端请求时,可以将cookie信息储存于request的head中发送给服务器; 
    服务器响应时,可以将cookie信息置于response中回传给客户端。 
    如下图代表,名称为test的cookie其值为aaa: 
    这里写图片描述

    那么getSession()里究竟做了什么?

    1.第一次用户请求,客户端本地没有任何数据,即其cookie为空,朝服务器发送request,getSession()中会解析request,发现其约定的cookie为null,则认为没有session,所以会重新创建一个session对象;

    2.创建session后会将此session的id放入response中,回传给客户端,客户端则保存response中的cookie;

    3.再次请求,服务器getSession()又会重新解析request获取cookie,发现了其中的sessionId,那么根据此sessionId去服务器的中去找,则得到了上次创建的session对象,那么则认为鉴权成功。

    如此,便完成了鉴权的整个流程,Java逻辑代码(伪代码)如下:

    public HttpSession getSession() {
        //从request中解析cookie
        HttpSession session = null;
        Cookie[] cookies = getRequest().getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("JSESSIONID")) {
                    String sessionId = cookie.getValue();
                    session = //根据sessionId获取内存中的session对象
                }
            }
        }
        if (session == null) {
            session = //创建一个新的session对象
        }
    
        //通过response将cookie返回
        Cookie cookie = new Cookie("JSESSIONID", session.getId());
        getResponse().addCookie(cookie);
    
        return session;
    }

    如上,java中将sessionId在cookie中保存的名称叫做“JSESSIONID”,即“Java Session Id”之意,打开浏览器可以看到类型的信息,如图: 
    这里写图片描述

    三、集群间如何实现session共享

    按照前文所说的session-cookie机制,session是保存在每台服务器的,但在集群中,拥有多台服务器,每台各自为政,势必会造成在这台服务器中登录,获取session成功,但是到另一台服务器上,又会获取不到session,造成鉴权失败,这样对用户来说是极不友好的,那么怎么解决这个问题呢?

    通过我们以上的分析,即可得出几种处理方式: 
    A.找一块公共的空间用来储存session,而不是将session储存在集群节点的某台服务器上,此时,每一台服务器都能访问这块空间,从而实现session共享;

    B.仍在每台服务器上保存session信息,不作修改,但采用另一种同步机制,实时同步没一台服务器的session信息;

    C.构建一种全新的鉴权机制,不采用session-cookie机制,但要去除此鉴权机制对单个服务器的依赖。

    综上所述,列举几种的具体实现方案:


    1.持久化session到数据库,即使用数据库来储存session。数据库正好是我们普遍使用的公共储存空间,一举两得,推荐使用mysql数据库,轻量并且性能良好。

    优点:就地取材,符合大多数人的思维,使用简单,不需要太多额外编码工作 
    缺点:对mysql性能要求较高,访问mysql需要从连接池中获取连接,又因为大部分请求均需要进行登录鉴权,所以操作数据库非常频繁,当用户量达到一定程度之后,极易造成数据库瓶颈,不适用于处理高并发的情况。


    2.使用redis共享session。redis是一个key-value的储存系统。可以简单的将其理解为一个数据库,与传统数据库的区别是,它将数据储存于内存中,并自带有内存到硬盘的序列化策略,即按策略将内存中的数据同步到磁盘,避免数据丢失,是目前比较流行的解决方案。

    优点:无需增加数据库的压力,因为数据存储于内存中,所以读取非常快,高性能,并能处理多种类型的数据。 
    缺点:额外增加一些编码,以便操作redis。


    3.使用memcache同步session,memcache可以实现分布式,可将服务器中的内存组合起来,形成一个“内存池”,以此充当公共空间,保存session信息。

    优点:数据储存在内存中,读取非常快,性能好; 
    缺点:memcache把内存分成很多种规格的存储块,有大有小,不能完全利用内存,会产生内存碎片,浪费资源,如果储存块不足,还会产生内存溢出。


    4.通过脚本或守护进程在多台服务器之间同步session。

    优点:实现了session共享; 
    缺点:对个人来说实现较为复杂,速度不稳定,有延时性,取决于现实中服务运行状态,偶然性较大,如果用于访问过快,可能出现session还没同步成功的情况。


    5.使用NFS共享session。NFS是Network File Server共享服务器的简称,最早由Sun公司为解决Unix网络主机间的目录共享而研发。选择一台公共的NFS做共享服务器,储存所有session数据,每台服务器所需的session均从此处获取。

    优点:较好的实现了session共享; 
    缺点:成本较高,对于个人来说难以实现。NFS依托于复杂的安全机制和文件系统,因此并发效率不高。


    6.使用Cookie共享session。此方案可以说是独辟蹊径了,将分布式思想用到了极致。如上文分析所说,session-cookie机制中,session与cookie相互关联,以cookie做中转站,用来找到对应的session,其中session存放在服务器。那么如果将session中的内容存放在cookie中呢,那么则省略了服务器保存session的过程,后台只需要根据cookie中约定的标识进行鉴权校验即可。

    优点:完美的贯彻分布式的理念,将每个用户都利用起来,无需耗费额外的服务器资源; 
    缺点:受http协议头长度限制,cookie中存储的信息不宜过多;为了保持cookie全局有效,所以其一般依赖在根域名下,所以基本上所有的http请求都需要传递cookie中的这些标记信息,所以会占用一些服务器的带宽;鉴权信息全存储于cookie中,cookie存在于客户端,服务器并没有储存相关信息,cookie存在着泄露的可能,或则其他人揣摩出规则后可以进行伪装,其安全性比其他方案差,故需要对cookie中信息进行加密解密,来增强其安全性。


    在此,我们将选择方案2使用redis来具体实现集群下的session共享。

    四、搭建测试环境

    1.为模拟集群环境,需要两台服务器或在一台服务器上安装两个tomcat; 
    2.使用nginx做集群纷发; 
    3.安装redis充当公共的空间存储session; 
    4.框架中编写session储存业务,因为需要使用java操作redis,redis提供了驱动包jedis,故需要掌握jedis进行操作。

    五、详细部署

    5.1 安装多个tomcat 
    怎么安装tomcat此处不作说明,只说明安装额外的tomcat,本人原安装的tomcat目录为apache-tomcat-7.0.77

    1.拷贝apache-tomcat-7.0.77为apache-tomcat-7.0.77_2 
    2.修改apache-tomcat-7.0.77_2下conf中server.xml文件端口号 
    ,共三处,将每处在原端口号port之上加1,确保两个tomcat不会共用端口,如下:

    <Server port="8006" shutdown="SHUTDOWN">
    
    <Connector port="8081" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
    
    <Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />

    5.2 更改nginx配置,模拟集群 
    修改nginx配置文件nginx.conf文件,在server闭包外添加upstream,由上可知两个tomcat端口号分别为8080,8081

    #建立集群
    upstream not_alone {
        server localhost:8080;
        server localhost:8081;
    }
    
    # 转发请求到tomcat下mate项目
    location / {
        proxy_pass http://not_alone/mate/; 
    }

    5.2 redis安装与配置

    1.下载,官网:https://redis.io/download 
    2.安装,以4.0.1版本为例

    $ wget http://download.redis.io/releases/redis-4.0.1.tar.gz
    $ tar xzf redis-4.0.1.tar.gz
    $ cd redis-4.0.1
    $ make

    3.启动

    $ src/redis-server
    •  

    4.关闭

    ctrl + c

    5.配置后台启动(redis默认是前台启动,启动成功后界面就持续停止在那个界面上,这对服务器操作很不方便) 
    这里写图片描述

    #修改其配置文件
    vim redis.conf
    
    将daemonize no改为daemonize yes
    
    #保存退出
    :wq!

    如下图: 
    这里写图片描述

    6.后台启动

    src/redis-server redis.conf

    如图: 
    这里写图片描述

    7.关闭 
    杀掉redis进程,如图: 
    这里写图片描述

    8.为redis配置系统服务,本人使用的系统是CentOS 7,需要配置使用systemctl进行管理。 
    /lib/systemd/system目录下创建文件redis.service,并编辑:

    #表示服务信息
    [Service]
    Type=forking
    #注意:需要和redis.conf配置文件中的信息一致
    PIDFile=/var/run/redis_6379.pid
    #启动服务的命令
    #redis-server安装的路径 和 redis.conf配置文件的路径
    ExecStart=/server/soft/redis-4.0.1/src/redis-server /server/soft/redis-4.0.1/redis.conf
    #重新加载命令
    ExecReload=/bin/kill -s HUP $MAINPID
    #停止服务的命令
    ExecStop=/bin/kill -s QUIT $MAINPID
    PrivateTmp=true
    
    #安装相关信息
    [Install]
    #以哪种方式启动
    WantedBy=multi-user.target
    #multi-user.target表明当系统以多用户方式(默认的运行级别)启动时,这个服务需要被自动运行。

    更多redis systemctl详细配置,请看:http://blog.csdn.net/u011389474/article/details/72303156

    配置成功,启动完成后,通过服务可知其运行状态,如图: 
    这里写图片描述

    至此,redis已全部安装部署完成。

    六、编写代码实现功能

    为了测试简便,后台web框架我选择的是JFinal,JFinal是中国开源社区中广受好评的后台轻量级极速web框架,因其操作简单,设计灵活而被大多数开发者所喜爱,有兴趣的朋友可以试试,用一次之后你就会喜欢它的,JFinal社区:http://www.jfinal.com/

    这里用JFianl的另一个好处就是JFinal核心库中自带Redis插件,集成了jedis的各种使用方法,这样就不用自己去编写了,省了很大的代码量。Jedis基本操作:http://www.cnblogs.com/edisonfeng/p/3571870.html

    为帮助理解代码,Jfinal中连接redis,只需要在主配置文件中编写:

    /**
     * 插件配置
     */
    @Override
    public void configPlugin(Plugins me) {
        /*
        * Redis配置:连接本地的mate redis库,端口号默认
        */
        RedisPlugin rp = new RedisPlugin("mate", "localhost");
        me.add(rp);
    }

    redis存取数据:

    Cache cache = Redis.use();
    
    //存
    cache.set(key, value);
    
    //取
    Object value  = cache.get(key);
    
    //设置redis过期时间
    cache.pexpire(key, time);

    正式代码如下,我们将会自定义session,每个sesison对象都是唯一的,需要给每个session分配一个唯一id,id生成算法,则可以借用UUID实现,UUID相关介绍:https://baike.baidu.com/item/UUID/5921266?fr=aladdin 
    自定义随机数工具类:

    /**
     * 随机数工具类
     * @author alone
     */
    public class RandomUtils {
        /**
         * 获取唯一的可辨识资讯UUID
         * UUID为128位二进制,每4位二进制=16进制,其添加了四个'-',故总长为36位
         * @param 是否删除标记
         * @return UUID
         */
        public static String getUUID(boolean rmtag) {
            String uuid = UUID.randomUUID().toString();
            if (rmtag) {
                uuid = uuid.replace("-", "");
            }
            return uuid;
        }
    }

    自定义RedisSession类,将替代原来的HttpSession:

    package com.alone.mate.common;
    
    import java.io.Serializable;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.alone.mate.utils.RandomUtils;
    import com.jfinal.plugin.redis.Cache;
    import com.jfinal.plugin.redis.Redis;
    
    /**
     * 自定义ResidSession解决集群会话共享
     * @author alone
     */
    @SuppressWarnings("serial")
    public class RedisSession implements Serializable {
    
        private String id;
        private SessionType type;
        private long createTime;
        private long destroyTime;
        private Map<String, Object> attrs;
    
        /**
         * 会话类型,不同类型的会话其有效期不同
         * @author alone
         */
        public enum SessionType {
    
            /**
             * 移动端,类型为1,会话有效期为一周
             */
            MOBILE(1, 1000 * 60 * 60 * 24 * 7),
            /**
             * 网页端,类型为2,会话有效期为半小时
             */
            BROWSER(2, 1000 * 60 * 30);
    
            private int type;
            private int value;
    
            private SessionType(int type, int value) {
                this.type = type;
                this.value = value;
            }
    
            public int getType() {
                return type;
            }
    
            public int getValue() {
                return value;
            }
    
            public static SessionType getSessionType(int type) {
                for (SessionType st : SessionType.values()) {
                    if (st.type == type) {
                        return st;
                    }
                }
                return null;
            }
        }
    
        public RedisSession(int sessionType) {
            this.id = RandomUtils.getUUID(true);
            this.type = SessionType.getSessionType(sessionType);
            this.createTime = System.currentTimeMillis();
            this.destroyTime = this.createTime + this.type.value;
            this.attrs = new HashMap<>();
        }
    
        public Object getAttribute(String key) {
            return attrs.get(key);
        }
    
        public void setAttribute(String key, Object value) {
            attrs.put(key, value);
            Cache cache = Redis.use();
            cache.set(this.getId(), this);
            cache.pexpire(this.getId(), this.getDestroyTime() - System.currentTimeMillis());//set后会将生存时间清零,需要重新设置有效期
        }
    
        public void removeAttribute(String key) {
            attrs.remove(key);
            Cache cache = Redis.use();
            cache.set(this.getId(), this);
        }
    
        public String getId() {
            return id;
        }
    
        public SessionType getType() {
            return type;
        }
    
        public long getCreateTime() {
            return createTime;
        }
    
        public long getDestroyTime() {
            return destroyTime;
        }
    
        public void setDestroyTime(long destroyTime) {
            this.destroyTime = destroyTime;
        }
    }
    

    仿造getSession()实现逻辑在控制器基类BaseController中自定义getSession()方法,获取RedisSession:

    public class BaseController extends Controller {
    
        private static final Logger logger = Logger.getLogger(BaseController.class);
        private String sessionId;
    
        /**
         * 获取RedisSession
         * @param sessionType 会话类型
         * @return
         */
        @SuppressWarnings("null")
        public RedisSession getSession(int sessionType) {
            boolean isReload = false;
            long now = System.currentTimeMillis();
            RedisSession session = null;
            Cache cache = Redis.use();
            if (sessionId == null) {
                Cookie[] cookies = getRequest().getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if (cookie.getName().equals("JSESSIONID")) {//查看请求中是否有对应的Cookie记录
                            sessionId = cookie.getValue();//本地记录此次请求的sessionId,防止在初次请求时后台多次获取session,获取的session均不同
                        }
                    }
                }
            }
            if (sessionId != null) {
                session = cache.get(sessionId);//如果有,从redis中取出对应的Session
                if (session != null) {
                    if (session.getType() == RedisSession.SessionType.BROWSER) {
                        session.setDestroyTime(now + RedisSession.SessionType.BROWSER.getValue());//若会话类型为浏览器则刷新其会话有效期
                        isReload = true;
                        logger.info("刷新会话时间,JSESSIONID:" + session.getId() + ",延长:" + RedisSession.SessionType.BROWSER.getValue()/1000/60/60.0 + "小时");
                    }
                    if (session.getDestroyTime() < now) {//若会话过期,从redis中删除
                        cache.del(session.getId());
                        session = null;
                        logger.info("删除过期会话,JSESSIONID:" + session.getId());
                    }
                }
            }
    
            if (session == null) {
                session = new RedisSession(sessionType);//若请求中没有对应Cookie记录,创建新的session
                sessionId = session.getId();//本地记录此次请求的sessionId,防止在初次请求时后台多次获取session,获取的session均不同
                isReload = true;
                logger.info("创建新会话,JSESSIONID:" + session.getId() + ",有效时间:" + (session.getDestroyTime() - now)/1000/60/60.0 + "小时");
            }
            if (isReload) {//session生命周期发生变化,需要重新redis中存储数据
                cache.set(session.getId(), session);//将session存入redis中
                cache.pexpire(session.getId(), session.getDestroyTime() - now);//设定redis数据储存有效期
            }
    
            Cookie cookie = new Cookie("JSESSIONID", session.getId());
            cookie.setPath("/");
            cookie.setHttpOnly(true);
            getResponse().addCookie(cookie);//将cookie返回
    
            return session;
        }
    }
    

    说明: 
    以上代码中,设想服务器给移动端和网页端同时提供服务,为了优化,但我希望移动端不需要频繁登录,就像微信一样,我将这个时间暂设一周;而网页端的话,session生存周期较短,只有半个小时,并且每次鉴权都刷新其可用时间,移动端只倒计时就可以了,一周登录一次就可以了。redis自带有过期策略,可以很好的实现这一点,同时为了保险起见,也手动验证了一下如过期,进行删除。为了避免初次请求时,多次调用getSession()生成多个session,故在创建session成功后记录其sessionId,再次调用getSession()时可对其进行验证。

    七、结果测试

    1.在Controller中编写两个接口,一为登录接口,登录成功,储存用户uid;二为验证登录接口,获取登录信息:

    public void redisLogin() {
        RedisSession session = getSession(RedisSession.SessionType.BROWSER.getType());
        session.setAttribute("uid", 1);
        renderText("登录成功,sessionId:" + session.getId());
    }
    
    
    public void redisCheckLogin() {
        RedisSession session = getSession(RedisSession.SessionType.BROWSER.getType());
        int uid = (int) session.getAttribute("uid");
        renderText("sessionId:" + session.getId() + ", uid: " + uid);
    }

    2.配置nginx分别跳转到不同tomcat下的不同接口

    #测试登录接口跳到8080
    location = /tomcat1 {
        proxy_pass http://localhost:8080/mate/test/redisLogin;
    }
    
    #校验登录接口跳到8081
    location = /tomcat2 {
        proxy_pass http://localhost:8081/mate/test/redisCheckLogin;
    }

    3.开启redis,nginx,两个tomcat下运行同样的项目,在浏览器中调用接口进行测试。

    调用tomcat1的登录接口 
    这里写图片描述 
    日志: 
    这里写图片描述

    调用tomcat2的登录接口 
    这里写图片描述 
    日志: 
    这里写图片描述

    可以看到,两个tomcat中的信息完全一样,很好的达到了我们预计的效果。

    到这里,本篇的内容也已经到了尾声,写的有点啰嗦,不过总算交代了来龙去脉,虽然有点累,但好歹写完了。未来还有很多工作要做,路漫漫其修远兮,吾将上下而求索。

    https://blog.csdn.net/qinmengdeCluntan/article/details/77532883?locationNum=5&fps=1

     

    展开全文
  • 完美实现分布式集群Session共享 简单多tomcat8+redis的session共享实现,支持tomcat8、tomcat8.5、tomcat9,不能用直接联系我积分双倍返回。
  • [Session共享]Tomcat集群实现Session共享

    前言

    实现Session共享的方法有很多种,利用redis、mysql或Tomcat等均可实现Session共享,本次是使用Tomcat实现Session共享。但此方案也有弊端,仅仅作用于Tomcat,以后会继续更新文章,推出其他解决方案。

    环境准备
    1、两个Tomcat
    2、两个项目

    首先我们简单配置一下项目,在index.jsp中添加如下测试代码,用来测试服务器间的的Session是否同步。

      <body>
            SessionID:<%=session.getId()%>  
            <BR>  
            SessionIP:<%=request.getServerName()%>  
            <BR>  
            SessionPort:<%=request.getServerPort()%>  
      </body>

    然后我们需要配置Tomcat的server.xml

    <Engine name="Catalina" defaultHost="localhost">

    在这段代码下面添加如下代码

            <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"  
                    channelSendOptions="8">  
    
             <Manager className="org.apache.catalina.ha.session.DeltaManager"  
                      expireSessionsOnShutdown="false"  
                      notifyListenersOnReplication="true"/>  
    
             <Channel className="org.apache.catalina.tribes.group.GroupChannel">  
               <Membership className="org.apache.catalina.tribes.membership.McastService"  
                           address="228.0.0.4"  
                           port="45564"  
                           frequency="500"  
                           dropTime="3000"/>  
               <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"  
                         address="auto"  
                         port="4000"  
                         autoBind="100"  
                         selectorTimeout="5000"  
                         maxThreads="6"/>  
    
               <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">  
               <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>  
               </Sender>  
               <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>  
               <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>  
             </Channel>  
    
             <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"  
                    filter=""/>  
             <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>  
    
             <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"  
                       tempDir="/tmp/war-temp/"  
                       deployDir="/tmp/war-deploy/"  
                       watchDir="/tmp/war-listen/"  
                       watchEnabled="false"/>  
    
             <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>  
             <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>  
           </Cluster>  

    例如:

    <Engine name="Catalina" defaultHost="localhost">
        <Cluster>
        </Cluster>
    <Engine>

    两个Tomcat配置完毕后,我们修改一下两个项目的web.xml,添加

    <distributable/>

    例如:

    <web-app>
        <display-name>spring</display-name>
        <distributable />
        <welcome-file-list>
            <welcome-file>index.html</welcome-file>
            <welcome-file>index.htm</welcome-file>
            <welcome-file>index.jsp</welcome-file>
            <welcome-file>default.html</welcome-file>
            <welcome-file>default.htm</welcome-file>
            <welcome-file>default.jsp</welcome-file>
        </welcome-file-list>
    </web-app>

    接下来我们启动两个项目测试一下

    1

    2

    至此Session已经实现了Tomcat集群间的共享

    展开全文
  • [Session共享]Spring-Redis实现Session共享

    千次阅读 2017-10-11 17:15:56
    [Session共享]Spring-Redis实现Session共享
  • 但是分布式部署的时候,我们请求的服务器可能不是同一台服务器,那么我们就必须要面对 session 共享的问题,下面介绍的是在 SpringBoot 实现 session 共享的方式 一、创建项目 创建 SpringBoot 项目,选择 Maven ...
  • nginx实现session共享

    千次阅读 多人点赞 2021-01-14 16:17:43
    nginx实现session共享
  • Spring Session + redis实现session共享
  • session共享方案(tomcat8+redis共享session) 一步一步详细指引
  • Spring Session解决Session共享

    万次阅读 多人点赞 2019-05-16 17:55:58
    对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是Session,服务端...
  • 实现session共享的三种方式发布时间:2020-06-05 16:16:43来源:亿速云阅读:341作者:Leah这篇文章为大家带来有关实现session共享的三种方式的详细介绍。大部分知识点都是大家经常用到的,为此分享给大家做个参考。...
  • Tomcat8+redis实现session共享 Tomcat8+redis实现session共享
  • WAS session共享

    2013-05-14 22:49:25
    WAS中session共享设置,解决session复制问题
  • session共享怎么做的(分布式如何实现session共享)? 问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那 么用户在 session 失效之前如果再次使用 app,那么可能...
  • 主要介绍了Spring boot集成spring session实现session共享的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Redis作session共享

    2021-04-07 17:09:23
    Redis作session共享 在大多数项目中,用户请求会经由负载均衡分发到不同的后端服务器上,如果不做session共享,那么用户的请求被打倒不同服务器上时就会发生session丢失。springboot提供了自动化的session共享配置,...
  • session共享问题

    2019-06-04 17:51:36
    一、session共享有哪些解决方案? 1、使用spring-session+redis解决 2、使用负载均衡策略ip绑定 3、使用cookie(不安全) 4、使用数据库(增加了对数据库的压力) 5、tomcat配置session共享 6、toke...
  • spring-session实现session共享

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,191
精华内容 6,476
关键字:

session共享