分布式解决方案-分布式session一致性问题


如未做特殊说明,本文均为原创,转载请注明出处

前言
上篇文章有介绍到,在分布式架构中,会出现很多分布式问题,本文将要概述的就是分布式Session一致性的问题。

​ Session一致性:服务器集群Session共享问题

那么首先剖析下session到底是什么鬼。。。

什么是Session

​ session 是一种服务端的会话机制。(被称为域对象)作为范围是一次会话的范围。

​ 服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。

​ Web开发中,web-server可以自动为同一个浏览器的访问用户自动创建session,提供数据存储功能。最常见的,会把用户的登录信息、用户信息存储在session中,以保持登录状态。

那么Session为什么会不一致呢?

​ 在基于请求与响应的HTTP通讯中,当第一次请求来时,服务器端会接受到客户端请求,会创建一个session,使用响应头返回sessionid给客户端。浏览器获取到sessionid后会保存到本地cookie中。

分布式解决方案-分布式session一致性问题_第1张图片
第一次请求

​ 当第二次请求来时,客户端会读取本地的sessionid,存放在请求头中,服务端在请求头中获取对象的sessionid在本地session内存中查询。

分布式解决方案-分布式session一致性问题_第2张图片
第二次请求
// 默认创建一个session,默认值为true,如果没有找到对象的session对象,就会创建该对象,并且将生成的sessionid 存入到响应头中。
HttpSession session = request.getSession();

    @Override
    public HttpSession getSession() {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return getSession(true);
    }
// 默认情况下就是true,如果session不存在,则创建一个存入到本地,
// 假设修改为false会是什么样子的呢,就会关闭session功能。

但是session属于会话机制,当当先会话结束时,session就会被销毁,并且web程序会为每一次不同的会话创建不同的session,所以在分布式场景下,即使是调用同一个方法执行同样的代码,但是他们的服务器不同,自然web程序不同,整个上下文对象也不同,理所当然session也是不同的。

分布式Session的诞生

​ 单服务器web应用中,session信息只需存在该服务器中,这是我们前几年最常接触的方式,但是近几年随着分布式系统的流行,单系统已经不能满足日益增长的百万级用户的需求,集群方式部署服务器已在很多公司运用起来,当高并发量的请求到达服务端的时候通过负载均衡的方式分发到集群中的某个服务器,这样就有可能导致同一个用户的多次请求被分发到集群的不同服务器上,就会出现取不到session数据的情况,于是session的共享就成了一个问题。

分布式解决方案-分布式session一致性问题_第3张图片
session一致性问题的产生

如上图,假设用户包含登录信息的session都记录在第一台web-server上,反向代理如果将请求路由到另一台web-server上,可能就找不到相关信息,而导致用户需要重新登录。

Session一致性解决方案

1.session复制(同步)Tomcat自带该功能

分布式解决方案-分布式session一致性问题_第4张图片
session复制

思路:多个web-server 之间相互同步session,这样每个web-server之间都包含全部的session

优点web-server 支持的功能,应用程序不需要修改代码

不足

  • session 的同步需要数据传输,占内网带宽,有时延
  • 所有web-server 都包含所有session数据,数据量受内存限制,无法水平扩展
  • 有更多web-server 时要歇菜

2.客户端存储法

思路:服务端存储所有用户的session,内存占用较大,可以将session存储到浏览器cookie中,每个端只要存储一个用户的数据了

优点:服务端不需要存储

缺点

  • 每次http请求都携带session,占外网带宽
  • 数据存储在端上,并在网络传输,存在泄漏、篡改、窃取等安全隐患
  • session存储的数据大小受cookie限制

这种方式,虽然不是很常用,但也可行。

3.反向代理hash一致性

思路web-server为了保证高可用,有多台冗余,反向代理层能不能做一些事情,让同一个用户的请求保证落在一台web-server 上呢?

使用Nginx的负载均衡算法其中的hash_ip算法将ip固定到某一台服务器上,这样就不会出现session共享问题,因为同一个ip访问下,永远是同一个服务器。

缺点:失去了Nginx负载均衡的初心。

优点

  • 只需要改nginx配置,不需要修改应用代码
  • 负载均衡,只要hash 属性是均匀的,多台web-server的负载是均衡的
  • 可以支持web-server水平扩展(session 同步法是不行的,受内存限制)

不足

  • 如果web-server重启,一部分session会丢失,产生业务影响,例如部分用户重新登录
  • 如果web-server水平扩展,rehashsession重新分布,也会有一部分用户路由不到正确的session

4.后端统一集中存储

分布式解决方案-分布式session一致性问题_第5张图片
将Session存储到数据库或者Redis中

思路:将session存储在web-server后端的存储层,数据库或者缓存

优点

  • 没有安全隐患
  • 可以水平扩展,数据库/缓存水平切分即可
  • web-server重启或者扩容都不会有session丢失

不足:增加了一次网络调用,并且需要修改应用代码

对于db存储还是cache,个人推荐后者:session读取的频率会很高,数据库压力会比较大。如果有session高可用需求,cache可以做高可用,但大部分情况下session可以丢失,一般也不需要考虑高可用。

方案:使用Spring Session框架,相当于将Session之缓存到Redis中。

问:在项目发布的时候,Session如何控制不会失效的?

答:使用缓存框架,缓存Session的值(这里可以使用Redis加上EhCache实现一级和危机缓存)

5.使用Token的方式代替Session功能

​ 在移动端,是没有Session这个概念的,都是使用Token的方式来实现的。

token最终会存放到Redis中,redis-cluster分片集群中是默认支持分布式共享的。完美的解决的共享问题。

推荐使用 4、5方式。

使用Spring Session实现Session一致性

​ Spring Session 可以零侵入的解决Session一致性的问题。

Spring-Session实现Session共享实现原理以及源码解析

实现原理这里简单说明描述:

就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

/**
 * 配置redis服务器连接
 *
 * @author by Assume
 * @date 2019/3/30 20:19
 */
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)//单位秒
public class SessionConfig {
    @Value("${redis.hostname}")
    private String hostName;

    @Value("${redis.port}")
    private int port;

    @Value("${redis.password}")
    private String password;

    @Bean
    public JedisConnectionFactory connectionFactory() {
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
        connectionFactory.setPort(port);
        connectionFactory.setHostName(hostName);
        connectionFactory.setPassword(password);
        return connectionFactory;
    }
}
/**
 * 初始化Session配置
 *
 * @author by Assume
 * @date 2019/3/30 20:30
 */
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer {
    public SessionInitializer() {
        super(SessionConfig.class);
    }
}

最靠谱的分布式Session解决方案

基于令牌(Token)方式实现Session解决方案,因为Session本身就是分布式共享连接。

将生成的Token 存入到Redis中。

你可能感兴趣的:(分布式解决方案-分布式session一致性问题)