应用场景
我们知道HTTP协议本身是无状态的,因此在使用HTTP协议进行通信的过程中,需要借助Session机制进行状态的保持。然而在大型网站中,我们的服务器数量通常不止一台,可能是几十台甚至几百台之多,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,再路由到具体的服务器上,由于Session默认是存储在单机服务器内存中的,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得Session机制在分布式环境下失效。
cookie:cookie是本地客户端用来存储少量数据信息的,保存在客户端,用户能够很容易的获取,安全性不高,存储的数据量小。
- Cookie的Domain和Path属性标识了这个Cookie是哪个网站发送给浏览器的;
- Cookie的Expire属性标识了Cookie的有效时间。
- 如果不设置过期时间,则表示这个Cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。即会话cookie,会话cookie保存在内存里;设置了expire的cookie保存在硬盘上,保存在硬盘上的cookie可以在不同的浏览器进程间共享。
- 存储在服务器的内存中,tomcat的StandardManager类将session存储在内存中,也可以持久化到file,数据库,memcache,redis等。
- 客户端只保存sessionid到cookie中,而不会保存session,session销毁只能通过invalidate或超时,关掉浏览器并不会关闭session。
session:session是服务器用来存储部分数据信息,保存在服务器,用户不容易获取,安全性高,储存的数据量相对大,存储在服务器,会占用一些服务器资源。
- 当浏览器第一次发送请求时,服务器自动生成一个hashtable和一个sessionID,然后通过响应发送到浏览器。tomcat生成的sessionid叫做jsessionid。
- 当浏览器第二次发送请求时,将sessionID放在请求中一并发送到服务器上,然后取出sessionID,找到hashtable 对比。
- session在访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid;
分布式解决方案
(1)Session复制
- 支持session复制的web服务器上,通过修改服务器配置同步到其他web服务器,使各web服务器session保持一致。
- session同步的原理是在同一个局域网里面通过发送广播来异步同步session的,一旦服务器多了,并发上来了,session需要同步的数据量就大了,需要将其他服务器上的session全部同步到本服务器上,会带来一定的网路开销,在用户量特别大的时候,会出现内存不足的情况。
- Tomcat内部已经支持分布式架构开发管理机制,可以对tomcat修改配置来支持session复制,在集群中的几台服务器之间同步session对象,使每台服务器上都保存了所有用户的session信息,这样任何一台本机宕机都不会导致session数据的丢失,而服务器使用session时,也只需要在本机获取即可。
- 在Tomcat安装目录下的config目录中的server.xml文件中,将注释打开,tomcat必须在同一个网关内,要不然收不到广播,同步不了session。distributable。
- 使用tomcat内置的session同步(同步可能会产生延迟)。
(2)Session粘贴。
- 同一个用户的每次请求分发到某一web服务器,只要这一web服务器存储了session数据,便能实现会话跟踪。
- 可以基于nginx的ip-hash策略,可以对客户端和服务器进行绑定,同一个客户端就只能访问该服务器,无论客户端发送多少次请求都被同一个服务器处理
- 容易造成单点故障,如果有一台服务器宕机,那么该台服务器上的session信息将会丢失
前端不能有负载均衡,如果有,session绑定将会出问题。 - 使用Nginx中的ip绑定策略,同一个ip只能在指定的同一个机器访问(不支持负载均衡)。
(3)Session集中管理
- 使用缓存技术,如使用redis存储session数据,集中管理所有的session,所有web服务器都能从redis存储介质中找到对应的session,实现session共享。
- spring为我们封装好了spring-session,直接引入依赖即可。数据保存在redis中,无缝接入,不存在任何安全隐患。redis自身可做集群,搭建主从,同时方便管理。
- 使用spring-session以及集成好的解决方案,存放在Redis中。
- @EnableRedisHttpSession:开启Session共享功能。
- maxInactiveIntervalInSeconds: 设置 Session 失效时间。使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。
- 当请求进来的时候,SessionRepositoryFilter会先拦截到请求,将request和response对象转换成 SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper。后续当第一次调用request的getSession方法时,会调用到SessionRepositoryRequestWrapper的getSession方法。这个方法的逻辑是先从request的属性中查找,如果找不到;再查找一个key值是"SESSION"的cookie,通过这个cookie拿到sessionId去redis中查找,如果查不到,就直接创建一个RedisSession对象,同步到Redis中(同步的时机根据配置来)。
(4)基于Cookie管理
- 用户每次发送请求,将session保存到cookie中发送到服务器。客户端通过http协议和服务器进行cookie交互,通常用来存储一些不敏感信息。
- 数据存储在客户端,存在安全隐患;cookie存储大小、类型存在限制;数据存储在cookie中,如果一次请求cookie过大,会给网络增加更大的开销。
(5)使用token代替session
spring session+redis实现共享
(1)安装redis服务
- redis官方下载地址:https://github.com/microsoftarchive/redis/releases
- 修改密码:打开redis.windows.conf文件,找到# requirepass foobared这一行去掉注释,foobared替换成自己的密码,保存。
- 运行服务:redis-server.exe redis.windows.conf
- 运行客户端:redis-cli.exe -h 127.0.0.1 -a 密码
(2)安装nginx
- 链接:https://pan.baidu.com/s/1BZ1kgJbt-zdcZtKOGlBvbQ 提取码:ig72
- 启动:start nginx
- 关闭:nginx -s stop
- 修改配置文件重启动:nginx -s reload
(3)添加依赖
- spring-boot-starter-data-redis依赖于spring-data-redis 和 lettuce 。Spring
Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X
切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。 - Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。
- Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案。Spring Session 提供了集群 Session(Clustered Sessions)功能,默认采用外置的 Redis 来存储 Session 数据,以此来解决 Session 共享的问题。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
(3)配置文件
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
# 连接池最大连接数
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
spring.session.store-type=redis
(4)session配置类
- maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
(5)测试
- 添加测试方法获取 sessionid
@RequestMapping("/uid")
String uid(HttpSession session) {
UUID uid = (UUID) session.getAttribute("uid");
if (uid == null) {
uid = UUID.randomUUID();
}
session.setAttribute("uid", uid);
return session.getId();
}
- 登录 Redis 输入 keys ‘sessions’
- expirations:失效时间
- sessions:sessionId
t<spring:session:sessions:db031986-8ecc-48d6-b471-b137a3ed6bc4
t(spring:session:expirations:1472976480000
(6)服务器共享
按照上面的步骤在另一个项目中再次配置一次,启动后自动就进行了 Session 共享。
Tomcat内置Session复制方案
(1)修改server.xml,在Host节点下添加如下Cluster节点
<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" />
<!-- 这里如果启动出现异常,则可以尝试把address中的"auto"改为"localhost" -->
<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.MessageDispatchInterceptor" />
</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.ClusterSessionListener" />
</Cluster>
nginx Ip绑定策略
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,从而解决session共享的问题。
upstream backserver {
ip_hash;
server 192.168.0.14:88;
server 192.168.0.15:80;
}